[Angular] Day16. Writing structural directives

在上一章中介绍了如何建立客制化的 attribute directive 与使用,而本章将会介绍如何建立 structural directives,那就接着往下看吧!

https://ithelp.ithome.com.tw/upload/images/20210821/20124767xY1I5d66k3.jpg


Creating a structural directive

本章一样会利用 Angular 官方文档的例子建立一个范例,范例内容是会建立一个 UnlessDirective 以及该如何设置他的条件值,这个 UnlessDirective 与 *ngIf 相法,当为 true 时会显示 NgIf 的内容反之则显示 UnlessDirective

  1. 首先利用 Angular CLI 建立一个 directive

    ng generate directive unless
    ng g d unless
    
  2. @angular/core 中引入 Input, TemplateRef, ViewContainerRef

    import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
    
    @Directive({ selector: '[appUnless]'})
    export class UnlessDirective {
    }
    
  3. 在 unless.directive.ts 的 constructor 中 inject TemplateRef 与 ViewContainerRef 成为他的私有变量

    import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
    
    @Directive({ selector: '[appUnless]' })
    export class UnlessDirective {
      constructor(
        private templateRef: TemplateRef<any>,
        private viewContainer: ViewContainerRef
      ) {}
    }
    

    UnlessDirective 会从 Angular 生成的 <ng-template> 中创建一个 embedded view 并将他插入到宿主 element 相邻的地方,而 TemplateRef 可以让你访问到 <ng-template> 的内容,而 ViewContainerRef 可以让你访问 view container。

    Note: embedded view 是为 ng-template 中指定的 view node 所创建的 view,简单来说他类似 component 中的 template 所创建出来的 view,但是他没有 component 的元素或数据,不过他还是属於一个有效的 view,会在检测过程中与其他 view 一样被检测到。

  4. 在 unless.directive.ts 中添加一个 property 用来表示是否显示画面,与使用 setter 添加一个 appUnless property。

    import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
    
    @Directive({ selector: '[appUnless]' })
    export class UnlessDirective {
      private hasView = false;                                    // (1)
      constructor(
        private templateRef: TemplateRef<any>,
        private viewContainer: ViewContainerRef
      ) {}
    
      @Input() set appUnless(condition: boolean) {                // (2)
        if (!condition && !this.hasView) {
          this.viewContainer.createEmbeddedView(this.templateRef);
          this.hasView = true;
        } else {
          this.viewContainer.clear();
          this.hasView = false;
        }
      }
    }
    
    • (1): 添加一个 property 用於表示是否显示画面
    • (2): 用 setter 添加一个 property,当 condition 为 true 时透过 viewContainer 创造一个 embedded view 并将 templateRef 放入,如果为 false 的话则清除
  5. 在 app.component.ts 中新增一个 property 与 method

    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent {
      condition = false;                        // (1)
    
      onClick() {                               // (2)
        this.condition = !this.condition;
      }
    }
    
    • (1): 新增一个 property 用於绑定 structural directive
    • (2): 新增一个 method 用於点击画面中按钮时触发变更 condition 的状态
  6. 在 app.component.html 中使用我们建立的 directive

    <!-- app.component.html -->
    
    <div *appUnless="condition">unless display area</div>
    <div *ngIf="condition">ngIf display area</div>
    
    <button (click)="onClick()">Click</button>
    

img

在画面中可以看到,我们一开始设计的逻辑就是希望要跟 ngIf 相反,所以当 condition = false 时会显示我们刚建立的 directive 满足的区域 unless display area,反之当点击按钮将 condition 变为 true 时,则会显示 ngIf 的内容 ngIf display area


Structural directive shorthand

看完上面的例子後应该会了解该怎麽建立自己的 structural directive,但是可能有人会疑问,连自己建立的 structural directive 也要使用星号 ( * ) 吗?这个星号是什麽?接着就要来讲解一下这个星号是什麽。

structural directive 上面的星号( * )语法是 Angular 将其解释为更常形式的速记,他会将星号转换成一个 <ng-template> 并将它围绕着宿主元素与他的所有子层,举例来说

<div *ngIf="hero" class="name">{{hero.name}}</div>

当 hero 为 true 时才会显示英雄的名字,而其实 Angular 看到上面的程序後会将它转变为

<ng-template [ngIf]="hero">
  <div class="name">{{hero.name}}</div>
</ng-template>

可以看到 Angular 将星号变为一个 <ng-template> ,而 ngIf 变成了他的 property binding,而其他的内容会移动到 <ng-template> 之中,因为 Angular 不会真正的创建 <ng-template> 元素而是指将内部的 <div>comment node placeholder 放到 DOM 中。

https://ithelp.ithome.com.tw/upload/images/20210811/20124767YcWEc4HmyA.png

而如果 ngIf 为 false 的话则会连 <div> 的内容都不会放在 DOM 中。

https://ithelp.ithome.com.tw/upload/images/20210811/20124767mkg1tsQNMi.png

看完了 ngIf 的速记用法後,接着来看看 ngFor 会长什麽样子,一样先举个例子

<div *ngFor="let hero of heroes; let i=index; let odd=odd; [class.odd]="odd">
  ({{i}}) {{hero.name}}
</div>

使用了 ngFor 将 heroes 中的内容迭代的显示在画面上,并使用了之前提到的 ngFor 的一些参数,而这个例子在经过 Angular 转换後会变成

<ng-template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd">
  <div [class.odd]="odd">({{i}}) {{hero.name}}</div>
</ng-template>

可以看到他跟 ngIf 一样会将星号变为一个 <ng-template> 并且将 ngFor 变为 property binding,比较不同的是在这个 <ng-template> 中使用了 let 宣告了一个模板输入变量,让他可以被 template 中被引用,在上面的例子中的输入变量是 hero, i, 'odd',解析器会将 let hero、let i 和 let odd 转换为 let-herolet-ilet-odd ,Angular 会将当前的值在适当的时间点赋予给宣告的输入变量。

Creating template fragments with <ng-template>

Angular 的 <ng-template> 定义了一个默认不渲染任何东西的 template,可以透过 structural directive 来控制是否要显示 <ng-template> 中定义的内容

<p>Hip!</p>
<ng-template>
  <p>Hip!</p>
</ng-template>
<p>Hooray!</p>

https://ithelp.ithome.com.tw/upload/images/20210811/201247672JMq8WtBWV.png

可以看到,当我们使用 <ng-template><p>Hip!</p> 包住,但是没有使用 structural directive 决定他是否要被显示出来,所以在 DOM 中会看到 comment node placeholder 而不是希望呈现的数据。


结论

本章中介绍了如何建立客制化的 structural directive,他相较於建立客制化的 attribute directive 男的多,所以需要了解更多的观念,比如说 Angular 的 view 观念其实非常复杂,这边只有稍微提到而已,如果之後有专案上的需求再回来钻研就好,这边只是大概介绍该如何使用与客制化。

本篇是 directive 的最後一篇,明天将会回头介绍 component 的最後一个例子,动态载入 component,他使用了之前讲到的满多技巧,范例的难度也比较大,那麽就明天来好好的讲解一下吧,那我们就明天见吧。


Reference


<<:  Day4: Network Access Control List(NACL) 简介与布建

>>:  Day16:图形搜寻-深度优先搜寻(Depth-First Search)

Day 21 BeautifulSoup模组三

今天的影片内容为介绍分析项目清单与表格文件的方法 而在影片的後半部,会带大家离开新手村,爬取一个真正...

每个人都该学的30个Python技巧|技巧 21:set的处理方法(字幕、衬乐、练习)

上次教到新的容器叫做集合,那听到这个名称有没有想起来在高中时期的数学也有学过集合呢?那时候教到的交集...

[JS] You Don't Know JavaScript [this & Object Prototypes] - Prototypes [下]

前言 我们在Prototypes [上]中介绍了什麽是Prototype也介绍了JavaScript...

【领域展开 23 式】 Page & Post ,双 P 关系确认

Page & Post 傻傻分不清楚 由於前两天在研究 Menu,发现设定 Menu 的时候...

WordPress网站加速经验-体质最佳化与5款快取外挂测速+功能比较

对於WordPress网站来说,快取外挂对网页载入速度的确是有帮助的。 在已经进行一些基本最佳化的网...