Skip to content

ngIf指令

前提

假设你已了解 Angular 框架的基本使用,知晓指令等相关概念及作用。

功能

NgIf根据表达式的值(强转为 boolean)是否为真值,来有条件的包含某个模板。当表达式计算为 true 时,Angular 会渲染 then 子句中提供的模板,当为 false 或 null 时则渲染可选的 else 子句中的模板。else 子句的默认模板是空白模板。

用法

通常使用指令的简写形式: *ngIf="condition",作为插入模板的锚点元素的属性提供。 Angular 将其扩展为更明确的版本,其中锚点元素包含在 <ng-template> 元素中。

具有简写语法的简单形式:

html
<div *ngIf="condition">condition为true时内容将被渲染</div>

具有扩展语法形式:

html
<ng-template [ngIf]="condition">
  <div>condition为true时内容将被渲染</div>
</ng-template>

带有 else 块的格式:

html
<div *ngIf="condition; else elseBlock">条件为true时内容将被渲染</div>

<ng-template #elseBlock>condition为false时内容将被渲染</ng-template>

带有 then else 块的格式:

html
<div *ngIf="condition; then thenBlock else elseBlock"></div>
<ng-template #thenBlock>condition为true时内容将被渲染</ng-template>
<ng-template #elseBlock>condition为false时内容将被渲染</ng-template>

将值存储为本地变量:

html
<div *ngIf="condition as value; else elseBlock">{{value}}</div>
<ng-template #elseBlock>value为null时内容被渲染</ng-template>

简写语法

html
<div class="hero-list" *ngIf="heroes else loading">...</div>

<ng-template #loading>
  <div>Loading...</div>
</ng-template>

等同于

html
<ng-template [ngIf]="heroes" [ngIfElse]="loading">
  <div class="hero-list">...</div>
</ng-template>

<ng-template #loading>
  <div>Loading...</div>
</ng-template>

源码分析

源码:

ts
@Directive({
  selector: '[ngIf]',
  standalone: true,
})
export class NgIf<T = unknown> {
  private _context: NgIfContext<T> = new NgIfContext<T>();
  private _thenTemplateRef: TemplateRef<NgIfContext<T>>|null = null;
  private _elseTemplateRef: TemplateRef<NgIfContext<T>>|null = null;
  private _thenViewRef: EmbeddedViewRef<NgIfContext<T>>|null = null;
  private _elseViewRef: EmbeddedViewRef<NgIfContext<T>>|null = null;

  constructor(private _viewContainer: ViewContainerRef, templateRef: TemplateRef<NgIfContext<T>>) {
    this._thenTemplateRef = templateRef;
  }

  /**
   * The Boolean expression to evaluate as the condition for showing a template.
   */
  @Input()
  set ngIf(condition: T) {
    this._context.$implicit = this._context.ngIf = condition;
    this._updateView();
  }

  /**
   * A template to show if the condition expression evaluates to true.
   */
  @Input()
  set ngIfThen(templateRef: TemplateRef<NgIfContext<T>>|null) {
    assertTemplate('ngIfThen', 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 ngIfElse(templateRef: TemplateRef<NgIfContext<T>>|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 static field 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`, the binding expression itself is used to
   * narrow its type, which allows the strictNullChecks feature of TypeScript to work with `NgIf`.
   */
  static ngTemplateGuard_ngIf: 'binding';

  /**
   * Asserts the correct type of the context for the template that `NgIf` will render.
   *
   * The presence of this method is a signal to the Ivy template type-check compiler that the
   * `NgIf` structural directive renders its template with a specific context type.
   */
  static ngTemplateContextGuard<T>(dir: NgIf<T>, ctx: any):
      ctx is NgIfContext<Exclude<T, false|0|''|null|undefined>> {
    return true;
  }
}

/**
 * @publicApi
 */
export class NgIfContext<T = unknown> {
  public $implicit: T = null!;
  public ngIf: T = null!;
}

function assertTemplate(property: string, templateRef: TemplateRef<any>|null): void {
  const isTemplateRefOrNull = !!(!templateRef || templateRef.createEmbeddedView);
  if (!isTemplateRefOrNull) {
    throw new Error(`${property} must be a TemplateRef, but received '${stringify(templateRef)}'.`);
  }
}

核心实现部分是47行_updateView()方法,主要是使用ViewContainercreateEmbeddedView方法,将ng-template实例传入,进行显示

转载请注明来源