Angular 17+ 高级教程 – Component 组件 の Structural Directive (结构型指令) & Syntax Reference (微语法)

这篇具有很好参考价值的文章主要介绍了Angular 17+ 高级教程 – Component 组件 の Structural Directive (结构型指令) & Syntax Reference (微语法)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

在 Attribute Directives 属性型指令 文章中,我们学习过了指令。指令是没有 HTML 和 CSS 的组件,它单纯用于封装 JS 的部分。

这一篇我们将继续学习另一种指令 -- Structural Directive 结构型指令。

就代码而言,Structural Directive 和 Attribute Directives 是完全一样的,只是用途不同,因此进行了区分。

Attribute Directives 通常用于监听事件,修改 class,styles 等等。

而 Structural Directive 则主要用于修改 DOM 结构。

在 Dynamic Component 和 ng-template 文章中,我们学习了如何用 MVVM 的方式动态输出/移除组件和模板,

在本文中,我们将继续利用 Structural Directive 来封装这一过程,使其变得可复用。

 文章来源地址https://www.toymoban.com/news/detail-837935.html

ngComponentOutlet

ngComponentOutlet 是 Angular built-in 的 Structural Directive,它的作用就是输出 Dynamic Component。

App Template

<button (click)="showComponent = SayHiComponent">show say hi</button>
<button (click)="showComponent = HelloWorldComponent">show hello world</button>

<ng-container [ngComponentOutlet]="showComponent">

有 2 个 Dynamic Component,一个是 SayHi 组件,一个是 HelloWorld 组件。

点击其中一个按钮它就会 createComponent -> insert to <ng-container />。如果 <ng-container /> 已经有组件,它还会先 remove 掉组件再插入新的。

AppComponent

export class AppComponent {
  showComponent!: typeof SayHiComponent | typeof HelloWorldComponent;

  SayHiComponent = SayHiComponent;
  HelloWorldComponent = HelloWorldComponent;
}

效果

Angular 17+ 高级教程 – Component 组件 の Structural Directive (结构型指令) & Syntax Reference (微语法)

如果我们自己写代码实现的话,我们要 query template/container,要 create hostView,要 insert to container,要 remove from container,非常的繁琐,用 ngComponentOutlet 就方便多了。

它也支持传入其它的 options,比如 projectableNodes、inputs 等等。

Angular 17+ 高级教程 – Component 组件 の Structural Directive (结构型指令) & Syntax Reference (微语法)

 

ngTemplateOutlet

ngTemplateOutlet 也是 Angular built-in 的 Structure Directive,它的用途是输出 ng-template。

App Template

<ng-template #template let-name="name">
  <h1>Hi, {{ name }}</h1>
</ng-template>

<ng-container [ngTemplateOutlet]="template" [ngTemplateOutletContext]="{ name: 'Derrick' }" />
<ng-container [ngTemplateOutlet]="template" [ngTemplateOutletContext]="{ name: 'Alex' }" />
<ng-container [ngTemplateOutlet]="template" [ngTemplateOutletContext]="{ name: 'Richard' }" />

AppComponent 无需任何代码。

效果

Angular 17+ 高级教程 – Component 组件 の Structural Directive (结构型指令) & Syntax Reference (微语法)

 

Control Flow 特别提示

下面教的 NgIf、NgForOf、NgSwtich 指令在 Angular v17 版本后已经被 Control Flow 取代了,Control Flow 下一篇会教。

虽然如此,我觉得作为学习 Angular,这 3 个指令是相当不错的,大家不妨看一看。

 

Show/Hide Element の NgIf 指令

效果

Angular 17+ 高级教程 – Component 组件 の Structural Directive (结构型指令) & Syntax Reference (微语法)

App Template

<button (click)="toggle()">Toggle</button>
<ng-template #template>
  <h1>Hi, Derrick</h1>
</ng-template>

AppComponent

export class AppComponent {
  @ViewChild('template')
  templateRef!: TemplateRef<void>;

  @ViewChild('template', { read: ViewContainerRef })
  viewContainerRef!: ViewContainerRef;

  toggle() {
    if (this.viewContainerRef.length === 0) {
      this.viewContainerRef.createEmbeddedView(this.templateRef);
    } else {
      this.viewContainerRef.clear();
    }
  }
}

很简单的代码,但有一个点值得注意,<ng-template> 这个节点也被用作 ViewContainer。

还记得吗?ViewContainer 可以是任何节点,不一定要是 <ng-container />。其它的比如 <div>, <my-component>, <ng-template> 都是可以作为 ViewContainer 的。

接着把它封装成 Structure Directive。

创建 ShowHide 指令

ng g d show-hide

App Template

<button (click)="show = !show">Toggle</button>
<ng-template [appShowHide]="show">
  <h1>Hi, Derrick</h1>
</ng-template>

我们用一个 show 属性来表示 show or hide。

ShowHideDirective

@Directive({
  selector: '[appShowHide]',
  standalone: true,
})
export class ShowHideDirective implements OnChanges {
  @Input('appShowHide')
  show = false;

  private templateRef: TemplateRef<void> = inject(TemplateRef);
  private viewContainerRef = inject(ViewContainerRef);

  ngOnChanges(changes: SimpleChanges): void {
    if (changes['show'].currentValue) {
      this.viewContainerRef.createEmbeddedView(this.templateRef);
    } else {
      this.viewContainerRef.clear();
    }
  }
}

3个点:

  1. 利用 @Input 与外部联系,看什么时候要 show 什么时候要 hide

  2. 利用 OnChanges Hook 监听 @input 变化

  3. 利用 inject 获取 TemplateRef 和 ViewContainerRef,之前是在 App 所以是用 @ViewChildren,现在位置换了,所以改成用 inject。

效果

Angular 17+ 高级教程 – Component 组件 の Structural Directive (结构型指令) & Syntax Reference (微语法)

NgIf 指令

show / hide 实在是一个太过普遍的需求了,所以 Angular 也有 built-in 指令 -- ngIf,我们不需要自己封装。

<button (click)="show = !show">Toggle</button>
<ng-template [ngIf]="show">
  <h1>Hi, Derrick</h1>
</ng-template>

把指令改成 [ngIf] 就可以了。

另外,我们不需要在 AppComponent imports NgIf 指令哦,因为它已经包含在 CommomModule 里了,至于什么是 Module? 这个在之后的 NgMoudle 章节会教。

Angular 17+ 高级教程 – Component 组件 の Structural Directive (结构型指令) & Syntax Reference (微语法)

ngIfElse

<button (click)="show = !show">Toggle</button>

<ng-template #elseTemplate>
  <h1>else template</h1>
</ng-template>

<ng-template [ngIf]="show" [ngIfElse]="elseTemplate">
  <h1>Hi, Derrick</h1>
</ng-template>

除了 true 的模板,我们还可以提供一个 else 的模板,在 false 的情况下它就会显示 elseTemplate。

效果

Angular 17+ 高级教程 – Component 组件 の Structural Directive (结构型指令) & Syntax Reference (微语法)

NgIfContext

ngIf 除了可以传入 true / false 以外,还可以传入一个 Context 对象,作为判断条件。

<ng-template [ngIf]="{ name: 'Derrick' }" [ngIfElse]="elseTemplate" let-person>
  <h1>Hi, {{ person.name }}</h1>
</ng-template>

当 ngIf 是 false、0、empty string、null 或 undefined 时,它会进入 elseTemplate,

其它情况会作为 ng-template 的 context.$implicit。

再一个例子

<ng-template #loadingTemplate>
  <p>loading...</p>
</ng-template>

<ng-template [ngIf]="person" [ngIfElse]="loadingTemplate" let-person>
  <h1>Hi, {{ person.name }}</h1>
</ng-template>

一开始 person 是 null,所以会显示 loading,当 person 有值后就显示 say hi template。

export class AppComponent {
  person!: { name: string };
  constructor() {
    setTimeout(() => {
      this.person = {
        name: 'Derrick',
      };
    }, 3000);
  }
}

效果

Angular 17+ 高级教程 – Component 组件 の Structural Directive (结构型指令) & Syntax Reference (微语法)

ngTemplateContextGuard

ngIf 指令设置了 static ngTemplateContextGuard 属性,所以 context 是有类型的,ngTemplateContextGuard 我们在 ng-template 文章中里有学过。

Angular 17+ 高级教程 – Component 组件 の Structural Directive (结构型指令) & Syntax Reference (微语法)

NgIf 的源码在 ng_if.ts

 

Repeating Element の NgForOf 指令

App Template

<div class="card-list">
  <div class="card">
    <p>index: 0</p>
    <p>name: Derrick</p>
    <p>age: 10</p>
  </div>

  <div class="card">
    <p>index: 1</p>
    <p>name: Alex</p>
    <p>age: 15</p>
  </div>

  <div class="card">
    <p>index: 2</p>
    <p>name: Richard</p>
    <p>age: 18</p>
  </div>
</div>

上面有 3 张卡片,element 的结构是相同的,只是数据不同,这种情况就可以使用 NgForOf 指令封装模板。

AppComponent

interface Person {
  name: string;
  age: number;
}

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule],
  templateUrl: './app.component.html',
  styleUrl: './app.component.scss',
})
export class AppComponent {
  people: Person[] = [
    { name: 'Derrick', age: 11 },
    { name: 'Alex', age: 18 },
    { name: 'Richard', age: 15 },
  ];
}

把数据抽出来,接着写模板

<div class="card-list">
  <ng-template let-person let-index="index">
    <div class="card">
      <p>index: {{ index }}</p>
      <p>name: {{ person.name }}</p>
      <p>age: {{ person.age }}</p>
    </div>
  </ng-template>
</div>

接着添加 NgForOf 指令

<ng-template ngFor [ngForOf]="people" let-person let-index="index">

NgForOf 指令需要 2 个 attributes 哦,一个 ngFor 和一个 ngForOf。

效果

Angular 17+ 高级教程 – Component 组件 の Structural Directive (结构型指令) & Syntax Reference (微语法)

NgForOf 指令除了 index 和 data,它还包含了许多常用的 variables,比如: count (总数)、first (是不是第一条, boolean)、last、odd (是不是单数)、even。

TrackByFunction

trackByFunction 是用来帮助 NgForOf 指令内部做优化的,我们需要提供一个识别对象的方式。

export class AppComponent {
  people: Person[] = [
    { name: 'Derrick', age: 11 },
    { name: 'Alex', age: 18 },
    { name: 'Richard', age: 15 },
  ];

  trackByName: TrackByFunction<Person> = (_index, person) => {
    return person.name;
  };
}
<ng-template ngFor [ngForOf]="people" [ngForTrackBy]="trackByName" let-person let-index="index">

NgForOf 指令会以 person.name 作为识别 person 对象的 key,在 people 发生变成 (比如排序) 时,以 ViewContainerRef.move 取代 ViewContainerRef.createEmbededView 来进行需改,这样性能就会比较好。

 

NgSwitch 指令

NgSwitch 也是 Angular built-in 指令,顾名思义。

<button (click)="status = 'Pending'">status to completed</button>
<button (click)="status = 'Completed'">status to pending</button>

<ng-container [ngSwitch]="status">

  <ng-template [ngSwitchCase]="'Completed'">
    <p>complete</p>
  </ng-template>

  <ng-template [ngSwitchCase]="'Pending'">
    <p>pending</p>
  </ng-template>
  
  <ng-template ngSwitchDefault>
    <p>none</p>
  </ng-template>

</ng-container>

当 status 变化,它会选择对应的模板作为展现。

有 2 点要注意:

  1. [ngSwtich] 可以用于任何 element,不一定要是 <ng-container />

    另外,ViewElementRef 并不是 <ng-container /> 哦,其实是 [ngSwitchCase] 和 ngSwtichDefault 的 ng-template。

  2. ngSwtich 指令没有 fallthrough 的概念,match 到后会自动 break。

 

指令微语法(Syntax Reference)

微语法是 Angular 为了让 Structure Directive 写起来比较好看而发明的。它的唯一用途就是比较好看而已。

我们来看一个 NgIf 指令的例子

AppComponent

interface Person {
  name: string;
}

export class AppComponent {
  person$ = new Observable<Person>((subscriber) => {
    setTimeout(() => {
      subscriber.next({ name: 'Derrick' });
      subscriber.complete();
    }, 2000);
  });
}

App Template

<ng-template #loadingTemplate>
  <p>loading...</p>
</ng-template>

<ng-template [ngIf]="person$ | async" let-person [ngIfElse]="loadingTemplate">
  <p>Hi, {{ person.name }}</p>  
</ng-template>

person$ 是一个 RxJS 的 stream,我们用 AsyncPipe 去 subscribe 它,在它还没有 next 之前它是 null,所以会显示 loading template。 

下面是用微语法的表达方式

<!-- 普通的 Structure Directive -->
<ng-template [ngIf]="person$ | async" let-person [ngIfElse]="loadingTemplate">
  <p>Hi, {{ person.name }}</p>  
</ng-template>

<!-- 微语法 -->
<p *ngIf="person$ | async, let person, else loadingTemplate">Hi, {{ person.name }}</p>

语法解析是这样的:

Angular 17+ 高级教程 – Component 组件 の Structural Directive (结构型指令) & Syntax Reference (微语法)

  • * 星号会在 p 的外层 wrap 一个 <ng-template>

  • ngIf 就是指令 [ngIf],只是省略了方括弧

  • person$ | async 作为传递给 NgIf 指令的参数

  • let person 就是 let-person

  • , 逗号是分割符,用分号 ; 也是可以,不放也是可以

  • else 会变成 [ngIfElse],它以 ngIf 作为 prefix 把 else 拼接上去

  • loadingTemplate 是 [ngIfElse] 的参数

下面这些写法也是正确的

<p *ngIf="person$ | async let person else loadingTemplate">Hi, {{ person.name }}</p>
<p *ngIf="person$ | async else loadingTemplate let person">Hi, {{ person.name }}</p>

除了开头一定要是传递给指令的参数 expression 或者一个 let,后面的语法顺序就不太重要了。

再看一个 NgForOf 指令的微语法

<ng-template #loadingTemplate>
  <p>loading...</p>
</ng-template>

<ng-template [ngIf]="people$ | async" let-people [ngIfElse]="loadingTemplate">
  <!-- 普通的 Structure Directive -->
  <ng-template ngFor [ngForOf]="people" let-person let-index="index" let-isFirst="first" >
    <p>index: {{ index }}</p>
    <p>name: {{ person.name }}</p>
    <p>is first: {{ isFirst }}</p>
  </ng-template>

</ng-template>

外部一层 NgIf 指令负责 loading,有资料了才 for loop。

微语法长这样

<ng-container *ngFor="let person of people, let index = index, let isFirst = first">
  <p>index: {{ index }}</p>
  <p>name: {{ person.name }}</p>
  <p>is first: {{ isFirst }}</p>
</ng-container>

注意,ng-template 换成了 <ng-container>,因为 * 星号解析后会自动 wrap 一层 ng-template 在外面,wrap 了后里面不可以再是一个 ng-template,

所以只好借助 <ng-container>。相等于下面这个写法

<ng-template ngFor [ngForOf]="people" let-person let-index="index" let-isFirst="first">
  <ng-container>
    <p>index: {{ index }}</p>
    <p>name: {{ person.name }}</p>
    <p>is first: {{ isFirst }}</p>
  </ng-container>
</ng-template>

虽然 <ng-container> 没有任何作用,但也没有任何副作用。

另外,切记开头第一句一定要是 let 或者是给指令的 expression 哦,像下面这样是错误的语法。

<ng-container *ngFor="of people, let person, let index = index, let isFirst = first">

下面这些则都可以

<ng-container *ngFor="let person let index = index let isFirst = first of people">

<ng-container *ngFor="let index = index let person let isFirst = first of people">

as Syntax

这句

<ng-container *ngFor="let person of people, let index = index, let isFirst = first">

和这句是等价的

<ng-container *ngFor="let person of people, index as index, first as isFirst">

first as isFirst 等价于 let isFirst = first

as syntax 除了可以当 let 用,还有一个特别的玩法

<ng-container *ngFor="let value of [1, 2, 3] as numbers">
  <pre>{{ numbers | json }}</pre>
</ng-container>

numbers 指向 array [1, 2, 3],什么原理?

of ... as 是一套的,它会变成 let-numbers="ngForOf"

Angular 17+ 高级教程 – Component 组件 の Structural Directive (结构型指令) & Syntax Reference (微语法)

NgForOfContext 特别设置了这么一个属性。

再一个例子

*ngIf="person$ | async as person"

ngIf...as person,它会变成 let-person="ngIf"

Angular 17+ 高级教程 – Component 组件 の Structural Directive (结构型指令) & Syntax Reference (微语法)

下面这两句的结果是一样的

<p *ngIf="person$ | async, let person">Hi, {{ person.name }}</p>
<p *ngIf="person$ | async as person">Hi, {{ person.name }}</p>

但原理其实不一样,let-person 指的是 context.$implicit,而 as person 指的是 context.ngIf,只是 NgIf 指令的 context.$implicit 刚巧等于 context.ngIf 而已。

一个节点只能有一个微语法

 <!-- 正确 -->
<ng-template 
  *ngIf="people$ | async as people else loadingTemplate" 
  ngFor [ngForOf]="people" let-person let-index="index" let-isFirst="first">
  <p>index: {{ index }}</p>
  <p>name: {{ person.name }}</p>
  <p>is first: {{ isFirst }}</p>
</ng-template>

<!-- 错误 -->
<ng-template 
  *ngIf="people$ | async as people else loadingTemplate" 
  *ngFor="let person of people; let index = index, let isFirst=first">
  <p>index: {{ index }}</p>
  <p>name: {{ person.name }}</p>
  <p>is first: {{ isFirst }}</p>
</ng-template>

总结:微语法只是一种简化的写法而已,其原理依旧是 Structure Directive ng-template + 指令 + [Input] + let。

 

总结

Structure Directive 其实是一个灵活的方案,而且也不难理解。

只是大部分人入门时都是直接学微语法和 built-in 的 Structure DIrective,没有学习 ng-template,

所以往往不懂原理,无法灵活运用。

Angular 为此也推出了 Control Flow 来替代 NgIf、NgForOf、NgSwtich 指令。这种高度的封装虽然牺牲了灵活性,但也降低了新手的学习和试错成本。

下一篇就让我们一起来学习 Control Flow 吧。

 

目录

上一篇 Angular 17+ 高级教程 – Component 组件 の ng-template

下一篇 Angular 17+ 高级教程 – Component 组件 の Control Flow

想查看目录,请移步 Angular 17+ 高级教程 – 目录

 

到了这里,关于Angular 17+ 高级教程 – Component 组件 の Structural Directive (结构型指令) & Syntax Reference (微语法)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

    之前在 Component 组件 の Angular Component vs Custom Elements 文章中,我们有学习过几个基础的 Lifecycle Hooks。 比如 OnChanges、OnInit、AfterViewInit、OnDestroy,但那篇只是微微带过而已。 这篇让我们来深入理解 Angular 的 Lifecycle Hooks。   在 Component 组件 の Dependency Injection NodeInjector 文章中

    2024年03月09日
    浏览(41)
  • Angular 17+ 高级教程 – Library

    当你需要管理超过一个项目时,你就需要知道怎么使用 Angular Library。 你可以把多个项目共享的组件放到这个 Library 了,就像 Angular Material 那样。   Sandro Roth – Building an Angular Library with multiple entry points (主要参考) Docs – Creating libraries Docs – Copy assets Docs – Embed assets in C

    2024年04月17日
    浏览(27)
  • Angular 17+ 高级教程 – Signals

    在上一篇 Change Detection 中, 我们有提到 MVVM 监听 ViewModel 变化的难题. 当年 AngularJS 和 Knockout.js (下面简称 KO) 各自选了不同的道路. 但如今, 事过境迁, Angular 最终也走向了 KO 的道路. 这就是这篇的主角 Signal。   在 JavaScript, 值类型 variable 无法被监听, Signal 的做法是把它们都变

    2024年03月09日
    浏览(57)
  • Angular 17+ 高级教程 – HttpClient

    HttpClient 是 Angular 对 XMLHttpRequest 和 Fetch 的封装。 HttpClient 的 DX (Developer Experience) 比 XMLHttpRequest 和 Fetch 都好,只是学习成本比较高,因为它融入了 RxJS 概念。 要深入理解 HttpClient 最好先掌握 3 个基础技能: XMLHttpRequest -- 看这篇 Fetch -- 看这篇 RxJS -- 看这系列 (如果只是为了

    2024年03月16日
    浏览(41)
  • Angular 17+ 高级教程 – NgModule

    NgModule 在 Angular v14 以前是一门必修课。然而,自 Angular v14 推出 Standalone Component 以后,它的地位变得越来越边缘化了。 本教程从开篇到本篇,所有例子使用的都是 Standalone Component,一点 NgModule 的影子也没有😔。 但是!NgModule 还是有价值的,而且在越复杂的项目中你越可以

    2024年03月12日
    浏览(52)
  • Angular 17+ 高级教程 – 盘点 Angular v14 到 v17 的重大改变

    我在 初识 Angular 文章里有提到 Angular 目前的断层问题。 大部分的 Angular 用户都停留在 v9.0 版本。 v9.0 是一个里程碑版本,Angular 从 v4.0 稳定版推出后,好几年都没有什么动静,直到 v9.0 推出了 Ivy rendering engine。 本以为 v9.0 以后 Angular 会大爆发,结果迎来的是 Angular 团队搞内

    2024年04月22日
    浏览(24)
  • Angular 17+ 高级教程 – Change Detection

    虽然 Angular 正在把大部分 Change Detection 概念换成 Signal,但是最快也要 1 年后,所以还是有必要认真学习一下的。   MVVM 框架的开发方式是这样的: 写 HTML 写 ViewModel 在 HTML 里加入 binding syntax。 在 HTML 里加入 listening syntax,在事件发生时修改 ViewModel。 MVVM 的宗旨是 \\\"不要直接

    2024年03月09日
    浏览(30)
  • Angular 17+ 高级教程 – 学以致用

    读这么多原理,到底为了什么?真实项目中真的会用得到吗? 你正在疑惑 \\\"知识的力量\\\" 吗? 本篇会给一个非常非常好的案例,让你感悟 -- 知识如何用于实战。 记住,我的目的是让你感悟,而不是要你盲目相信知识。   下面是我在 2020-11-06 记入的一个问题。 一模一样的问题

    2024年04月22日
    浏览(25)
  • Angular 17+ 高级教程 – Angular 的局限 の Query Elements

    熟悉 Angular 的朋友都知道,Angular 有非常多的局限,许多事情它都做不好,打开 Github 一堆 2016 - 2017 的 Issues,时至今日都没有解决。 原因也很简单 -- Angular 团队的不作为😔。 通常我会把常见的 Angular 的局限记入在这篇 Angular 的局限和 Github Issues,但由于本篇要讲的问题篇幅

    2024年04月24日
    浏览(28)
  • Angular 17+ 高级教程 – Dependency Injection 依赖注入

    本来是想先介绍 Angular Component 的,但 Component 里面会涉及到一些 Dependency Injection (简称 DI) 的概念,所以还是先介绍 DI 吧。 温馨提醒:如果你对 JS class、prototype 不太熟悉的话,建议你先看这篇 JavaScript – 理解 Object, Class, This, Prototype, Function, Mixins   首先我们有一个 class Ser

    2024年03月09日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包