這次紀錄一些額外的databinding與component資料傳遞相關的方法。
1. 從parent傳資料到child component
如果要傳遞某個資料到child component的某個attribute,可以透過attribute binding的方式,但要透過@Input() 來指定component特定attribute為可接受外面傳入資料,才可透過該方式傳遞資料。以下列程式舉例來說:
app-item.component.html:
<app-item
*ngFor="let item of items" ></app-item>
app-item.component.ts程式:
export class AppItemComponent implements OnInit {
item: { type: string, name: string, content: string }
constructor() {
}
ngOnInit() {
}
}
AppItemComponent中有一item屬性,我們希望在ngFor的item可以傳入到AppItemComponent中的item屬性,就可以透過上述的方式調整程式:
調整app-item.component.html:
<app-item
*ngFor="let item of items"
[srvItem]="item"></app-item>
*ngFor="let item of items"
[srvItem]="item"></app-item>
調整app-item.component.ts程式:
import { Component, OnInit, Input } from '@angular/core';
export class AppItemComponent implements OnInit {
@Input('srvItem') item: { type: string, name: string, content: string }
}
主要透過Input的方式,將component內item變數宣告為可接受外部傳入資料,或比較像是讓item變成AppItemComponent的attribute,讓使用component的parent可以透過attribute binding的方式將值綁定。使用時要記得將Input import進來。
其中如果是直接使用 @Input() item,在attribute binding時,就要使用[item]="item",即attribute binding的名稱就是變數原始名稱;如果是使用@Input('srvItem') item,則外部看的名稱是srvItem,attribute binding名稱就會是srvItem(如上例程式)。
2. 客製自己的event (可傳送資料)
自己製作的component,可以客製自己的event binding方式,大致方式如下:app-item.Component.html:
<button class="btn btn-primary" (click)="onAddItem(ItemNameInput)">Add Item</button>
app-item.component.ts:
export class CockpitComponent implements OnInit {
@Output('itemAdd') itemAdded= new EventEmitter<{ itemName: string, itemContent: string }>();
constructor() { }
ngOnInit() {
}
onAddItem(itemInput: HTMLInputElement) {
this.itemAdded.emit({ itemName: itemInput.value, itemContent: 'content' });
}
}
Parent template:
<app-item (itemAdd)="onItemAdded($event)"></app-item>
Parent script:
onItemAdded(item: { itemName: string, itemContent: string }) {
}
這個程式最終結果,是parent template的地方,使用了<app-item>,並且用了event binding,目標是AppItemComponent的itemAdd event。
而itemAdd觸發主要條件,就在app-item.component.ts內,可列為下列幾點:
1. parent如何可以成功綁定<app-item>的itemAdd event:
主要是透過EventEmitter,透過EventEmitter可以做到自己定義event事件,關鍵在於透過EventEmitter的emit() function,EventEmitter可用generic型態宣告,所以宣告會像是:
@Output('itemAdd')
itemAdd = new EventEmitter<{ serverName: string, serverContent: string }>();
2. 如何觸發itemAdd event到parent
在AppItemComponent觸發click的button事件後,會到到AppItemComponent的onAddItem function中,接著function內透過EventEmitter: itemAdded呼叫emit( data) ,觸發itemAdd event,其中emit所傳入的data,就是parent處 event bindng傳入的內容,呼叫emit後對於parent就是AppItemComponent觸發了itemAdd event。3. Parent接收itemAdd所傳入之資料
觸發了itemAdd event,會呼叫onItemAdded function,在onItemAdded function內,會帶入一個參數,其參數就是AppItemComponent的EventEmitter所送出之data,parent可以透過這方式取得child component所送出之資料。3. Local reference
Local Reference是指在template內,我們會利用ngModel來綁定input element,並將綁定的input element在特定事件觸發後加以處理,舉例來說
<input type="text" class="form-control" [(ngModel)]="itemName">
<button class="btn btn-primary" (click)="onAddItem()">Add Item</button>
在上面的template中,我們在Add Item按鈕點下後,因為twoway binding的關係,所以可以在onAddItem function中處理itemName input資料。
不過還有另外可行的方式,就是透過local reference的方式,方法是在input element(或是ngModel綁定的input element),透<tag #variableName>指向某個input element,讓某個input element在template內可以直接傳遞使用,舉例來說:
<input type="text" class="form-control" #itemNameInput>
<button class="btn btn-primary" (click)="onAddItem(itemNameInput)">Add Item</button>
原先使用twoway binding的方式,現在直接用itemNameInput變數指向input element,接著在click event中,就可以直接將itemNameInput作為參數傳入onAddItem中處理。
4. ChildView
我們在angular程式內設定一個reference,直接參考到template上的element,不過一般似乎比較不建議這種方法,還是希望透過data binding的方式做比較好,不過這邊一樣記錄下來可供參考,使用方式與local reference很相似,主要是透過ChildView來做到,方法是在angular內宣告一個變數:
import { Component, OnInit, EventEmitter, Output, ViewChild, ElementRef } from '@angular/core';
@ViewChild('itemNameInput') itemNameInput: ElementRef;
記得要將ViewChild與ElementRef兩個class import到程式內,
在template一樣是透過<tag #variableName>的方式,但variable必須為ViewChild所宣告之名稱才可,不過在ViewChild宣告的地方,是透過ElementRef,可以先透過console看ElementRef內容:
從上面可以知道,要透過ElementRef取得使用者輸入之資料內容,要透過ElementRef.nativeElement.value,所以在angular程式內,就會變成
ElementRef內容 |
在nativeElement屬性下有個value屬性 |
this.itemNameInput.nativeElement.value
5. View Encapsulation
每個component內所定義之css,使用後可以發現與預期的運作方式有些差異,舉例來說,如果在<parent-component>內定義
打開瀏覽器的develop tool看就會發現,在css的p定義中,變成了p[_ng-content-_e0j]這類的css,表示唯有 <p _ng-content-_e0j>這類的element才會套用css,再仔細查可以發現,在<parent-component>定義的template中,所有p都符合這css規則,但是<child-component>卻不合,所以才不會套用css。
原因跟Angular ViewEncapsulation有關係,預設Angular是使用ViewEncapsulation.Emulated方式,只有component自己的element會受到自己定義的css影響,如同上面看到的,Angular會自動在每個element上加入一個獨特的屬性,藉此區別parent與child不同component。
ViewEncapsulation有三種模式:
p {
color: blue;
}
那麼如果這麼使用:
<parent-component>
<child-component></child-component>
</parent-component>
那麼child-component內的 <p> 內容顏色應該也會是藍色才對,不過實際上會發現,<child-element>內的element css完全不會受到<parent-component>所定義之css影響。打開瀏覽器的develop tool看就會發現,在css的p定義中,變成了p[_ng-content-_e0j]這類的css,表示唯有 <p _ng-content-_e0j>這類的element才會套用css,再仔細查可以發現,在<parent-component>定義的template中,所有p都符合這css規則,但是<child-component>卻不合,所以才不會套用css。
原因跟Angular ViewEncapsulation有關係,預設Angular是使用ViewEncapsulation.Emulated方式,只有component自己的element會受到自己定義的css影響,如同上面看到的,Angular會自動在每個element上加入一個獨特的屬性,藉此區別parent與child不同component。
ViewEncapsulation有三種模式:
- ViewEncapsulation.Native: 以瀏覽器原生地Shadow Dom方式產生,有些瀏覽器不支援該模式則無法正常呈現。
- ViewEncapsulation.Emulated:Angular模擬Shadow Dom,方法是在css與element上加入隨機的是別屬性。
- ViewEncapsulation.None:不使用也不模擬Shadow Dom。
使用方法是在Angular的component宣告內,
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
encapsulation: ViewEncapsulation.Emulated
})
可以在這邊設定要採用什麼方式。6. component tag內放入html
在自己定義了component後,如果希望可以有html當作參數,顯示於component中,Angular預設是會忽略掉的,像是下列template,<p></p>的部分是不會顯示在component中的。
<app-item>
<p>
test
</p>
</app-item>
如果希望可以讓component tag接受template內容,並顯示在component中,則只要在<app-item> element的template檔案中,加入
<ng-content></ng-content>
關鍵就是ng-content,<ng-content>擺放的地方,就是傳入的html element顯示的地方,在設計上可以利用這點,結合component的template與輸入之template。
以上例來說,<ng-content></ng-content>的地方,在runtime就會變成<p>test</p>。
沒有留言:
張貼留言