@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
TypeScript
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 };