UNPKG

@rx-angular/template

Version:

**Fully** Reactive Component Template Rendering in Angular. @rx-angular/template aims to be a reflection of Angular's built in renderings just reactive.

432 lines (431 loc) 15.9 kB
import { DoCheck, EmbeddedViewRef, NgIterable, OnDestroy, OnInit, Signal, TemplateRef, TrackByFunction, ViewContainerRef } from '@angular/core'; import { RxStrategyNames } from '@rx-angular/cdk/render-strategies'; import { RxListViewComputedContext } from '@rx-angular/cdk/template'; import { Observable, Subject } from 'rxjs'; import { RxVirtualForViewContext, RxVirtualViewRepeater } from './model'; import * as i0 from "@angular/core"; /** * @Directive RxVirtualFor * * @description * * The `*rxVirtualFor` structural directive provides a convenient and performant * way for rendering huge lists of items. It brings all the benefits `rxFor` does, * and implements virtual rendering. * * Instead of rendering every item provided, rxVirtualFor only renders what is * currently visible to the user, thus providing excellent runtime performance * for huge sets of data. * * The technique to render items is comparable to the on used by twitter and * explained in very much detail by @DasSurma in his blog post about the [complexities * of infinite scrollers](https://developer.chrome.com/blog/infinite-scroller/). * * "Each recycling of a DOM element would normally relayout the entire runway which * would bring us well below our target of 60 frames per second. * To avoid this, we are taking the burden of layout onto ourselves and use * absolutely positioned elements with transforms." (@DasSurma) * * ## API * The API is a combination of \@rx-angular/template/for & * \@angular/cdk `*cdkVirtualFor`. * * trackBy: `(index: number, item: T) => any` | `keyof T` * * strategy: `string` | `Observable<string>` * * parent: `boolean`; * * renderCallback: `Subject<T[]>` * * viewCache: `number` * * (Injected) scrollStrategy: `RxVirtualScrollStrategy<T, U>` * * provides itself as RxVirtualViewRepeater for RxVirtualViewPortComponent to operate * * ## Features * * Push based architecture * * Comprehensive set of context variables * * Opt-out of `NgZone` with `patchZone` * * Notify when rendering of child templates is finished (`renderCallback`) * * Super efficient layouting with css transformations * * Define a viewCache in order to re-use views instead of re-creating them * * Configurable RxVirtualScrollStrategy<T, U> providing the core logic to calculate the viewRange and position DOM * Nodes * * ### Context Variables * * The following context variables are available for each template: * * - $implicit: `T` // the default variable accessed by `let val` * - item$: `Observable<T>` // the same value as $implicit, but as `Observable` * - index: `number` // current index of the item * - count: `number` // count of all items in the list * - first: `boolean` // true if the item is the first in the list * - last: `boolean` // true if the item is the last in the list * - even: `boolean` // true if the item has on even index (index % 2 === 0) * - odd: `boolean` // the opposite of even * - index$: `Observable<number>` // index as `Observable` * - count$: `Observable<number>` // count as `Observable` * - first$: `Observable<boolean>` // first as `Observable` * - last$: `Observable<boolean>` // last as `Observable` * - even$: `Observable<boolean>` // even as `Observable` * - odd$: `Observable<boolean>` // odd as `Observable` * - select: `(keys: (keyof T)[], distinctByMap) => Observable<Partial<T>>` * // returns a selection function which * // accepts an array of properties to pluck out of every list item. The function returns the selected properties of * // the current list item as distinct `Observable` key-value-pair. See the example below: * * This example showcases the `select` view-context function used for deeply nested lists. * * ```html * <rx-virtual-scroll-viewport> * <div * autosized * *rxVirtualFor="let hero of heroes$; trackBy: trackItem; let select = select;"> * <div> * <strong>{{ hero.name }}</strong></br> * Defeated enemies: * </div> * <span *rxFor="let enemy of select(['defeatedEnemies']); trackBy: trackEnemy;"> * {{ enemy.name }} * </span> * </div> * </rx-virtual-scroll-viewport> * ``` * * ### Using the context variables * * ```html * <rx-virtual-scroll-viewport> * <div * *rxVirtualFor=" * let item of observableItems$; * let count = count; * let index = index; * let first = first; * let last = last; * let even = even; * let odd = odd; * trackBy: trackItem; * " * > * <div>{{ count }}</div> * <div>{{ index }}</div> * <div>{{ item }}</div> * <div>{{ first }}</div> * <div>{{ last }}</div> * <div>{{ even }}</div> * <div>{{ odd }}</div> * </div> * </rx-virtual-scroll-viewport> * ``` * * @docsCategory RxVirtualFor * @docsPage RxVirtualFor * @publicApi */ export declare class RxVirtualFor<T, U extends NgIterable<T> = NgIterable<T>> implements RxVirtualViewRepeater<T>, OnInit, DoCheck, OnDestroy { private readonly templateRef; private readonly scrollStrategy; private readonly iterableDiffers; private readonly cdRef; private readonly ngZone; /** @internal */ private injector; readonly viewContainer: ViewContainerRef; private readonly strategyProvider; private readonly errorHandler; private readonly defaults?; /** @internal */ private _differ?; /** @internal */ private partiallyFinished; /** @internal */ private staticValue?; /** @internal */ private renderStatic; /** * @description * The iterable input * * @example * <rx-virtual-scroll-viewport> * <app-hero *rxVirtualFor="heroes$; let hero" * [hero]="hero"></app-hero> * </rx-virtual-scroll-viewport> * * @param potentialSignalOrObservable */ set rxVirtualForOf(potentialSignalOrObservable: Observable<(U & NgIterable<T>) | undefined | null> | Signal<(U & NgIterable<T>) | undefined | null> | (U & NgIterable<T>) | null | undefined); /** * @internal * A reference to the template that is created for each item in the iterable. * @see [template reference variable](guide/template-reference-variables) * (inspired by @angular/common `ng_for_of.ts`) */ private _template?; set rxVirtualForTemplate(value: TemplateRef<RxVirtualForViewContext<T, U>>); /** @internal */ private strategyHandler; /** * @description * The rendering strategy to be used to render updates to the DOM. * Use it to dynamically manage your rendering strategy. You can switch the strategy * imperatively (with a string) or by binding an Observable. * The default strategy is `'normal'` if not configured otherwise. * * @example * \@Component({ * selector: 'app-root', * template: ` * <rx-virtual-scroll-viewport> * <app-hero * autosized * *rxVirtualFor="let hero of heroes$; strategy: strategy" * [hero]="hero"></app-hero> * </rx-virtual-scroll-viewport> * ` * }) * export class AppComponent { * strategy = 'low'; * } * * // OR * * \@Component({ * selector: 'app-root', * template: ` * <rx-virtual-scroll-viewport> * <app-hero * autosized * *rxVirtualFor="let hero of heroes$; strategy: strategy$" * [hero]="hero"></app-hero> * </rx-virtual-scroll-viewport> * ` * }) * export class AppComponent { * strategy$ = new BehaviorSubject('immediate'); * } * * @param strategyName * @see {@link strategies} */ set strategy(strategyName: RxStrategyNames<string> | Observable<RxStrategyNames<string>>); /** * @description * Controls the amount if views held in cache for later re-use when a user is * scrolling the list. If this is set to 0, `rxVirtualFor` won't cache any view, * thus destroying & re-creating very often on scroll events. */ templateCacheSize: number; /** * @description * If `parent` is set to `true` (default to `false`), `*rxVirtualFor` will * automatically run change-detection for its parent component when its scheduled * tasks are done in order to update any pending `@ContentChild` or `@ViewChild` * relation to be updated according to the updated ViewContainer. * * @example * \@Component({ * selector: 'app-root', * template: ` * <rx-virtual-scroll-viewport> * <app-list-item * *rxVirtualFor=" * let item of items$; * trackBy: trackItem; * parent: true; * " * [item]="item" * autosized * ></app-list-item> * </rx-virtual-scroll-viewport> * ` * }) * export class AppComponent { * // those queries won't be in sync with what `rxVirtualFor` is rendering * // when parent is set to false. * \@ViewChildren(AppListItem) listItems: QueryList<AppListItem>; * * items$ = itemService.getItems(); * } * * @param renderParent * * @deprecated this flag will be dropped soon, as it is no longer required when using signal based view & content queries */ renderParent: boolean; /** * @description * A flag to control whether `*rxVirtualFor` rendering happens within * `NgZone` or not. The default value is set to `true` if not configured otherwise. * If `patchZone` is set to `false` `*rxVirtualFor` will operate completely outside of `NgZone`. * * @example * \@Component({ * selector: 'app-root', * template: ` * <rx-virtual-scroll-viewport> * <app-list-item * *rxVirtualFor=" * let item of items$; * trackBy: trackItem; * patchZone: false; * " * [item]="item" * autosized * ></app-list-item> * </rx-virtual-scroll-viewport> * ` * }) * export class AppComponent { * items$ = itemService.getItems(); * } * * @param patchZone */ patchZone: boolean; /** * @description * A function or key that defines how to track changes for items in the provided * iterable data. * * When items are added, moved, or removed in the iterable, * the directive must re-render the appropriate DOM nodes. * To minimize operations on the DOM, only nodes that have changed * are re-rendered. * * By default, `rxVirtualFor` assumes that the object instance identifies * the node in the iterable (equality check `===`). * When a function or key is supplied, `rxVirtualFor` uses the result to identify the item node. * * @example * \@Component({ * selector: 'app-root', * template: ` * <rx-virtual-scroll-viewport> * <app-list-item * *rxVirtualFor=" * let item of items$; * trackBy: 'id'; * " * autosized * [item]="item" * > * </app-list-item> * </rx-virtual-scroll-viewport> * ` * }) * export class AppComponent { * items$ = itemService.getItems(); * } * * // OR * * \@Component({ * selector: 'app-root', * template: ` * <rx-virtual-scroll-viewport> * <app-list-item * *rxVirtualFor=" * let item of items$; * trackBy: trackItem; * " * autosized * [item]="item" * > * </app-list-item> * </rx-virtual-scroll-viewport> * ` * }) * export class AppComponent { * items$ = itemService.getItems(); * trackItem = (idx, item) => item.id; * } * * @param trackByFnOrKey */ set trackBy(trackByFnOrKey: keyof T | TrackByFunction<T>); /** * @description * A `Subject` which emits whenever `*rxVirtualFor` finished rendering a * set of changes to the view. * This enables developers to perform actions exactly at the timing when the * updates passed are rendered to the DOM. * The `renderCallback` is useful in situations where you rely on specific DOM * properties like the `height` of a table after all items got rendered. * It is also possible to use the renderCallback in order to determine if a * view should be visible or not. This way developers can hide a list as * long as it has not finished rendering. * * The result of the `renderCallback` will contain the currently rendered set * of items in the iterable. * * @example * \@Component({ * selector: 'app-root', * template: ` * <rx-virtual-scroll-viewport> * <app-list-item * *rxVirtualFor=" * let item of items$; * trackBy: trackItem; * renderCallback: itemsRendered; * " * autosized * [item]="item" * > * </app-list-item> * </rx-virtual-scroll-viewport> * ` * }) * export class AppComponent { * items$: Observable<Item[]> = itemService.getItems(); * trackItem = (idx, item) => item.id; * // this emits whenever rxVirtualFor finished rendering changes * itemsRendered = new Subject<Item[]>(); * } * * @param renderCallback */ set renderCallback(renderCallback: Subject<U>); /** @internal */ readonly viewsRendered$: Subject<EmbeddedViewRef<RxVirtualForViewContext<T, U, RxListViewComputedContext, keyof T>>[]>; /** @internal */ readonly viewRendered$: Subject<{ view: EmbeddedViewRef<RxVirtualForViewContext<T, U>>; index: number; item: T; }>; /** @internal */ readonly renderingStart$: Subject<Set<number>>; /** @internal */ private get template(); /** @internal */ private observables$; /** @internal */ private _renderCallback?; /** @internal */ readonly values$: Observable<NgIterable<T>>; /** @internal */ private values?; /** @internal */ private templateManager; /** @internal */ private _destroy$; /** @internal */ _trackBy: TrackByFunction<T> | null; /** @internal */ static ngTemplateContextGuard<T, U extends NgIterable<T> = NgIterable<T>, K = keyof T>(dir: RxVirtualFor<T, U>, ctx: any): ctx is RxVirtualForViewContext<T, U, RxListViewComputedContext, K>; constructor(templateRef: TemplateRef<RxVirtualForViewContext<T, U>>); /** @internal */ ngOnInit(): void; /** @internal */ ngDoCheck(): void; /** @internal */ ngOnDestroy(): void; private render; private handleError; private getDiffer; /** @internal */ private createViewContext; /** @internal */ private updateViewContext; static ɵfac: i0.ɵɵFactoryDeclaration<RxVirtualFor<any, any>, never>; static ɵdir: i0.ɵɵDirectiveDeclaration<RxVirtualFor<any, any>, "[rxVirtualFor][rxVirtualForOf]", never, { "rxVirtualForOf": { "alias": "rxVirtualForOf"; "required": false; }; "rxVirtualForTemplate": { "alias": "rxVirtualForTemplate"; "required": false; }; "strategy": { "alias": "rxVirtualForStrategy"; "required": false; }; "templateCacheSize": { "alias": "rxVirtualForTemplateCacheSize"; "required": false; }; "renderParent": { "alias": "rxVirtualForParent"; "required": false; }; "patchZone": { "alias": "rxVirtualForPatchZone"; "required": false; }; "trackBy": { "alias": "rxVirtualForTrackBy"; "required": false; }; "renderCallback": { "alias": "rxVirtualForRenderCallback"; "required": false; }; }, {}, never, never, true, never>; }