iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 4
1
Modern Web

Angular新手村學習筆記(2019)系列 第 4

Day04_Component_Part2

  • 分享至 

  • xImage
  •  

[S06E03] Component Part 2

https://www.youtube.com/watch?v=0l1aodCxLzE&list=PL9LUW6O9WZqgUMHwDsKQf3prtqVvjGZ6S&index=10

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

Component超基礎、超重要的,再來看Part 2
主要是生命週期的先後順序、觸發時機、可配合使用的東西
今天內容有:

  • Component的生命週期
  • view與content的範圍
  • 在component裡撈到element的方法 - ViewChild、ViewChildren、ContentChild、ContentChildren
    我竟然看懂ContentChild、ContentChildren了!
  • ng-container跟ng-template的差異
  • encapsulation 的4種模式 ViewEncapsulation.Emulated | None | Native | ShadowDom

生命週期

import { Component, 
    OnChanges, 
    OnInit,
    DoCheck,               // 當有event發生時,都會觸發check
    AfterContentInit,
    AfterContentChecked,   // 當有event發生時,都會觸發check
    AfterViewInit,
    AfterViewChecked,      // 當有event發生時,都會觸發check
    OnDestroy,
    SimpleChanges
} from '@angular/core';

@Component({...})
export class AppComponent implements OnChanges,
    OnInit,DoCheck,AfterContentInit,AfterContentChecked,AfterViewInit,
    AfterViewChecked,OnDestroy {
    name = 'Angular';
    data = { name : ''}; // 建立給初使值的習慣,避免undefine
    constructor(){}
    
    // trigger on Input changes
    // 跟 ChangeDetectionStragety 有關(Default | OnPush)
    // 要有@Input
    ngOnChanges(changes: SimpleChanges){

    }
    
    // 大部分初使化建議使用ngOnInit,不要在constructor(){}初使化
    ngOnInit(){} // class建立時會觸發1次
    
    // repeat on erery Change Detection
    // 判斷Angular所無法判斷的資料變更
    ngDoCheck(){}
    ngAfterContentInit{}
    // repeat on erery Change Detection
    ngAfterContentChecked{}
    ngAfterViewInit(){}
    // repeat on erery Change Detection
    ngAfterViewChecked(){
        // 如果Checked又去改資料,會報Error: ExpressionChangedAfterItHasBeenCheckedError
        this.data = { name: 'yoo' };
    }
    
    // 當Element被移除時會被觸發
    // 例如: *ngIf="true" 再變成 "false" 時,<demo></demo>會被移除
    // <demo *ngIf="show"></demo>
    // <button (click="show = !show">切換show的boolean值</button>
    ngOnDestroy(){
        // 假設這個Component有訂閱Subscription
        this.subscription.unsubcribe(); // 取消訂閱,避免memory leak
    }
}
  • 試範ngOnChanges
    要有@Input才會有ngOnChanges
    父元件 塞值給子元件 -> 子元件@Input有值 -> 觸發子元件的ngOnchanges
    ngOnChanges(changes: SimpleChanges){}
  1. demo.component.ts
import { Component, OnChanges, Input, SimpleChanges
    ChangeDetectionStrategy,
    ChangeDetectorRef
} from '@angular/core';

@Component({ 
    selector: 'demo',
    ...,
    changeDetection: ChangeDetectionStrategy.OnPush
    // 當是OnPush,且@Input是Object的時候,
    // 則只有改變新Object才會觸發ngOnChanges()
    // 只改變Object裡面的值不會觸發ngOnChanges()
})
export class DemoComponent {
    @Input() data;
    constructor(
        private cd: ChangeDetectorRef // 當有cd發生時觸發
        // ChangeDetectionStrategy.Default @Input值有變就觸發
        // by value: 文字、數字
        // by reference: Object // OnPush時,同Object視為沒異動 
    ){}
    ngOnChanges(changes: SimpleChanges){ 
        // currentValue // 目前的值
        // firstChange: true // 只有第1次改變是true,其餘都false
        // previousValue // 之前的值
    }
  1. app.component.html
<demo [data]="dataOutter"></demo>
<input [(ngModel)]="dataOutter />
當值一改變, @Input的值會變,ngOnChanges()觸發


<demo [data]="dataObject"></demo>
{{ dataObject | json }}

當demo的CD策略設為OnPush,且@Input接收的是Object時
changeDetection: ChangeDetectionStrategy.OnPush
只改變data Object裡的name的值 不會觸發demo的ngOnChanges()
只有變為新的Object才會觸發demo的ngOnChanges()
<input [(ngModel)]="dataObject.name" /> 
<button (click)="assignData()">assign</button>
  1. app.component.ts
export class AppComponent {
    dataOutter = 'not object';
    dataObject = { name : '' };
    constructor(){}
    assingData(){
        this.dataObject = { ...this.dataObject }; // 設為新的Object
    }

view 與 content 的範圍

content會先處理,再處理view
所以ngAfterContentInit()會在ngAfterViewInit()之前

  1. app.component.html
<!-- view -->
<demo [data]="data">
    <!-- content 又稱 projection(投射)-->
    <h1>這裡是content</h1>
</demo>
<!-- 下面3行都屬於view -->
{{ data | json }}
<input...>
<button...>
  1. demo.component.html
<ng-content></ng-content> 
如果沒這行,則app.component.html的<demo>content的部分不會顯示出來</demo>

在component裡撈到element的方法 - ViewChild、ContentChild

  1. app.component.ts
import { Component,
    ViewChild,
    ViewChildren, // Children是複數
    ElementRef,
    QueryList // ViewChildren取多個instance用
    } from '@angular/core';
import { DemoComponent } from './demo/demo.component';
@Component({...})
export class AppComponent {

    // 用法一: @ViewChild('文字') ...
    // 如果element是<input #input ...>
    @ViewChild('input') _input: ElementRef;
    
    // 用法二: @ViewChild(直接塞型別) ...
    // 也能取component,會取到component的instance
    @ViewChild(DemoComponent) demoComponent: DemoComponent;
    // 取多個DemoComponent,要用QueryList型別來接多個instance 的清單
    @ViewChildren(DemoComponent) demoComponents: QueryList<DemoComponent>;
    
    // 要到ngAfterViewInit(){}才能取到_input
    ngAfterViewInit(){
        console.log(this._input);
        this._input.nativeElement.value = '1';
        // this.demoComponent.data
        
        console.log(this.demoComponents);
        // QueryList._results: Array(2)
        // 0: DemoComponent {cd: ViewRef_, data: {...}}
        // 1: DemoComponent {cd: ViewRef_, data: "1"}
        // length: 2
    }
}
  1. app.component.html
會被 @ViewChild('input') _input: ElementRef; 取到
<input #input [(ngModel)]="data.name" />

@ViewChild(DemoComponent) demoComponent: DemoComponent; 取到
只會取到第1個DemoComponent instance
<demo [data]="data"></demo>

<demo [data]="'1'"></demo>

@ContentChild

較@ViewChild不直覺,用範例來體會
目標:要從子元件的ts裡,抓到父元件html裡的子元件 裡的content
要從demo.component.ts裡抓到 app.component.html裡的<demo> <!--content--> </demo>

  1. demo.component.ts
import { Component, AfterContentInit
} from '@angular/core';

@Component({ 
    selector: 'demo',...
})
export class DemoComponent {
    @Input() data;
    @ContentChild('.someClass') content; // 用className取不到
    @ContentChild('cardFooter') content; // 用templateRef才取得到
    constructor(){}
    ngAfterContentInit(){
        console.log(this.content);
        // ElementRef
        // nativeElement: HTMLDivElement
    }
  1. app.component.html
<demo [data]="data">
    <!-- content 又稱 projection (用ViewChild是抓不到的)--> 
    <div class="someClass" #carFooter>
        <h1>有someClass的</h1>
    </div>
    <div>
        <h1>沒有someClass的</h1>
    </div>
    
    <ng-container ngProjectAs="some"> 會把內容轉為some的select
        <h1>如果content內容很多,可以用ng-container包起來</h1>
    </ng-container>
    
    ngProjectAs="some"等同於「some的element」
    <some></some>
    
</demo>
  1. demo.component.html
<ng-content select=".someClass"></ng-content> 
如果沒這行,則app.component.html的<demo>content的部分不會顯示出來</demo>
只顯示class="someClass"的content (<div class="someClass" ...)

<ng-content select="some"></ng-content> 
顯示app.component.html裡的<demo>裡的<ng-container ngProjectAs="some"> </demo>

<ng-content></ng-content> 
沒有someClass的會被放在下面

ng-container跟ng-template的差異

  • ng-container
    不會產生額外的TAG,不會影響CSS切板,比用<div>去包好
<ng-container *ngFor="let item of [1,2,3]">
    text in container
</ng-container>
  • ng-template
    使用時機:搭配ng-container使用,可以當*ngIf="false"的時候,就跑else要顯示的樣板
<ng-container *ngIf="show; else #b">
    text in container
</ng-container>
<hr>
<ng-template #b>
    text in template
</ng-template>
<button (click)="show = !show">showSwitch</button>

Component的style

encapsulation 的4種模式

  • encapsulation: ViewEncapsulation.Emulated
  1. app.component.ts
@Component({
    selector: 'my-app',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css'],
    encapsulation: ViewEncapsulation.Emulated
  1. app.component.css
p { font-family: Lato; }

跑ng serve觀察結果

p會加個selector [_ngcontent-c143],用來隔離css適用範圍,只適用該元件
</head>
<style>p[_ngcontent-c143] { font-family: Lato; }</style>
</head>
<body>
    <my-app _nghost-c143 ng-version="7.0.0">...</my-app>
</body
  • encapsulation: ViewEncapsulation.None // 該component寫的css的scope為全域
跑ng serve觀察結果
None就不會再加selector,變會全域
`<style>p { font-family: Lato; }</style>`
  • encapsulation: ViewEncapsulation.Native
  • encapsulation: ViewEncapsulation.ShadowDom
    Native 其實也是 ShadowDom,只是版本不同
跑ng serve觀察結果
<style>...</style>會不見,會被移到該元件的<TAG>的裡面</TAG>
<my-app ng-version="7.0.0">
    #shadow-root (open)
        "loading"
</my-app>

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

尚未有邦友留言

立即登入留言