|
1 | | -import {Directive, Input, NgModule, TemplateRef, ViewContainerRef} from '@angular/core'; |
| 1 | +import { |
| 2 | + Directive, |
| 3 | + EmbeddedViewRef, |
| 4 | + Input, |
| 5 | + NgModule, |
| 6 | + TemplateRef, |
| 7 | + ViewContainerRef, |
| 8 | + ɵstringify as stringify |
| 9 | +} from '@angular/core'; |
2 | 10 | import {CommonModule} from '@angular/common'; |
3 | 11 |
|
4 | 12 | @Directive({ |
| 13 | + // eslint-disable-next-line @angular-eslint/directive-selector |
5 | 14 | selector: '[rxIfList]', |
6 | 15 | }) |
7 | 16 | export class RxIfListDirective { |
8 | | - @Input() set rxIfList(value: ArrayLike<unknown> | null | undefined) { |
9 | | - this.vcr.clear(); |
10 | | - if ((value?.length ?? []) > 0) { |
11 | | - this.vcr.createEmbeddedView(this.tpl); |
| 17 | + private _context: RxIfListContext = new RxIfListContext(); |
| 18 | + private _thenTemplateRef: TemplateRef<RxIfListContext>|null = null; |
| 19 | + private _elseTemplateRef: TemplateRef<RxIfListContext>|null = null; |
| 20 | + private _thenViewRef: EmbeddedViewRef<RxIfListContext>|null = null; |
| 21 | + private _elseViewRef: EmbeddedViewRef<RxIfListContext>|null = null; |
| 22 | + /** @internal */ |
| 23 | + static rxIfListUseIfTypeGuard: void; |
| 24 | + |
| 25 | + /** |
| 26 | + * Assert the correct type of the expression bound to the `rxIfList` input within the template. |
| 27 | + * |
| 28 | + * The presence of this static field is a signal to the Ivy template type check compiler that |
| 29 | + * when the `rxIfList` structural directive renders its template, the type of the expression bound |
| 30 | + * to `rxIfList` should be narrowed in some way. For `rxIfList`, the binding expression itself is used to |
| 31 | + * narrow its type, which allows the strictNullChecks feature of TypeScript to work with `rxIfList`. |
| 32 | + */ |
| 33 | + static ngTemplateGuard_rxIfList: 'binding'; |
| 34 | + |
| 35 | + /** |
| 36 | + * Asserts the correct type of the context for the template that `rxIfList` will render. |
| 37 | + * |
| 38 | + * The presence of this method is a signal to the Ivy template type-check compiler that the |
| 39 | + * `rxIfList` structural directive renders its template with a specific context type. |
| 40 | + */ |
| 41 | + static ngTemplateContextGuard<T>(dir: RxIfListDirective, ctx: any): |
| 42 | + ctx is RxIfListContext{ |
| 43 | + return true; |
| 44 | + } |
| 45 | + |
| 46 | + constructor(private _viewContainer: ViewContainerRef, templateRef: TemplateRef<RxIfListContext>) { |
| 47 | + this._thenTemplateRef = templateRef; |
| 48 | + } |
| 49 | + |
| 50 | + /** |
| 51 | + * The Boolean expression to evaluate as the condition for showing a template. |
| 52 | + */ |
| 53 | + @Input() |
| 54 | + set rxIfList(value: ArrayLike<unknown> | null | undefined) { |
| 55 | + this._context.$implicit = this._context.rxIfList = ((value?.length ?? []) > 0); |
| 56 | + this._updateView(); |
| 57 | + } |
| 58 | + |
| 59 | + /** |
| 60 | + * A template to show if the condition expression evaluates to true. |
| 61 | + */ |
| 62 | + @Input() |
| 63 | + set rxIfListThen(templateRef: TemplateRef<RxIfListContext>|null) { |
| 64 | + assertTemplate('rxIfListThen', templateRef); |
| 65 | + this._thenTemplateRef = templateRef; |
| 66 | + this._thenViewRef = null; // clear previous view if any. |
| 67 | + this._updateView(); |
| 68 | + } |
| 69 | + |
| 70 | + /** |
| 71 | + * A template to show if the condition expression evaluates to false. |
| 72 | + */ |
| 73 | + @Input() |
| 74 | + set rxIfListElse(templateRef: TemplateRef<RxIfListContext>|null) { |
| 75 | + assertTemplate('rxIfListElse', templateRef); |
| 76 | + this._elseTemplateRef = templateRef; |
| 77 | + this._elseViewRef = null; // clear previous view if any. |
| 78 | + this._updateView(); |
| 79 | + } |
| 80 | + |
| 81 | + private _updateView() { |
| 82 | + if (this._context.$implicit) { |
| 83 | + if (!this._thenViewRef) { |
| 84 | + this._viewContainer.clear(); |
| 85 | + this._elseViewRef = null; |
| 86 | + if (this._thenTemplateRef) { |
| 87 | + this._thenViewRef = |
| 88 | + this._viewContainer.createEmbeddedView(this._thenTemplateRef, this._context); |
| 89 | + } |
| 90 | + } |
| 91 | + } else { |
| 92 | + if (!this._elseViewRef) { |
| 93 | + this._viewContainer.clear(); |
| 94 | + this._thenViewRef = null; |
| 95 | + if (this._elseTemplateRef) { |
| 96 | + this._elseViewRef = |
| 97 | + this._viewContainer.createEmbeddedView(this._elseTemplateRef, this._context); |
| 98 | + } |
| 99 | + } |
12 | 100 | } |
13 | 101 | } |
14 | | - constructor( |
15 | | - private readonly vcr: ViewContainerRef, |
16 | | - private readonly tpl: TemplateRef<unknown> |
17 | | - ) {} |
| 102 | + |
| 103 | + |
| 104 | +} |
| 105 | + |
| 106 | +/** |
| 107 | + * @publicApi |
| 108 | + */ |
| 109 | +export class RxIfListContext { |
| 110 | + $implicit = false; |
| 111 | + rxIfList = false; |
| 112 | +} |
| 113 | + |
| 114 | +function assertTemplate(property: string, templateRef: TemplateRef<any>|null): void { |
| 115 | + const isTemplateRefOrNull = !!(!templateRef || templateRef.createEmbeddedView); |
| 116 | + if (!isTemplateRefOrNull) { |
| 117 | + throw new Error(`${property} must be a TemplateRef, but received '${stringify(templateRef)}'.`); |
| 118 | + } |
18 | 119 | } |
19 | 120 |
|
20 | 121 | @NgModule({ |
|
0 commit comments