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.

447 lines (444 loc) 16.2 kB
import * as i0 from '@angular/core'; import { OnInit, OnDestroy, OnChanges, Signal, TemplateRef, SimpleChanges } from '@angular/core'; import { RxNotificationKind, RxNotification } from '@rx-angular/cdk/notifications'; import { RxViewContext } from '@rx-angular/cdk/template'; import { ObservableInput, Subscribable, Observable, NextObserver, Subject } from 'rxjs'; /** @internal */ interface RxLetViewContext<T> extends RxViewContext<T> { rxLet: T; } /** * @Directive RxLet * * @description * In Angular there is one way to handle asynchronous values or streams in the template, the `async` pipe. * Even though the async pipe evaluates such values in the template, it is insufficient in many ways. * To name a few: * * it will only update the template when `NgZone` is also aware of the value change * * it leads to over rendering because it can only run global change detection * * it leads to too many subscriptions in the template * * it is cumbersome to work with values in the template * * read more about the LetDirective in the [official docs](https://www.rx-angular.io/docs/template/let-directive) * * **Conclusion - Structural directives** * * In contrast to global change detection, structural directives allow fine-grained control of change detection on a per directive basis. * The `LetDirective` comes with its own way to handle change detection in templates in a very efficient way. * However, the change detection behavior is configurable on a per directive or global basis. * This makes it possible to implement your own strategies, and also provides a migration path from large existing apps running with Angulars default change detection. * * This package helps to reduce code used to create composable action streams. * It mostly is used in combination with state management libs to handle user interaction and backend communication. * * ```html * <ng-container *rxLet="observableNumber$; let n"> * ... * </ng-container> * ``` * * * @docsCategory LetDirective * @docsPage LetDirective * @publicApi */ declare class RxLet<U> implements OnInit, OnDestroy, OnChanges { private templateRef; /** @internal */ private strategyProvider; /** @internal */ private cdRef; private injector; /** @internal */ private ngZone; /** @internal */ private viewContainerRef; /** @internal */ private errorHandler; static ngTemplateGuard_rxLet: 'binding'; /** * @description * The Observable or value to be bound to the context of a template. * * @example * const hero1 = {name: 'Batman'}; * const hero$ = of(hero); * * <ng-container *rxLet="hero1; let hero"> * <app-hero [hero]="hero"></app-hero> * </ng-container> * * <ng-container *rxLet="hero$; let hero"> * <app-hero [hero]="hero"></app-hero> * </ng-container> * * @param { ObservableInput<U> | U | null | undefined } rxLet */ rxLet: ObservableInput<U> | Subscribable<U> | Signal<U> | U | null | undefined; /** * @description * * You can change the used `RenderStrategy` by using the `strategy` input of the `*rxLet`. It accepts * an `Observable<RxStrategyNames>` or [`RxStrategyNames`](https://github.com/rx-angular/rx-angular/blob/b0630f69017cc1871d093e976006066d5f2005b9/libs/cdk/render-strategies/src/lib/model.ts#L52). * * The default value for strategy is * [`normal`](https://www.rx-angular.io/docs/template/cdk/render-strategies/strategies/concurrent-strategies). * * Read more about this in the * [official docs](https://www.rx-angular.io/docs/template/let-directive#use-render-strategies-strategy). * * @example * * \@Component({ * selector: 'app-root', * template: ` * <ng-container *rxLet="hero$; let hero; strategy: strategy"> * <app-hero [hero]="hero"></app-hero> * </ng-container> * * <ng-container *rxLet="hero$; let hero; strategy: strategy$"> * <app-hero [hero]="hero"></app-hero> * </ng-container> * ` * }) * export class AppComponent { * strategy = 'low'; * strategy$ = of('immediate'); * } * * @param { string | Observable<string> | undefined } strategyName * @see {@link RxStrategyNames} */ set strategy(strategyName: string | Observable<string> | undefined); /** * @description * Defines the template for the complete state. Will be * shown when the bound Observable is in "complete" state. * * @example * <ng-container *rxLet="hero$; let hero; complete: completeTemplate"> * <app-hero [hero]="hero"></app-hero> * </ng-container> * <ng-template #completeTemplate> * <mat-icon>thumb_up</mat-icon> * </ng-template> * * @param { TemplateRef<RxLetViewContext<U | undefined | null> | null> } complete */ complete: TemplateRef<RxLetViewContext<U | undefined | null> | null>; /** * @description * Defines the template for the error state. Will be * shown when the bound Observable is in "error" state. * * @example * <ng-container *rxLet="hero$; let hero; error: errorTemplate"> * <app-hero [hero]="hero"></app-hero> * </ng-container> * <ng-template #errorTemplate> * <mat-icon>thumb_down</mat-icon> * </ng-template> * * @param { TemplateRef<RxLetViewContext<U | undefined | null> | null> } error */ error: TemplateRef<RxLetViewContext<U | undefined | null> | null>; /** * @description * Defines the template for the suspense state. Will be * shown when the bound Observable is in "suspense" state. * Suspense means any undefined value, a never emitted value or `NEVER` itself. * * @example * <ng-container *rxLet="hero$; let hero; suspense: suspenseTemplate"> * <app-hero [hero]="hero"></app-hero> * </ng-container> * <ng-template #suspenseTemplate> * <mat-progress-spinner></mat-progress-spinner> * </ng-template> * * @param { TemplateRef<RxLetViewContext<U | undefined | null> | null> } suspense */ suspense: TemplateRef<RxLetViewContext<U | undefined | null> | null>; /** * @description * A trigger to manually set the active template. It accepts a `RxNotificationKind` * which determines what template to display. If no template is given, a context * variable resembling the notification state is put into the `Next` * template of the directive * * @example * <ng-container * *rxLet=" * hero$; * let hero; * let e = error; * contextTrigger: contextTrigger$ * "> * * <app-hero [hero]="hero"></app-hero> * <error *ngIf="e"></error> * </ng-container> * * // trigger template from component.ts * contextTrigger$.next(RxNotificationKind.error) * * @param { Observable<RxNotificationKind> } contextTrigger * @see {@link RxNotificationKind} */ contextTrigger?: Observable<RxNotificationKind>; /** * @description * A trigger to manually activate the complete template. It accepts any value, * on emission it will display the error template. If no template is given, * the complete context variable will complete set to true instead. * * @example * <ng-container * *rxLet=" * hero$; * let hero; * let c = complete; * completeTrigger: completeTrigger$ * "> * * <app-hero [hero]="hero"></app-hero> * <done *ngIf="c"></done> * </ng-container> * * // trigger template from component.ts * completeTrigger$.next() * * @param { Observable<unknown> } completeTrigger */ completeTrigger?: Observable<unknown>; /** * @description * A trigger to manually activate the error template. It accepts any value, * on emission it will display the error template. If no template is given, * the error context variable will be set to true instead. * * @example * <ng-container * *rxLet=" * hero$; * let hero; * let e = error; * errorTrigger: errorTrigger$ * "> * * <app-hero [hero]="hero"></app-hero> * <error *ngIf="e"></error> * </ng-container> * * // trigger template from component.ts * errorTrigger$.next() * * @param { Observable<unknown> } errorTrigger */ errorTrigger?: Observable<unknown>; /** * @description * A trigger to manually activate the suspense template. It accepts any value, * on emission it will display the suspense template. If no template is given, * the suspense context variable will be set to true instead. * * @example * <ng-container * *rxLet=" * hero$; * let hero; * let s = suspense; * suspenseTrigger: suspenseTrigger$ * "> * * <app-hero [hero]="hero"></app-hero> * <loader *ngIf="s"></loader> * </ng-container> * * * // trigger template from component.ts * suspenseTrigger$.next() * * @param { Observable<unknown> } suspenseTrigger */ suspenseTrigger?: Observable<unknown>; /** * @description * A trigger to manually activate the default template. It accepts any value, * on emission it will switch to the let directives default template. * * @example * <ng-container * *rxLet=" * hero$; * let hero; * suspense: suspense * nextTrigger: nextTrigger$ * "> * * <app-hero [hero]="hero"></app-hero> * </ng-container> * * <ng-template #suspense><loader></loader></ng-template> * * // trigger template from component.ts * nextTrigger$.next() * * @param { Observable<unknown> } nextTrigger */ nextTrigger?: Observable<unknown>; /** * @description * A `Subject` which emits whenever *rxFor finished rendering a set changes to the view. * This enables developers to perform actions when a list has finished rendering. * The `renderCallback` is useful in situations where you rely on specific DOM properties like the `height` 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: ` * <app-list-component> * <app-list-item * *rxFor=" * let item of items$; * trackBy: trackItem; * renderCallback: itemsRendered; * "> * <div>{{ item.name }}</div> * </app-list-item> * </app-list-component> * ` * }) * export class AppComponent { * items$: Observable<Item[]> = itemService.getItems(); * trackItem = (idx, item) => item.id; * // this emits whenever rxFor finished rendering changes * itemsRendered = new Subject<Item[]>(); * * constructor(elementRef: ElementRef<HTMLElement>) { * itemsRendered.subscribe(() => { * // items are rendered, we can now scroll * elementRef.scrollTo({bottom: 0}); * }) * } * } * * @param callback */ set renderCallback(callback: NextObserver<U>); /** * @description * * When local rendering strategies are used, we need to treat view and content queries in a * special way. * To make `*rxLet` in such situations, a certain mechanism is implemented to * execute change detection on the parent (`parent`). * * This is required if your components state is dependent on its view or content children: * * - `@ViewChild` * - `@ViewChildren` * - `@ContentChild` * - `@ContentChildren` * * Read more about this in the * [official * docs](https://www.rx-angular.io/docs/template/let-directive#local-strategies-and-view-content-queries-parent). * * @example * \@Component({ * selector: 'app-root', * template: ` * <app-list-component> * <app-list-item * *rxLet=" * item$; * let item; * parent: true; * " * > * <div>{{ item.name }}</div> * </app-list-item> * </app-list-component> * ` * }) * export class AppComponent { * item$ = itemService.getItem(); * } * * @param boolean * * @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 *rxLet templates are created within `NgZone` or not. * The default value is `true, `*rxLet` will create it's `EmbeddedViews` inside `NgZone`. * * Event listeners normally trigger zone. Especially high frequently events cause performance issues. * * Read more about this in the * [official docs](https://www.rx-angular.io/docs/template/let-directive#working-with-event-listeners-patchzone). * * @example * \@Component({ * selector: 'app-root', * template: ` * <app-list-component> * <app-list-item * *rxLet=" * item$; * let item; * patchZone: false; * " * > * <div>{{ item.name }}</div> * </app-list-item> * </app-list-component> * ` * }) * export class AppComponent { * item$ = itemService.getItem(); * } */ patchZone: boolean; /** @internal */ private observablesHandler; /** @internal */ private strategyHandler; /** @internal */ private triggerHandler; /** @internal */ private _renderObserver; /** @internal */ private subscription; /** @internal */ private templateManager; /** @internal */ private rendered$; /** @internal */ readonly templateNotification$: Subject<RxNotification<U>>; /** @internal */ readonly values$: Observable<RxNotification<U>>; readonly rendered: Observable<void>; /** @internal */ static ngTemplateContextGuard<U>(dir: RxLet<U>, ctx: unknown | null | undefined): ctx is RxLetViewContext<U>; constructor(templateRef: TemplateRef<RxLetViewContext<U>>); /** @internal */ ngOnInit(): void; /** @internal */ ngOnChanges(changes: SimpleChanges): void; /** @internal */ ngOnDestroy(): void; /** @internal */ private _createTemplateManager; static ɵfac: i0.ɵɵFactoryDeclaration<RxLet<any>, never>; static ɵdir: i0.ɵɵDirectiveDeclaration<RxLet<any>, "[rxLet]", never, { "rxLet": { "alias": "rxLet"; "required": false; }; "strategy": { "alias": "rxLetStrategy"; "required": false; }; "complete": { "alias": "rxLetComplete"; "required": false; }; "error": { "alias": "rxLetError"; "required": false; }; "suspense": { "alias": "rxLetSuspense"; "required": false; }; "contextTrigger": { "alias": "rxLetContextTrigger"; "required": false; }; "completeTrigger": { "alias": "rxLetCompleteTrigger"; "required": false; }; "errorTrigger": { "alias": "rxLetErrorTrigger"; "required": false; }; "suspenseTrigger": { "alias": "rxLetSuspenseTrigger"; "required": false; }; "nextTrigger": { "alias": "rxLetNextTrigger"; "required": false; }; "renderCallback": { "alias": "rxLetRenderCallback"; "required": false; }; "renderParent": { "alias": "rxLetParent"; "required": false; }; "patchZone": { "alias": "rxLetPatchZone"; "required": false; }; }, { "rendered": "rendered"; }, never, never, true, never>; } export { RxLet };