-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
235 additions
and
17 deletions.
There are no files selected for viewing
10 changes: 9 additions & 1 deletion
10
...app/scroll-strategies/components/scroll-advanced-item/scroll-advanced-item.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,9 @@ | ||
<p>scroll-advanced-item works!</p> | ||
<ion-item> | ||
<ion-avatar slot="start"> | ||
<ion-img [src]="item()?.photo"></ion-img> | ||
</ion-avatar> | ||
<ion-label class="ion-text-wrap"> | ||
<h2>{{ item()?.name }}</h2> | ||
<p>{{ item()?.description }}</p> | ||
</ion-label> | ||
</ion-item> |
33 changes: 31 additions & 2 deletions
33
...c/app/scroll-strategies/components/scroll-advanced-item/scroll-advanced-item.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,41 @@ | ||
import { Component, OnInit } from '@angular/core'; | ||
import { Component, effect, ElementRef, inject, input, OnInit } from '@angular/core'; | ||
import { ScrollAdvancedItem } from '../../scroll-strategies.type'; | ||
import { IonAvatar, IonImg, IonItem, IonLabel } from '@ionic/angular/standalone'; | ||
import { ScrollAdvancedCalcService } from '../../scroll-advanced-calc.service'; | ||
|
||
@Component({ | ||
selector: 'app-scroll-advanced-item', | ||
templateUrl: './scroll-advanced-item.component.html', | ||
styleUrls: ['./scroll-advanced-item.component.scss'], | ||
standalone: true, | ||
imports: [IonItem, IonAvatar, IonImg, IonLabel], | ||
}) | ||
export class ScrollAdvancedItemComponent implements OnInit { | ||
constructor() {} | ||
item = input<ScrollAdvancedItem>(); | ||
#el = inject(ElementRef); | ||
#calcService = inject(ScrollAdvancedCalcService); | ||
|
||
constructor() { | ||
effect(() => | ||
(async (item: ScrollAdvancedItem | undefined) => { | ||
if (item === undefined) { | ||
return; | ||
} | ||
// Wait for rendering | ||
await new Promise((resolve) => requestAnimationFrame(resolve)); | ||
this.#calcService.cacheCalcDynamic.update((cache) => { | ||
if (!cache.some((c) => c.trackId === item.trackId)) { | ||
cache.push({ | ||
trackId: item.trackId, | ||
itemSize: this.#el.nativeElement.getBoundingClientRect().height, | ||
}); | ||
} | ||
// Update, but not notify to computed | ||
return cache; | ||
}); | ||
})(this.item()), | ||
); | ||
} | ||
|
||
ngOnInit() {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
121 changes: 116 additions & 5 deletions
121
projects/demo/src/app/scroll-strategies/pages/scroll-advanced/scroll-advanced.page.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,128 @@ | ||
import { Component, OnInit } from '@angular/core'; | ||
import { Component, computed, inject, OnInit, signal, viewChild } from '@angular/core'; | ||
import { CommonModule } from '@angular/common'; | ||
import { FormsModule } from '@angular/forms'; | ||
import { IonBackButton, IonButtons, IonContent, IonHeader, IonTitle, IonToolbar } from '@ionic/angular/standalone'; | ||
import { | ||
IonAvatar, | ||
IonBackButton, | ||
IonButton, | ||
IonButtons, | ||
IonContent, | ||
IonHeader, | ||
IonIcon, | ||
IonImg, | ||
IonInfiniteScroll, | ||
IonInfiniteScrollContent, | ||
IonItem, | ||
IonLabel, | ||
IonList, | ||
IonTitle, | ||
IonToolbar, | ||
ViewDidEnter, | ||
ViewDidLeave, | ||
ViewWillEnter, | ||
} from '@ionic/angular/standalone'; | ||
import { CdkDynamicSizeVirtualScroll, DynamicSizeVirtualScrollService, itemDynamicSize } from 'scroll-strategies'; | ||
import { InfiniteScrollCustomEvent } from '@ionic/angular'; | ||
import { DynamicSizeCache, ScrollAdvancedItem } from '../../scroll-strategies.type'; | ||
import { CdkVirtualForOf, CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; | ||
import { FixVirtualScrollElementDirective } from 'scroll-header'; | ||
import { ScrollAdvancedItemComponent } from '../../components/scroll-advanced-item/scroll-advanced-item.component'; | ||
import { ScrollAdvancedCalcService } from '../../scroll-advanced-calc.service'; | ||
import { Subscription } from 'rxjs'; | ||
|
||
@Component({ | ||
selector: 'app-scroll-advanced', | ||
templateUrl: './scroll-advanced.page.html', | ||
styleUrls: ['./scroll-advanced.page.scss'], | ||
standalone: true, | ||
imports: [IonContent, IonHeader, IonTitle, IonToolbar, CommonModule, FormsModule, IonBackButton, IonButtons], | ||
imports: [ | ||
IonContent, | ||
IonHeader, | ||
IonTitle, | ||
IonToolbar, | ||
CommonModule, | ||
FormsModule, | ||
IonBackButton, | ||
IonButtons, | ||
IonButton, | ||
IonIcon, | ||
CdkDynamicSizeVirtualScroll, | ||
CdkVirtualForOf, | ||
CdkVirtualScrollViewport, | ||
FixVirtualScrollElementDirective, | ||
IonInfiniteScroll, | ||
IonInfiniteScrollContent, | ||
IonList, | ||
IonItem, | ||
IonAvatar, | ||
IonImg, | ||
IonLabel, | ||
ScrollAdvancedItemComponent, | ||
], | ||
}) | ||
export class ScrollAdvancedPage implements OnInit { | ||
export class ScrollAdvancedPage implements OnInit, ViewDidEnter, ViewDidLeave { | ||
readonly virtualScroll = viewChild(CdkVirtualScrollViewport); | ||
readonly #enterSubscription$: Subscription[] = []; | ||
|
||
readonly #scroll = inject(DynamicSizeVirtualScrollService); | ||
readonly #calcService = inject(ScrollAdvancedCalcService); | ||
|
||
readonly items = signal<ScrollAdvancedItem[]>([]); | ||
readonly page = signal<number>(0); | ||
readonly dynamicSize = computed<itemDynamicSize[]>(() => { | ||
return this.#calcService.changeItemsToDynamicItemSize(this.items(), this.#calcService.cacheCalcDynamic(), this.virtualScroll()); | ||
}); | ||
readonly bindDynamicItemHeight = this.#scroll.getBindDynamicItemHeight(this.dynamicSize); | ||
|
||
constructor() {} | ||
|
||
ngOnInit() {} | ||
ngOnInit() { | ||
this.items.set(this.#createItems(100)); | ||
} | ||
|
||
ionViewDidEnter() { | ||
this.#enterSubscription$.push( | ||
this.virtualScroll()!.scrolledIndexChange.subscribe(() => { | ||
if (this.#calcService.beforeCacheCalcDynamicSize() !== this.#calcService.cacheCalcDynamic().length) { | ||
this.#calcService.cacheCalcDynamic.update((cache) => [...cache]); | ||
this.#calcService.beforeCacheCalcDynamicSize.set(this.#calcService.cacheCalcDynamic().length); | ||
} | ||
}), | ||
); | ||
} | ||
|
||
ionViewDidLeave() { | ||
this.#enterSubscription$.forEach((subscription) => subscription.unsubscribe()); | ||
} | ||
|
||
async loadInfinite(event: InfiniteScrollCustomEvent) { | ||
await new Promise((resolve) => setTimeout(resolve, 500)); | ||
this.items.update((items) => { | ||
// You must create new array. | ||
return [...items, ...this.#createItems(100)]; | ||
}); | ||
await event.target.complete(); | ||
} | ||
|
||
#createItems(length: number): ScrollAdvancedItem[] { | ||
this.page.update((page) => page + 1); | ||
return Array.from({ length }).map((_, index) => { | ||
const nameLength = Math.floor(Math.random() * (10 - 5)) + 5; | ||
const descriptionLength = Math.floor(Math.random() * (400 - 20)) + 20; | ||
return { | ||
trackId: this.page() + '-' + index, | ||
name: Array.from({ length: 100 }) | ||
.map(() => Math.random().toString(36)) | ||
.join() | ||
.slice(-nameLength), | ||
description: Array.from({ length: 100 }) | ||
.map(() => Math.random().toString(36)) | ||
.join() | ||
.slice(-descriptionLength), | ||
photo: `https://picsum.photos/200?random=${index}`, | ||
}; | ||
}); | ||
} | ||
|
||
trackByFn = (_: number, item: ScrollAdvancedItem) => item.trackId; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
4 changes: 2 additions & 2 deletions
4
projects/demo/src/app/scroll-strategies/pages/scroll-reverse/scroll-reverse.page.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
36 changes: 35 additions & 1 deletion
36
projects/demo/src/app/scroll-strategies/scroll-advanced-calc.service.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,42 @@ | ||
import { Injectable } from '@angular/core'; | ||
import { inject, Injectable, signal } from '@angular/core'; | ||
import { DOCUMENT } from '@angular/common'; | ||
import { DynamicSizeCache, ScrollAdvancedItem } from './scroll-strategies.type'; | ||
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; | ||
import { itemDynamicSize } from 'scroll-strategies'; | ||
|
||
@Injectable({ | ||
providedIn: 'root', | ||
}) | ||
export class ScrollAdvancedCalcService { | ||
readonly cacheCalcDynamic = signal<DynamicSizeCache[]>([]); | ||
readonly beforeCacheCalcDynamicSize = signal<number>(0); | ||
|
||
constructor() {} | ||
|
||
changeItemsToDynamicItemSize( | ||
items: ScrollAdvancedItem[], | ||
dynamicSizeCache: DynamicSizeCache[], | ||
virtualScroll: CdkVirtualScrollViewport | undefined, | ||
): itemDynamicSize[] { | ||
if (virtualScroll === undefined) { | ||
return []; | ||
} | ||
return ( | ||
items?.map((item, index) => { | ||
const cacheSize = dynamicSizeCache.find((cache) => cache.trackId === item.trackId)?.itemSize; | ||
|
||
/** | ||
* 150 is approximate item size. | ||
* This is important for the accuracy of the initial display. | ||
*/ | ||
const itemSize = cacheSize || 150; | ||
|
||
return { | ||
itemSize: Math.ceil(itemSize), | ||
trackId: item.trackId, | ||
source: cacheSize !== undefined ? 'cache' : 'temporary', | ||
}; | ||
}) || [] | ||
); | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
projects/demo/src/app/scroll-strategies/scroll-strategies.type.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
export type ScrollAdvancedItem = { | ||
trackId: string; | ||
name: string; | ||
description: string; | ||
photo: string; | ||
}; | ||
|
||
export type DynamicSizeCache = { trackId: number | string; itemSize: number }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters