https://www.youtube.com/watch?v=0l1aodCxLzE&list=PL9LUW6O9WZqgUMHwDsKQf3prtqVvjGZ6S&index=10
S06由Kevin Yang大大主講,藉由大大導讀文件後,我們就有能力能自己查文件了
Component超基礎、超重要的,再來看Part 2
主要是生命週期的先後順序、觸發時機、可配合使用的東西
今天內容有:
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
}
}
要有@Input才會有ngOnChanges
父元件 塞值給子元件 -> 子元件@Input有值 -> 觸發子元件的ngOnchanges
ngOnChanges(changes: SimpleChanges){}
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 // 之前的值
}
<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>
export class AppComponent {
dataOutter = 'not object';
dataObject = { name : '' };
constructor(){}
assingData(){
this.dataObject = { ...this.dataObject }; // 設為新的Object
}
content會先處理,再處理view
所以ngAfterContentInit()會在ngAfterViewInit()之前
<!-- view -->
<demo [data]="data">
<!-- content 又稱 projection(投射)-->
<h1>這裡是content</h1>
</demo>
<!-- 下面3行都屬於view -->
{{ data | json }}
<input...>
<button...>
<ng-content></ng-content>
如果沒這行,則app.component.html的<demo>content的部分不會顯示出來</demo>
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
}
}
會被 @ViewChild('input') _input: ElementRef; 取到
<input #input [(ngModel)]="data.name" />
@ViewChild(DemoComponent) demoComponent: DemoComponent; 取到
只會取到第1個DemoComponent instance
<demo [data]="data"></demo>
<demo [data]="'1'"></demo>
較@ViewChild不直覺,用範例來體會
目標:要從子元件的ts裡,抓到父元件html裡的子元件 裡的content
要從demo.component.ts裡抓到 app.component.html裡的<demo> <!--content--> </demo>
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
}
<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>
<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的會被放在下面
<div>
去包好<ng-container *ngFor="let item of [1,2,3]">
text in container
</ng-container>
<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({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
encapsulation: ViewEncapsulation.Emulated
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
跑ng serve觀察結果
None就不會再加selector,變會全域
`<style>p { font-family: Lato; }</style>`
跑ng serve觀察結果
<style>...</style>會不見,會被移到該元件的<TAG>的裡面</TAG>
<my-app ng-version="7.0.0">
#shadow-root (open)
"loading"
</my-app>