From 7883d0c2948cfb43c30b99a55e5e23fa5e12e903 Mon Sep 17 00:00:00 2001 From: Michael Berger Date: Tue, 11 Apr 2023 14:08:14 +0200 Subject: [PATCH] feat(cdk): enhance rxIfList with a template to render in falsy case --- .../lib/rx-if-list/rx-if-list.directive.ts | 119 ++++++++++++++++-- 1 file changed, 110 insertions(+), 9 deletions(-) diff --git a/libs/cdk/template/src/lib/rx-if-list/rx-if-list.directive.ts b/libs/cdk/template/src/lib/rx-if-list/rx-if-list.directive.ts index 2233d77..895058d 100644 --- a/libs/cdk/template/src/lib/rx-if-list/rx-if-list.directive.ts +++ b/libs/cdk/template/src/lib/rx-if-list/rx-if-list.directive.ts @@ -1,20 +1,121 @@ -import {Directive, Input, NgModule, TemplateRef, ViewContainerRef} from '@angular/core'; +import { + Directive, + EmbeddedViewRef, + Input, + NgModule, + TemplateRef, + ViewContainerRef, + ɵstringify as stringify +} from '@angular/core'; import {CommonModule} from '@angular/common'; @Directive({ + // eslint-disable-next-line @angular-eslint/directive-selector selector: '[rxIfList]', }) export class RxIfListDirective { - @Input() set rxIfList(value: ArrayLike | null | undefined) { - this.vcr.clear(); - if ((value?.length ?? []) > 0) { - this.vcr.createEmbeddedView(this.tpl); + private _context: RxIfListContext = new RxIfListContext(); + private _thenTemplateRef: TemplateRef|null = null; + private _elseTemplateRef: TemplateRef|null = null; + private _thenViewRef: EmbeddedViewRef|null = null; + private _elseViewRef: EmbeddedViewRef|null = null; + /** @internal */ + static rxIfListUseIfTypeGuard: void; + + /** + * Assert the correct type of the expression bound to the `rxIfList` input within the template. + * + * The presence of this static field is a signal to the Ivy template type check compiler that + * when the `rxIfList` structural directive renders its template, the type of the expression bound + * to `rxIfList` should be narrowed in some way. For `rxIfList`, the binding expression itself is used to + * narrow its type, which allows the strictNullChecks feature of TypeScript to work with `rxIfList`. + */ + static ngTemplateGuard_rxIfList: 'binding'; + + /** + * Asserts the correct type of the context for the template that `rxIfList` will render. + * + * The presence of this method is a signal to the Ivy template type-check compiler that the + * `rxIfList` structural directive renders its template with a specific context type. + */ + static ngTemplateContextGuard(dir: RxIfListDirective, ctx: any): + ctx is RxIfListContext{ + return true; + } + + constructor(private _viewContainer: ViewContainerRef, templateRef: TemplateRef) { + this._thenTemplateRef = templateRef; + } + + /** + * The Boolean expression to evaluate as the condition for showing a template. + */ + @Input() + set rxIfList(value: ArrayLike | null | undefined) { + this._context.$implicit = this._context.rxIfList = ((value?.length ?? []) > 0); + this._updateView(); + } + + /** + * A template to show if the condition expression evaluates to true. + */ + @Input() + set rxIfListThen(templateRef: TemplateRef|null) { + assertTemplate('rxIfListThen', templateRef); + this._thenTemplateRef = templateRef; + this._thenViewRef = null; // clear previous view if any. + this._updateView(); + } + + /** + * A template to show if the condition expression evaluates to false. + */ + @Input() + set rxIfListElse(templateRef: TemplateRef|null) { + assertTemplate('rxIfListElse', templateRef); + this._elseTemplateRef = templateRef; + this._elseViewRef = null; // clear previous view if any. + this._updateView(); + } + + private _updateView() { + if (this._context.$implicit) { + if (!this._thenViewRef) { + this._viewContainer.clear(); + this._elseViewRef = null; + if (this._thenTemplateRef) { + this._thenViewRef = + this._viewContainer.createEmbeddedView(this._thenTemplateRef, this._context); + } + } + } else { + if (!this._elseViewRef) { + this._viewContainer.clear(); + this._thenViewRef = null; + if (this._elseTemplateRef) { + this._elseViewRef = + this._viewContainer.createEmbeddedView(this._elseTemplateRef, this._context); + } + } } } - constructor( - private readonly vcr: ViewContainerRef, - private readonly tpl: TemplateRef - ) {} + + +} + +/** + * @publicApi + */ +export class RxIfListContext { + $implicit = false; + rxIfList = false; +} + +function assertTemplate(property: string, templateRef: TemplateRef|null): void { + const isTemplateRefOrNull = !!(!templateRef || templateRef.createEmbeddedView); + if (!isTemplateRefOrNull) { + throw new Error(`${property} must be a TemplateRef, but received '${stringify(templateRef)}'.`); + } } @NgModule({