https://www.youtube.com/watch?v=-8mGWhY4jGo&list=PL9LUW6O9WZqgUMHwDsKQf3prtqVvjGZ6S&index=9
S06 由 Kevin Yang 大大主講,藉由大大導讀文件後,我們就有能力能自己查文件了
看完基礎但內容有點多的 Component 之後,來學比較輕鬆的 Directive
[S06E04] Directive 片長只有 35 分鐘,重點在於跟著 Kevin Yang 老師學會怎麼看文件
首先,先開好片頭導讀的文章,再跟著影片一起看,以後才有能力自己查文件
今天內容有:(方便search)
跟著文件的範例操作一次
<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 來訂閱 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;
  }
}
directive可以用exportAs
@Directive({
    selector: '[appHighlightS]',
    exportAs: 'appHightlightE' // 把Directive export出來
用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>
<input name="firstName" ngModel #firstName="ngModel" />
<button [disabled]="firstName.invalid">
https://angular.io/guide/structural-directives
selector 是 [ngFor] 或 [ngForOf]
開啟文件
<ul>
    <li *ngFor="let item of [1,2,3]; let i=index"> {{i}} - {{ item }} </li>
                                           0,1,2 index就是exported出來的,可當區域變數
</ul>
<T>
<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>
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()
<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>
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 不解??
}
基礎的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>
再來看到Mike大大的大作「[Angular 大師之路] Day 11 - *ngIf 有 else 可以用嗎?」
https://ithelp.ithome.com.tw/articles/10205494
<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
如何自定義 類似 let item of items 的 directive?
Write a structural directive
https://angular.io/guide/structural-directives#unless
程式在app.component.html的最下面,自行研究囉,我也看不懂,不敢亂解釋
https://stackblitz.com/angular/oaypkvmqklj
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;
    }
  }
}
<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>
找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]);
        });
    }
}