/** * @license * Copyright Google Inc. All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import {Directive, EmbeddedViewRef, Input, TemplateRef, ViewContainerRef, ɵstringify as stringify} from '@angular/core'; /** * Conditionally includes a template based on the value of an `expression`. * * `ngIf` evaluates the `expression` and then renders the `then` or `else` template in its place * when expression is truthy or falsy respectively. Typically the: * - `then` template is the inline template of `ngIf` unless bound to a different value. * - `else` template is blank unless it is bound. * * * @usageNotes * * ### Most common usage * * The most common usage of the `ngIf` directive is to conditionally show the inline template as * seen in this example: * {@example common/ngIf/ts/module.ts region='NgIfSimple'} * * ### Showing an alternative template using `else` * * If it is necessary to display a template when the `expression` is falsy use the `else` template * binding as shown. Note that the `else` binding points to a `` labeled `#elseBlock`. * The template can be defined anywhere in the component view but is typically placed right after * `ngIf` for readability. * * {@example common/ngIf/ts/module.ts region='NgIfElse'} * * ### Using non-inlined `then` template * * Usually the `then` template is the inlined template of the `ngIf`, but it can be changed using * a binding (just like `else`). Because `then` and `else` are bindings, the template references can * change at runtime as shown in this example. * * {@example common/ngIf/ts/module.ts region='NgIfThenElse'} * * ### Storing conditional result in a variable * * A common pattern is that we need to show a set of properties from the same object. If the * object is undefined, then we have to use the safe-traversal-operator `?.` to guard against * dereferencing a `null` value. This is especially the case when waiting on async data such as * when using the `async` pipe as shown in following example: * * ``` * Hello {{ (userStream|async)?.last }}, {{ (userStream|async)?.first }}! * ``` * * There are several inefficiencies in the above example: * - We create multiple subscriptions on `userStream`. One for each `async` pipe, or two in the * example above. * - We cannot display an alternative screen while waiting for the data to arrive asynchronously. * - We have to use the safe-traversal-operator `?.` to access properties, which is cumbersome. * - We have to place the `async` pipe in parenthesis. * * A better way to do this is to use `ngIf` and store the result of the condition in a local * variable as shown in the the example below: * * {@example common/ngIf/ts/module.ts region='NgIfAs'} * * Notice that: * - We use only one `async` pipe and hence only one subscription gets created. * - `ngIf` stores the result of the `userStream|async` in the local variable `user`. * - The local `user` can then be bound repeatedly in a more efficient way. * - No need to use the safe-traversal-operator `?.` to access properties as `ngIf` will only * display the data if `userStream` returns a value. * - We can display an alternative template while waiting for the data. * * ### Syntax * * Simple form: * - `
...
` * - `
...
` * * Form with an else block: * ``` *
...
* ... * ``` * * Form with a `then` and `else` block: * ``` *
* ... * ... * ``` * * Form with storing the value locally: * ``` *
{{value}}
* ... * ``` * * @ngModule CommonModule * @publicApi */ @Directive({selector: '[ngIf]'}) export class NgIf { private _context: NgIfContext = new NgIfContext(); private _thenTemplateRef: TemplateRef|null = null; private _elseTemplateRef: TemplateRef|null = null; private _thenViewRef: EmbeddedViewRef|null = null; private _elseViewRef: EmbeddedViewRef|null = null; constructor(private _viewContainer: ViewContainerRef, templateRef: TemplateRef) { this._thenTemplateRef = templateRef; } @Input() set ngIf(condition: any) { this._context.$implicit = this._context.ngIf = condition; this._updateView(); } @Input() set ngIfThen(templateRef: TemplateRef|null) { assertTemplate('ngIfThen', templateRef); this._thenTemplateRef = templateRef; this._thenViewRef = null; // clear previous view if any. this._updateView(); } @Input() set ngIfElse(templateRef: TemplateRef|null) { assertTemplate('ngIfElse', 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); } } } } /** @internal */ public static ngIfUseIfTypeGuard: void; /** * Assert the correct type of the expression bound to the `ngIf` input within the template. * * The presence of this method is a signal to the Ivy template type check compiler that when the * `NgIf` structural directive renders its template, the type of the expression bound to `ngIf` * should be narrowed in some way. For `NgIf`, it is narrowed to be non-null, which allows the * strictNullChecks feature of TypeScript to work with `NgIf`. */ static ngTemplateGuard_ngIf(dir: NgIf, expr: E): expr is NonNullable { return true; } } /** * @publicApi */ export class NgIfContext { public $implicit: any = null; public ngIf: any = null; } 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)}'.`); } }