iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 5
0

[S06E04] Directive

https://www.youtube.com/watch?v=-8mGWhY4jGo&list=PL9LUW6O9WZqgUMHwDsKQf3prtqVvjGZ6S&index=9

S06 由 Kevin Yang 大大主講,藉由大大導讀文件後,我們就有能力能自己查文件了

看完基礎但內容有點多的 Component 之後,來學比較輕鬆的 Directive
[S06E04] Directive 片長只有 35 分鐘,重點在於跟著 Kevin Yang 老師學會怎麼看文件

首先,先開好片頭導讀的文章,再跟著影片一起看,以後才有能力自己查文件

  1. 開啟 https://angular.io
  2. FUNDAMENTALS / Components & Templates / Attribute Directives
    https://angular.io/guide/attribute-directives

Directives overview - 在 Angular 裡面有 3 種 directives

  • Components — 有 template 的 directives
  • (結構性)Structural directives — 透過新增/刪除DOM elements 來改變 DOM layout
    例如:*ngIf、*ngFor、*ngSwitch
  • Attribute directives — 改變 element、component、其他directive 的「外觀」或「行為」
    很常用,可自定義,既有的有:NgClass、NgStyle、([ngModel])
    可把功能封裝成directives,再使用率變高

今天內容有:(方便search)

  • attribute directive
  • @HostListener decorator - 監聽事件
  • exportAs
  • Structural Directives
  • [ngForTrackBy]="trackById">
  • 自定義Structural directive
  • 操作DOM的API - Renderer2 (class)

attribute directive

跟著文件的範例操作一次

<p appHighlight>p tag裡面的文字</p> 掛在<TAG>上面

ng g directive highlight

import { Directive, ElementRef, AfterViewInit } from '@angular/core';

@Directive({
  selector: '[appHighlight]' // selector 用 attribute 的方式
  // selector: 'appHighlight' // 也可以用<TAG>的方式
})
export class HighlightDirective implements AfterViewInit {
  constructor(el: ElementRef) { // 注入 ElementRef
      // el 就是那個 <p appHighlight>Highlight me!</p>
      el.nativeElement.style.backgroundColor = 'yellow';
      // 由於傳入的 ElementRef 不一定是 HTML的Element,使用nativeElement來取可避免錯誤
      
      console.log(el.nativeElement);
      // HTMLParagraphElement {tagName:"p",attributes:{...},innerHTML:"p tag裡面的文字",...}
  }
  // 這裡值才初使化好
  ngAfterViewInit(){
      console.log(  (el.nativeElement as HTMLParagraphElement).innerText  );
      // p tag裡面的文字
  }
}

@HostListener decorator - 監聽事件

@Input

使用 @HostListener decorator 來訂閱 DOM element的事件
輕鬆的幫 <p appHighlight>p tag裡面的文字</p> 增加監聽mouse事件,做到更多互動

import { Directive, ElementRef, HostListener, Input } from '@angular/core';

@Directive({
  selector: '[appHighlight]'
})
export class HighlightDirective {
  constructor(private el: ElementRef) { }
  // 監聽mouse移進來
  @HostListener('mouseenter') onMouseEnter() {
    this.highlight(this.highlightColor || 'red');
  }
  // 監聽mouse移出<p appHighlight></p>
  @HostListener('mouseleave') onMouseLeave() {
    this.highlight(null);
  }
  // <p appHighlight>Highlight me!</p>
  // <p [appHighlight]="color">Highlight me!</p> // 父元件傳color進去
  @Input('appHighlight') highlightColor: string;

  private highlight(color: string) {
    this.el.nativeElement.style.backgroundColor = color;
  }
}

exportAs

directive可以用exportAs

  1. highlight.directive.ts
@Directive({
    selector: '[appHighlightS]',
    exportAs: 'appHightlightE' // 把Directive export出來
  1. app.component.html
用reference template #f 去接 export 出來的 Directive appHightlightE
<p appHighlightS #f="appHightlightE">Highlight me!<p>
button 可以去觸發<p appHighlight>裡面的directive裡面的function()
<button (click)="f.showMessage()">show message</button>

其他可以用exportAs的decorator

  • ngModel
<input name="firstName" ngModel #firstName="ngModel" />
<button [disabled]="firstName.invalid">

Structural Directives

https://angular.io/guide/structural-directives

*ngFor

selector 是 [ngFor] 或 [ngForOf]
開啟文件

  1. 開啟 https://angular.io
  2. 右上角Search,查ngForOf,開紅色Directive的NgForOf
    https://angular.io/api/common/NgForOf
    可看詳細NgFor的用法
<ul>
    <li *ngFor="let item of [1,2,3]; let i=index"> {{i}} - {{ item }} </li>
                                           0,1,2 index就是exported出來的,可當區域變數
</ul>

export出來的值可當區域變數

  • $implicit: T
  • ngForOf: NgIterable<T>
  • index: number
  • first: boolean 第一項
  • last: boolean 最後一項
  • even: boolean 是不是偶數項
  • odd: boolean 是不是奇數項
<li *ngFor="let user of userObservable | async as users; index as i; first as isFirst">
  {{i}}/{{users.length}}. {{user}} <span *ngIf="isFirst">default</span>
</li>

[ngForTrackBy]="trackById">

https://angular.io/api/common/NgForOf#ngForTrackBy
用途:當iterable在異動項目時,最小化DOM的變更,只重新改變「異動的項目」
說明:預設change detector假設object instance來identify(識別)iterable裡的node(項目)
若提供TrackByFunction,Directive用function的return的結果來識別 item node,
而不是識別 object本身
文件裡只有TrackByFunction<T>,只好用範例裡的trackBy: trackById
https://stackblitz.com/angular/oaypkvmqklj

@Input() ngForTrackBy: TrackByFunction<T> // 後面接要track的function()

  1. app.component.html
<li *ngFor="let item of items; index as i; trackBy: trackById">...</li>
                                            簡寫
或用<ng-template>去包<li>

<ng-template ngFor let-item [ngForOf]="items" let-i="index" [ngForTrackBy]="trackById">
  <li>...</li>
</ng-template>
  1. app.component.ts
export class Item {
    id: number; // key
    name: string;
}
export const items: Item[] = [
    { id:1, name:'alice'},
    { id:2, name:'bob'}
];
export class AppComponent {
  items = items;
  item = this.items[0];
  // trace iterable中項目異動時的function
  trackById(index: number, item: Item): number { return item.id; }
  // 文件是寫傳入 iteration index, node object ID 不解??
}

*ngSwitch

基礎的Kevin老師用官網的範例帶過

<div [ngSwitch]="hero?.emotion">
  <app-happy-hero    *ngSwitchCase="'happy'"    [hero]="hero"></app-happy-hero>
  <app-sad-hero      *ngSwitchCase="'sad'"      [hero]="hero"></app-sad-hero>
  <app-confused-hero *ngSwitchCase="'confused'" [hero]="hero"></app-confused-hero>
  <app-unknown-hero  *ngSwitchDefault           [hero]="hero"></app-unknown-hero>
</div>

[Angular 大師之路] Day 11 - *ngIf 有 else 可以用嗎?

再來看到Mike大大的大作「[Angular 大師之路] Day 11 - *ngIf 有 else 可以用嗎?」
https://ithelp.ithome.com.tw/articles/10205494

  • else時顯示<ng-template>
<div *ngIf="display;then tmpRefId4then else tmpRefId4else">Hello World</div>
<button (click)="display = !display">display switch</button>
<ng-template #tmpRefId4then> 設定 template reference id
    when then case to show
</ng-template>
<ng-template #tmpRefId4else> 設定 template reference id
    when else case to show
</ng-template>

*做了哪些事?

參考文件
https://angular.io/guide/structural-directives#prefer-the-asterisk--syntax

自定義Structural directive

如何自定義 類似 let item of items 的 directive?
Write a structural directive
https://angular.io/guide/structural-directives#unless

程式在app.component.html的最下面,自行研究囉,我也看不懂,不敢亂解釋
https://stackblitz.com/angular/oaypkvmqklj

  1. unless.directive.ts
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
@Directive({ selector: '[appUnless]'})
export class UnlessDirective {
  private hasView = false;

  constructor(
    // 看來關鍵在於 注入的 TemplateRef、ViewContainerRef 的操作
    // TemplateRef、ViewContainerRef 屬於 API,可詳讀
    // https://angular.io/api/core/ElementRef
    // https://angular.io/api/core/ViewContainerRef
    private templateRef: TemplateRef<any>,
    private viewContainer: ViewContainerRef) { }

  @Input() set appUnless(condition: boolean) {
    if (!condition && !this.hasView) {
      this.viewContainer.createEmbeddedView(this.templateRef);
      this.hasView = true;
    } else if (condition && this.hasView) {
      this.viewContainer.clear();
      this.hasView = false;
    }
  }
}
  1. app.component.html
<h4>UnlessDirective with template</h4>
condition是boolean;
<p *appUnless="condition">Show this sentence unless the condition is true.</p>
<p *appUnless="condition" class="code unless">
  (A) <p *appUnless="condition" class="code unless">
</p>

<ng-template [appUnless]="condition">
  <p class="code unless">
    (A) <ng-template [appUnless]="condition">
  </p>
</ng-template>

操作DOM的API - Renderer2 (class)

找renderder2,選API,C的(class)
用Renderer2會再多包一層,相容性應該會比較好,減少跨平台的錯誤訊息
https://angular.io/api/core/Renderer2

不要用Document.createElement()
https://developer.mozilla.org/zh-TW/docs/Web/API/Document/createElement

如果有用listen,記得取消掉,否則會一直存在

import {Directive,Renderer2, Input, ElementRef, OnChange, AfterViewInit' from '@angular/core';
const centerWays = {
    0: {
        parent: {},
        own: {
            'position: 'absolute',
            'width': '400px',
            'top': '50%',
            'left': '50%',
            'margin-left': '-200px',
            'margin-top': '35
        }
    }
}
@Directive({
    selector: '[amosCenter]'
})
export class CenterDirective implements AfterViewInit, OnChanges{
    @Input('amosCenter') way = 0;
    dom = this.ele.nativeElement as HTMLDivElement;
    parentStyle;
    constructor(
        private ele: ElementRef, // 重點是API有哪些Properties、Methods可用
        Private render: Renderer2
    ){}
    // @Input值有變的時候觸發
    ngOnChanges() {
        this.parentStyle = centerWays[this.way || 0].parent;
    }
    // ViewInit後觸發
    ngAfterViewInit(){
        this.setParentStyle();
    }
    private setParentStyle() {
        Object.entries(this.parentStyle).forEach(item => {
            this.render.setStyle(this.dom.parentElement, item[0], item[1]);
        });
    }
}

上一篇
Day04_Component_Part2
下一篇
Day06_NgModule & DI
系列文
Angular新手村學習筆記(2019)33
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言