更新 : 2019-01-03css
原來 @ContentChild 是能夠找到 sibling 指令或組件的html
不當心看到 routerLinkActive 的源碼,發現它是使用 @Contentchild 來獲取 routerlink 的 html5
原本以爲有點怪,contentchild 不是隻能獲取到 transclude 的指令嗎, 但是 <a routerlink routerlinkactive > 也能夠 sibling 用啊. c#
作了測試才知道原來一直都是能夠的.後端
多個 ng-content 時要留意緩存
refer : https://www.youtube.com/watch?v=PTwKhxLZ3jI性能優化
<ng-content></ng-content>
after <ng-content></ng-content>
當模板中有超過一個 ng-content 時, 只有最後一個會顯示出來. 就是 after 以後的那個.app
<ng-content *ngIf="true" ></ng-content> after <ng-content *ngIf="false" ></ng-content>
這樣的話, 你可能覺得第一個會展示,但實際上是不會,由於 ng-content 是最後一個,而最後一個 ngif 確實 false 因此就什麼也沒展示了.dom
試試用 ng-template + ng-container 來 appendasync
<ng-template #template> <ng-content></ng-content> </ng-template> <ng-container #target ></ng-container> <div (click)="append()" >append</div>
結果第一個是有 content 的,接下來就沒有了. ngFor 也是如此
若是咱們能夠接受只有其中一個 ngIf 獲取到 content 那麼,寫法能夠是這樣的.
這個其實也是上面 template + container 的概念作的.
<ng-template #content> <ng-content></ng-content> </ng-template> <ng-container *ngIf="true" > <ng-template [ngTemplateOutlet]="content" ></ng-template> </ng-container> <ng-container *ngIf="true" > <ng-template [ngTemplateOutlet]="content" ></ng-template> </ng-container>
更新 : 2018-12-22
viewchild viewchildren contentchild 使用細節
補上一些以前沒有寫清楚的細節
XXChild vs XXChildren
child 返回第一個被 select 中的值,
children 返回一個 queryList 對象
child 的好處是簡單, 適合用於只有一個值並且這個是固定存在的狀況下
children 除了能夠獲取到多個值覺得,另外一個特點是它能夠 watch, 當數量變化時能夠監聽到.
contentXX vs viewXX
區別是查找的區域不一樣
好比你在 test.component 裏寫了這 2 個 query
view 的查找區域是 test.component.html
ng-content, ng-template內, 內組件的模板都不在其查找範圍內哦
<ng-content></ng-content> <ng-template> <abc></abc> </ng-template> <abc></abc>
上面只有最後一行是在範圍內的, 而 abc.html 裏頭的 component 也不在範圍內了. (因此沒有層中層找的概念)
content 的查找區域來至於 trancslude
一樣的 ng-template, ng-content. 組件內的模板也都不算在範圍哦
<test> <ng-template> <abc></abc> </ng-template> <ng-content></ng-content> <abc></abc> <div> <abc></abc> </div> </test>
這裏有個值得注意的點
@ContentChild(Abc, { descendants: true })
descendants 子孫, 默認是 false. 意思是隻搜索第一層的 element, 最後那個被 div 包起來的 abc 組件是搜索不到的
若是設置是true, 那麼 div 內的 abc 才能夠獲取到. 它的區別只是這個而已哦.
另外還要講講 read
XXChild(firstParam).
firstParam 能夠是任何依賴注入. 好比一個 class, 一個 token. 雖說是模板查找可是實際上是依賴注入查找來的.
好比 :
ViewChild(Xyz)
假設 abc.compomnent 的 decorator 聲明瞭 providers : [{ provide Xyz, useClass: WhatEverClass }]
那麼 ViewChild 是會找到這個 WhatEverClass 的
除了 class token, 還能夠是模板變量
這裏就會須要用到 read 了
好比 test.html
<abc #abc ></abc> <div #div ></div> <div someDir #dir1 ></div> <div someDir #dir2="someDir" ></div>
viewchild('abc') 獲取到 abc component
viewchild('div') 獲取到 elementRef 注意這裏是 ref 而不是直接拿到 element
viewchild('dir1') 獲取到 elementRef 模板變量遇到 component 就會拿 component, 遇到指令不會有設麼特別反應,因此依然獲取的是 elementRef
viewchild('dir2') 獲取到 someDir 由於咱們寫了一個 =exportAsDir
因此最關鍵的地方是第 1 個, 當模板變量沒有指定獲取指令時,若是 element 是 component, ng 會認爲咱們要的是 component 而不是原生 html element
這個合理, 由於其實 component 固然也是 html element 丫,在 custom element 的狀況下也是. 固然對於後端渲染就不同啦
因此呢,若是咱們想要的是原生 element 而不是 component 咱們就必須作一些額外的處理.
viewchild('abc', { read : ElementRef })
這樣就能夠了, 隨帶一提, 這個默認選 component 的設計, 在咱們只使用模板變量時尤爲糟糕, 由於咱們沒法告訴 ng 選擇原生 html element 惟一的方法就是經過 viewchild 或者使用另個一指令來曝露. 好比寫一個 native 指令來幹這個事兒...
<abc native #native="native" ></abc> <div (click)="log(native.element)" >click</div>
最後若是是第 4 個場景
<div someDir #dir2="someDir" ></div>
viewchild('dir2') 會獲取到 someDir 可是若是咱們硬加上 read, 最後 read 會優勝.
viewchild('dir2', { read : ElementRef }) 最後依然是 elementRef
更新 : 2018-12-19
模板語法 'as' 的原理
<div *ngIf="condition as value; else elseBlock">{{value}}</div>
之前我覺得 'as' 應該被視爲 NgIfAs 指令, 可是在源碼裏卻沒找到
問了大神 Trotyl 後才知道
as 是 let 的嚴格語法糖,`foo as bar` 就是 `let bar = foo`,不過二者省略內容時的自動補全不一樣,`as bar` 會被補全爲 `<directiveName> as bar` 而 `let bar` 會被補全爲 `let bar = $implicit`。。一個很常見的用戶誤解是認爲 as 和前面的表達式有關係,*ngIf="condition as value" 會被直接斷句爲 *ngIf="condition; as value" 然後補全爲 *ngIf="condition; ngIf as value",換成 let 語法就是 *ngIf="condition; let value = ngIf"。。
依據上面這個解析, 若是有 2 給 async 回如何呢 ?
<div *ngIf="(can$ | async) as can && (super$ | async) as super else loading" > {{ super }} </div>
天然是直接報錯咯~~
只能有一個 as, 下面這樣纔對. ng 會用最後一個 async 做爲 context.ngIf 的值, 若是咱們把順序調過來, super 的也會跟着變化
<div *ngIf="(can$ | async) && (super$ | async) as super else loading" > {{ super }} </div> <ng-template [ngIf]="(can$ | async) && (super$ | async)" [ngIfElse]="loading" let-super="ngIf" > {{ super }} </ng-template>
若是遇到這種狀況,可使用 combineLatest 作一個合併的流, 或者 nested ngIf 來實現. 雖然看上去有點蠢...哈哈
更新 : 2018-04-02
component 也能夠當指令用
<div app-super >kknd</div> <div> <app-super> kknd </app-super> </div>
上面幾乎是等價的, 雖然 component 一般都是 element, 但其實也是能夠寫成 attribute 的, 不過一個 element 只能綁定 1 個.
@Component({ selector: '[app-super]', templateUrl: './super.component.html', styleUrls: ['./super.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush })
更新 : 2018-04-01
結構指令 + 微語法 + template
refer : https://angular.cn/guide/structural-directives#microsyntax (微語法規範)
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
其實以前也說過了,只是說的不詳細, 多舉些例子熟悉一下.
像上面這種寫法,很是適合作動態插入模板.
原理是用一個結構指令把 template 存起來,稍後才輸出
<app-super *dada="let contextName = name; let $implicitValue; columns: 100"> {{ $implicitValue }} <br> {{ contextName }} </app-super> <ng-template dada [dadaColumns]="100" let-$implicitValue let-contextName="name"> <app-super>{{ $implicitValue }} <br> {{ contextName }}</app-super> </ng-template>
上面 2 個寫法是徹底同樣的意思, 第一個在 ng 編輯以後會變成第 2 個. (第一個只是美一點罷了)
咱們一般會使用第一種寫法.
須要注意幾個事兒.
* 表示是 ngtemplate 把 super 包起來,
dada 是結構指令
let $implicitValue 能夠寫成任何的變量, let whatever, 最終它的值是 ngTemplateContext.$implicit
columns 編輯後會變成 [dadaColumns] 就是把 *dada <--這個指令名加上去 prefix. dadaColumns 能夠是 input or 另外一個指令均可以,
這裏有一個點要留意, 就是第一個字大小寫, 按理說 dadaColumns input 應該時對應 Columns 第一個字大寫,可是 ng 會無視掉, 大小寫均可以. 可是呢, ide 會報錯.
雖然報錯,可是運行時正確的.
let contextName = name 這個對應 變量 = ngTemplateContext.name
在要使用它的地方調用就能夠了. 從 dada 裏面拿出 template 輸入 context
<ng-container *ngTemplateOutlet="dada.template; context: { name : 'xinyao', $implicit : 'value' }"></ng-container>
p,s 微語法的順序要注意,
*dada="columns: 100;let contextName = name; let $implicitValue;" <-- 這樣是不能夠的,開頭必定要 let ...
dada 指令
import { Directive, TemplateRef, Input } from '@angular/core'; export class Person { name: string $implicit = 'value' } @Directive({ selector: '[dada]', exportAs : 'dada' }) export class DadaDirective { constructor( // 經過注入獲取到當前模板, 由於指令會放在模板上面,因此能夠注入到 // public template: TemplateRef<Person> ) { } @Input('dadaColumns') // ng 的規範是 columns: number }
更新 : 2018-03-11
[attr.href]="null" null 能夠把 attribute href 給移除.
更新 : 2018-03-09
補充 :
constructor 階段
app.html
view scan 順序是 上到下,外到裏 (dir 的順序不是預計你放哪一個在前面,而是 declarations 的順序)
<1 3 2 >
<4></4>
</1>
<5></5>
依照順序每一個 component and directive constructor 運行.
這一個 view 搞定後, 進入 1 comp 的 view 重複上面的 view scan,一直重複往裏面進, 直到沒有 component or directive 了就退回上一個 view
好比退回到這 view, 再進入 4 的 view... 而後 5 的 view..
搞定後這個階段就結束了。全世界的 comp and dir 都 constructor 了.
onInit, onContentInit, ContentChecked, render 階段
依照上面的順序,逐個 oninit, 這時 @input 就有了. 但要當心,由於 1 comp onInit 比 2 dir 早
若是 1 comp 依賴 2 dir 的值就要當心哦,
你能夠依賴 2 dir constructor 後的值,但不能夠依賴 2 dir onInit 的值 (由於它都尚未 onInit)
這一個 view 搞定 onInit 後, 先不要進入 1 comp 的 view。
而是繼續在這個 view 跑 onContentInit 和 ContentChecked
順序是這樣的 裏到外,上到下 (dir 後於 comp 哦)
<2 4 3>
{{ render1 }}
<1>{{ render2 }}</1>
</2>
<5></5>
onContentInit和ContentChecked 是一塊兒跑的,而不是跑徹底部人的 onContentInit 才跑 ContentChecked. 是緊接着一塊兒,不是分開哦.
這一個 view 完成後, 就跑這一頁的 render,這時 render1,2 的 getter 就會觸發了.
到這裏 app view 渲染算是完成了,如今要處理它的孩子們.
咱們從 2 comp 的 view 開始運行 onInit 階段. 而後遞歸進去..
這裏有個關鍵就是,何時一個 comp 的 viewInit 會被觸發.
答案是 這個 comp 的 parent view 處理完全部 comp init 階段後.
好比 2 comp 的 viewinit 何時觸發.
2 comp view 即便 render 了它也不會觸發.
而是等到 2 comp 的 parent 也就是 app view 處理完全部 comp (2,4,3,1,5 comps) 以後, 纔會逐個調用 2,4,3,1,5 的 viewInit and viewchecked.
這 2 也是緊接着一塊兒觸發的。
ng 的順序
start from html
scan 上到下,外到裏 把全部組件和指令都實例化 constructor
note : constructor 階段拿不到 input 值
順序是這樣
app.component.html
<1>
<2>{{ renderValue1 }}</2>
</1>
{{ renderValue2 }}
<3></3>
完了以後按順序進入組件的模板, 而後重複上面的實例化一直 loop 到最裏面裏面完.
所有實例化完後,回到最初的模板.
開始一個個組件 OnInit, 順序如出一轍
init 的時候就能夠拿到 input 值了,若是有 viewchild 也都拿到了
由於全部組件都實例化了嘛, 不過特別注意,雖然你拿到 viewchild 組件,可是這些組件都尚未 OnInit 哦.
這也是爲何會有 afterViewInit 的意思了。懂了吧.
這一模板 OnInit 完了以後
開始一個個組件 run AfterContentInit and AfterContentCheck, 上到下, 裏到外
爲何是這個時候 run 呢?
由於咱們上一個步驟才把這一頁全部的組件 OnInit 啊.
好比 <2></2> 就是一個 transclude 的, 咱們剛把它 Oninit 好,如今就要通知 <1></1>
調用它的 afterContentInit and checked 了
特別注意, 雖然這裏調用了checked, 但其實目前這個模板是尚未 reader 的
好比 renderValue 是一個 getter, 這個時候, 是尚未被調用過的.
等到這一模板全部的 afterContentInit and checked 後, 這個模板就開始 render 了
renderValue getter 被觸發.
這一模板算是搞定一大半了, 如今咱們就開始進入 <1> 的模板
在 <1> 的模板裏,要作的事情依舊是一個個組件 OnInit -> afterContent -> render
和剛纔同樣. 假設這個 <1> 模板 reder 完了以後發現, 發現已經沒有子層組件能夠繼續深刻了。
那麼它就會調用 <1> 的 AfterViewInit.
而後退出這個模板回到上一個.
因此這個進入返回的循環就成立了。
當咱們深刻完 <3></3> 而且返回了以後,最初的模板就沒有組件能夠在深刻了。這時咱們就調用
AfterViewInit 假設咱們最初這個模板就是 AppComponnt, 那麼 AppCompoennt.AfterViewInit 後
整個循環也就結束了.
實例化全部的 component and 指令 (全部包括子孫的 view 裏面的)
init 這個模板的全部 component (外面到裏面)
template 若是在這個模板上會被渲染的話, 會先去 render template 的內容, 好比 *ngIf='true', templateoutlet
contentInit 這個模板的全部 component (裏面到外面)
render 這個模板 (binding data)
遞歸的進入子孫層, 循環上面的順序
循環完了之後, viewInit 這個模板的 component (裏面到外面), 概念 : component 的 viewInit 是依據使用它的組建的模板決定執行時間的,不是說這個 component render 了之後就立刻執行 viewInit. 注意哦
更新 : 2018-02-08
ng 的順序
一個組件的順序很好理解 construtor -> init -> content -> view
但層中層有時候就容易昏頭了
這裏記入一下,
1. 全部的 construtor 必定是先執行的 (一直到子孫層, 所有執行, 除了在 ng-template 的不會立刻運行)
2. 把模板中全部的 component onInit 一遍, 上到下,外到裏
3. 而後在模板中尋找擁有 transclude 的 component, 並運行 afterContent 裏到外
4.只要模板中存有其它 component, 就必須等到這些 component 都 after view 了, 本身才能 after view.
4. 模板中的 component 要 afterviewinit 必須等到整個模板的子孫層都遍歷 2-4 一遍, 子孫所有好了,
在依據上到下,裏到外去把模板中每個 component 運行 afterviewinit 另外說一點, ng-template + transclude content + ngtemplateoutlet 其實就是把模板內的組件移入子層,它會在 aftercontent 以後才運行 construtor, 而後其它的順序就連上普通的了.
我以前一直覺得 template 會在 after view 以後才跑,其實不會, 它和通常 component 是同樣的執行只是 construtor 有點奇葩而已.
看例子 : youtube link : https://www.youtube.com/watch?v=rQEfUG3GN40&feature=youtu.be (給我本身看的)
更新 : 2018-02-01
viewchild 的循環引用
有時候咱們會遇到循環引用好比 parent 使用了 viewchild 同時 child 又 inject 了 parent component
這時咱們可使用
更新 : 2017-10-18
當 local variable 遇到 ngIf
<div (click)="input.focus()" >click</div> <input *ngIf="true" #input type="text">
上面 focus 會報錯, 由於 ngIf 會打亂順序.
解決方法就是使用 viewchild
@ViewChild('input', { read : ElementRef })
input : ElementRef
<div (click)="input.nativeElement.focus()" >click</div> <input *ngIf="true" #input type="text">
注意這時 input 變成了 ElementRef 類型了
select option 用 [value] 和 [ngValue] 的區別
基本上用 [ngValue] 就對了, [value] 只能是 String.
若是用 mat-option 那是沒有 [ngValue] 的, 可是它的 [value] 其實就是 [ngValue] 的功能
更新 : 2017-10-14
ViewChildren + 派生類
<form [formGroup]="form" [fGroup]="edm" (ngSubmit)="submit()"> <s-mat-input controlName="name"></s-mat-input> <s-mat-upload controlName="images"></s-mat-upload> <button type="submit">submit</button> </form>
好比咱們想把全部的 control select 出來
app.component.ts
@ViewChildren(AbstractAccessorComponent, { read: AbstractAccessorComponent })
accessors: QueryList<AbstractAccessorComponent>
mat-input.ts 經過依賴注入就能夠了
@Component({ selector: 's-mat-input', templateUrl: './input.component.html', styleUrls: ['./input.component.scss'], providers: [{ provide: AbstractAccessorComponent, useExisting: forwardRef(() => InputComponent) }] }) export class InputComponent extends AbstractAccessorComponent implements OnInit { required = false; ngOnInit() { super.ngOnInit(); this.required = this.fControl.validators.find(v => v.name == 'required') != null; this.inputType = this.fControl.ui.accessorType.substring('input'.length).toLowerCase(); } }
更新 : 2017-09-05
<app-abc [comp]="ttc" ></app-abc> <app-efg #ttc ></app-efg>
組件能夠這樣溝通, 但執行順序咱們要搞清楚哦.
依據 element 的結構, app-abc 會先被 onInit, 這時咱們能夠獲取到 efgComponent 實例, 可是這個 efg 實例只是個對象有一些原始值, 但尚未被 onInit 過. 因此若是咱們想依據它來作渲染, 那麼要 settimeout 哦。
更新 2017-08-30
監聽 transclude components 變化
test-dada.component.html
<test-dada> <dada-child *ngFor="let item of items"></dada-child> </test-dada>
dada-child.component.ts
export class DadaComponent implements OnInit, AfterContentInit { @ContentChildren(DadaChildComponent, { read : DadaChildComponent }) dadaChildComponents: QueryList<DadaChildComponent> ngAfterContentInit() { this.dadaChildComponents.changes.subscribe((c) => { console.log('changed', c);
console.log(c === this.dadaChildComponents); // 是同樣的 }); } }
test-dada.component.ts
changItem() { this.items.push({ Id : 4, value : 'd' }); // ok this.items = []; // ok this.items[0].Id = 5; // ok or fail (依據有沒有使用 trackby) this.items[0].value = 'dada'; // fail }
無論有沒有使用 onPush, 無論有沒有 immutable, ng 均可以監聽到 array 的增長和減小. 須要注意的是 ng 是i經過 trackby 來作對比的, 而後判斷 array 的存在與否.
要在 component 內綁定全局事件的話,可使用 @HostListener, 它會隨着 component destroy 而 unbind, 很方便的哦.
export class StoogesAppComponent implements OnInit { @HostListener('document:click', ['$event']) private documentClick(event: Event) { this.globalClicked$.emit(null); }
更新 2017-08-29
ng-template 使用
以前講過, 不過例子不整齊, 這裏重寫一個
<app-dada> <ng-template #resultTemplate let-insideValue="insideValue"> {{ insideValue }} </ng-template> <ng-template #optionTemplate let-i="index" let-item="$implicit"> {{ i }} : {{ item }} </ng-template> </app-dada>
咱們想把 2 個模板傳進 dada 組件裏, 其中一個仍是用於 ngFor 哦 (注意, $implicit 用來獲取 ngFor 的 item)
此外咱們還經過 let-insideValue 來和 dada 組件溝通, 這個 insideValue 是由 dada 組件負責填入的.
export class DadaComponent implements OnInit {
constructor() { }
@ContentChild('optionTemplate', { read: TemplateRef }) optionTemplateRef: TemplateRef<NgForOfContext<string>> items = ['a', 'b', 'c']; ngOnInit() { } }
達達組件定義好 2 個 templateRef, 咱們也可使用 @Input 傳入 templateRef, 但這裏咱們用的是 translude 的手法.
dada.component.html
<ng-container *ngTemplateOutlet="resultTemplateRef; context: { insideValue : 'insideValueDone' }"></ng-container> <ng-template ngFor [ngForOf]="items" [ngForTemplate]="optionTemplateRef"></ng-template>
這樣就能夠了.
更新
更新 2017-06-30
組件是能夠繼承來用的. 不少時候 template, css 是一直變化的,可是 controller 邏輯卻常常同樣,因此就可使用繼承來解決了.
@Component({ selector: 'app-test', templateUrl: './test.component.html', styles: [], changeDetection: ChangeDetectionStrategy.OnPush, // 若是父組件是 accessor 記得這裏要寫 provider 哦 providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => TestComponent), multi: true }], }) export class TestComponent extends PaginationComponent implements OnInit { constructor( cdr: ChangeDetectorRef, validators: ValidatorsService ) { super(cdr, validators); // 父組件須要什麼就注入什麼 } ngOnInit() { super.ngOnInit(); // 記得調用父組件哦 // do something else ... } }
使用 extends 就能夠了, selector, css, template 都必須 override
ngOnInit, OnDestroy 這些,若是子組件沒有 override, 父組件依然會自動跑,若是你 override 了請本身決定要不要調用.
更新 2017-03-15
image src biding 404 注意事項
<img [src]="data" >
data = "" 就不會去加載圖片( undefined, null 也是去加載哦 ), 其他狀況下,模板一旦渲染就會立刻去加載.
好的處理就是寫個 ngIf 等待 async data 回來才渲染圖片.
更新 2017-03-03:
ng 上寫 keyCode 事件
<input (keydown.arrowUp)="$event.preventDefault()" (keydown.shift.tab)="$event.preventDefault()" type="text" />
看的出就是把 keyCode.key 寫成駝峯式就能夠監聽到了. 配合 shift 也很容易寫哦.
QueryList & transclude Content
@ContentChildren(OptionComponent) options: QueryList<OptionComponent>
在處理 ContentChildren 時要注意幾件事. 若是咱們想監聽它的 length 改變, 可使用 change.subscribe(...)
若是你想監聽 content 內 component 的事件,你要記得 rewatch,像我上面那樣,由於 content 的數量是動態的,compoennt 會被建立和銷燬, 一旦銷燬了, 你的監聽也被銷燬,而新建立的你得再監聽一次.
若是你想在數量變化以後對 content 進行操做,你得寫一個 timeout, 讓它觸發下一次的 digest
最近本身在寫 multiple select 有些發現
ng 的 select option 的 2 個小邏輯 :
<select [(ngModel)]="hero.skillId" name ="skillId"> <option *ngFor ="let skill of skills" [ngValue]="skill.id">{{ skill.name}}</ option> </select>
1.若是你修改 skills 的話, this.skills[0].value = "bla bla" 或 this.skills = new array ..
ng 經過 doCheck 檢測到你的修改, 而後作出更新. 若是當前的 model value 沒法在新的 options 中匹配到的話, ng 不會作任何處理.
2.即便你寫 trackBy, 當你把 skill.value 換掉後, ng 沒法經過 trackBy 找到對的 value 來更新. e.g your select is { Id :1,value :"v" }, then you change skills to [{ Id:1,value:"ttc" }] after detectchange your model still "v".
3.因爲是直接 muta skills, ng 只能經過 doCheck 來檢查, 在 ChangeDetectionStrategy.OnPush 的狀況下, ng 會直接壞掉哦. 解決方法就是手動觸發 changeDetect. 參考 OnPush : http://www.cnblogs.com/keatkeat/p/5963295.html
更新 : 2017-02-11
angular drag & drop 若是不要使用 plugin 的話, 能夠用最基本的寫法
能夠參考原生 html 的例子 : http://www.w3schools.com/html/html5_draganddrop.asp
<form [formGroup]="form" submitableForm> <s-upload #imagesUpload [config]="uploadConfig" formControlName="images"></s-upload> <div *ngFor="let fileData of imagesUpload.fileDatas" draggable="true" (dragstart)="imagesUpload.dragingFileData = fileData" (dragend)="imagesUpload.dragingFileData = null" (dragenter)="imagesUpload.dragingFileData && imagesUpload.moveFileData(fileData,imagesUpload.dragingFileData)" [sDragover]="imagesUpload.dragingFileData" class="uploadImage"> <img [src]="fileData.file" width="100px"> <i [show]="fileData.loading" class="fa fa-spin fa-spinner loading"></i> <i (click)="imagesUpload.removeFileData(fileData)" [show]="!fileData.loading" class="fa fa-times close"></i> </div> </form>
dragingFileData 用於緩存變量
(dragend) 清楚緩存, 緩存還有一個重要用途就是若是你有 2 個 upload file 的時候不容許它們 cross drag
之因此不直接使用 (dragover) 是由於它會一次觸發 digest (性能優化), sDragover 是一個指令裏面手動添加了 event, 這樣就不會一直 digest 了.
更新 : 2017-01-26
component selector 不須要擔憂和將來遊覽器撞名字, ng 最終會勝利.
directive 和 component input 不須要擔憂衝突, 2 個能夠一塊兒工做, 都拿獲得值.
2016-08-28
refer :
https://angular.cn/docs/ts/latest/guide/template-syntax.html
https://angular.cn/docs/ts/latest/cookbook/component-communication.html
https://angular.cn/docs/ts/latest/guide/displaying-data.html
https://angular.cn/docs/ts/latest/guide/user-input.html
https://angular.cn/docs/ts/latest/guide/lifecycle-hooks.html
ng 的組件和遊覽器原生的組件是同一個概念,在方方面面都很類似.
和 ng1 同樣,組件少不了數據綁定
1. model to view 綁定
template: ` <my-product [model-to-view-value]="'Derrick' + 'Yam'" ></my-product> ` @Input("model-to-view-value") //若是屬性名字不同的話能夠調整 modelToViewValue: string;
有幾個點容易讓人混淆
1. 左邊不使用 [括弧] : <my-product value="abc" > 這樣的寫法右邊的值 "abc" 只是一個很普通的 string, 它並非引用咱們 component instance 的屬性哦. 也由於如此, 你以後就不可能改變它的值了. 算是 one time binding and only for string. (html 就是這樣).
2.你可能會問若是這樣呢 : <my-product value="{{ abc }}" > 這樣的話右邊的 "abc" 會引用 component instance, 並且會一直保持同步, 也就是說不是 one time binding 了. 但值依然只能是 string. abc must be string.
3. 左邊是 [括弧] : <my-product [value]="abc"> 這樣的話 abc 就能夠傳入任何類型了, 也會一直保持同步. 這也是咱們最經常使用的表達模式.
2. 事件綁定 (監聽嘛)
和 html 同樣,咱們內部只返回一個值, 外部經過關鍵字 $event 來獲取
selector: "my-app", template: ` <my-product (myClick)="doSomething($event)" ></my-product> ` doSomething(data: string) { console.log(data) } selector: "my-product", template: ` <div>this is product component</div> <div (click)="publish($event)" >publish</div> ` @Output() myClick: EventEmitter<string> = new EventEmitter(); publish($event) {
//$event = html original click event this.myClick.emit("value"); }
很惋惜 ng 的事件不支持冒泡 很惋惜 ng 的自定義事件不支持冒泡, 這裏和 dom event 不一樣,很差記 /.\
3.雙向綁定
selector: "my-app", template: ` <p> value outside : {{ someValue }}</p> <div (click)="updateValue()" >update value from outside</div> <my-product [(someValue)]="someValue" ></my-product> ` someValue: string = "value start"; updateValue() { this.someValue = "value updated from outside"; }
selector: "my-product", template: ` <div> value inside : {{ someValue }}</div> <div (click)="updateValue()" >update value from inside</div> ` @Input() someValue: string; @Output() someValueChange: EventEmitter<string> = new EventEmitter(); updateValue() { this.someValueChange.emit("value updated from inside"); }
其實ng是經過數據綁定加上事件綁定來完成這事兒的, [(value)] 只是語法糖
把它拆開是這樣的
<my-product [someValue]="someValue" (someValueChange)="someValue = $event" ></my-product>
若是要綁定原生 html 好比 input, select 等就是用 ng 幫咱們作好的 [(ngModel)] 就能夠了, 以後我會和表單一塊兒講.
當咱們綁定到原生遊覽器組件時有些概念要清楚.
dom Attribute 和 dom Property 不是同一個東西
<img src="123.jpg" /> 寫在 element 上的是 Attribute, 它的值只用來初始化 property 的值
imgDomObject.src = "456.jpg" 這個纔是 property.
ng 的綁定是在 property 上的.
Attribute 和 Property 不老是一對一匹配的, 有些 Attribute 是沒有 property 的,這時咱們要這樣寫
<some-elem [attr.attributeName]="..." > 要在前面加 attr.
沒有 ng1 的 ng-options 了,如今是這樣 :
ng-repeat = *ngFor (*表示結構類型的指令)
<select [(ngModel)]="hero.skillId" name ="skillId"> <option *ngFor ="let skill of skills" [ngValue]="skill.id">{{ skill.name}}</ option> </select>
模板中若是有引用對象屬性,可是對象是 null 時, 會報錯哦. 可使用 ?. 來處理,這個和 c# 6.0 語法同樣
<div>{{ someObj?.value }}</div>
*ngFor 的值使用的是"微語法"解析, 它和咱們平時寫模板表達式是不一樣的哦.
*ngIf 用的也是 "微語法".
track by 的值是一個方法 (必定要是一個方法)
track by 的做用 :
ng 很聰明, 若是 heroes 是值類型的話, 它不會隨便的 rebuild element, 好比 : heroes = ["a","b"], 而後 heroes = ["b","a"] 這樣 ng 是不會 rebuild 的, 它只會更新 element.
可是若是是引用類型, heroes = [{ Id : 1 },{ Id : 2 }] 而後 [{Id : 2},{Id :1}] , ng 就會 rebuild element 了
爲了要性能好一些, 咱們盡力不要 rebuild, 能夠用 trackby, trackby return 的值決定了需不須要 rebuild.
trackby index 和 Id 的區別 :
[{Id : 1}, {Id :2}] change to [{Id: 2}]
trackby index 的話 destroy 的是 Id 2 (由於 ng 是把 slot 1 的 Id update 去了 2, 而後 destroy 掉 2)
trackby Id 的話 destroy 的是 Id 1
依據你的需求來決定怎樣 trackby 哦.
template: ` <div *ngFor="let hero of heroes; let i=index; trackBy:trackByHeroes" > <p>{{ hero.Id }} {{ hero.name }}</p> </div> ` trackByHeroes(index: number, hero: Hero) { return hero.Id; }
全部的 * 結構指令都只是語法糖, 下面這個纔是真正的寫法
<ng-template ngFor let-number [ngForOf]="[1,2,3]" let-i="index" [ngForTemplate]="templateRef" [ngForTrackBy]="trackByFn" > </ng-template>
因此這 3 個是同樣的:
1. <abc> <div *="let insideValue = insideValue;" > {{ outsideValue }} vs {{ insideValue }} </div> </abc> 2. <abc> <div template="let insideValue = insideValue;" > {{ outsideValue }} vs {{ insideValue }} </div> </abc> 3. <abc> <ng-template let-insideValue="insideValue" > <div> {{ outsideValue }} vs {{ insideValue }} </div> </ng-template> </abc>
補上一個對應的
@Component({
selector: 'abc',
template : `
<ng-template [ngTemplateOutlet]="templateRef" [ngOutletContext]="{ insideValue : insideValue }" ></ng-template>
`
})
注意, 第 3 個的 let-inside 有一個 "-"
我推薦你們使用第 3 個, 雖然長一點,不過好理解,也不容易遇到坑
好比 :
<abc> <ng-template #firstContent let-insideValue="insideValue" > <div> {{ outsideValue }} vs {{ insideValue }} </div> </ng-template> </abc> @ContentChild("firstContent") templateRef; // 是對的 <abc> <div #firstContent *="let insideValue = insideValue" > {{ outsideValue }} vs {{ insideValue }} </div> </abc> @ContentChild("firstContent") templateRef; // 是錯的, 由於拿到的是 div 而不是 template 了.
ngForTemplate 容許咱們綁定 templateRef, 好比 transclude 的 viewContent 等等.
<div *ngFor="let number of [1,2,9]" > <template [ngTemplateOutlet]="templateRef" [ngOutletContext]="{ data : number }" ></template> </div>
ngTemplateOutlet 和 ngOutletContext ( ng還在試驗中 ) 一樣容許咱們綁定 templateRef 並且能夠注入 context 讓 templateRef 經過 let-abc="data" 來調用到 number.
refer : http://stackoverflow.com/questions/37676593/how-to-repeat-a-piece-of-html-multiple-times-without-ngfor-and-without-anoher-c
模板引用變量 ( template reference variables )
template: ` <input type="text" #input > {{ someValue }} <div (click)="someValue = input.value" >click</div> `
#input 表示那個組件( 自定義組件也能夠哦 ), 可用範圍很廣, 父親,兄弟, 孩子都用的到
它還能夠配合自定義指令的 exportAs 來使用, 相似 #variable="exporstAs"; 當一個 element 上有不少指令時, 咱們就能夠選擇要使用那個 class
咱們能夠很容易的把一個外部的模板傳進去組件中,而且溝通. (下面還會介紹一個 transclude)
//outer component <template #abc let-innerValue="data" > <div>{{ outerValue }}</div> <div>{{ innerValue }}</div> </template> <inner [template]="abc" ></inner> //inner component <p>inner</p> <template [ngTemplateOutlet]="template" [ngOutletContext]="{ data : 'innervValue' }" ></template>
此外還能用它來獲取 element 的 style 好比 height, width, scrollTop 等等 <div #div (click)="doSomething(div.scrollTop)">
管道 Pipe
import { Pipe, PipeTransform } from "@angular/core"; //pure 的意思是 angular1 的 $stateful @Pipe({ name: "demoCombine", pure : true }) export class DemoCombinePipe implements PipeTransform { transform(value: string, param: string): string { return value + " " + param; } } template: ` <div> {{ name | demoCombine : value }} </div> `, pipes: [DemoCombinePipe]
transclude 和 ng1 相似
template: ` <demo-transclude-dir> <div class="first" >first space</div> <div class="second" >second space</div> </demo-transclude-dir> `,
能夠選擇任何方式來表示不一樣分區,我這裏使用的是 class
template: ` <div> <ng-content select=".first" ></ng-content> <ng-content select=".second"></ng-content> </div> `
內部經過 <ng-content> 配合 dom selector 來匹配區塊
transclude 一般會用到一個叫 viewProviders 的東西, 做用是隔離依賴注入, 好比咱們不但願外部嵌套進來的 content 能依賴注入內部的 providers 就能夠定義載 viewProviders.
viewProviders 只有本身和 child 能夠注入到, content 則注入不到.
這裏懶得寫了
說說一些概念就好
https://angular.cn/docs/ts/latest/guide/lifecycle-hooks.html#!#afterview (生命週期鉤子)
主要是說
ngAfterContentInit, ngAfterContentChecked
ngAfterViewInit, ngAfterViewChecked 的使用
Content 攔截指的是 transclude 的內容, 在子層作攔截能夠獲取 transclude 組件對象而後動一點手腳.
afterView 則指的是子層完成後,在父層作攔截, 這裏能夠調用到子層組件對象作一點手腳, 不過記得要跑一個 timeout 否則不會渲染 (由於這個階段渲染已經結束了)
下面是以前寫的一些代碼 :
demo-interception.component.ts
import { Component } from "@angular/core"; import { DemoInterceptionParentComponent } from "./demo-interception-parent.component"; import { DemoInterceptionContentComponent } from "./demo-interception-content.component"; @Component({ selector: "demo-interception", //注意 demo-interception-content 須要定義一個模板引用和一個class //還有 demo-interception-content 的 [value] 也可讓 parent 去攔截作 binding, 不必定要在這裏作 binding, 由於重要是 create content component 時要有就能夠了. template: ` <div> <demo-interception-parent [value]="'parent value'" > <demo-interception-content #firstContentRef class="firstContent" [value]="'content value1'" ></demo-interception-content> </demo-interception-parent> </div> `, directives: [ DemoInterceptionParentComponent, DemoInterceptionContentComponent ] }) export class DemoInterceptionComponent { }
demo-interception-content.component.ts
import { Component, Input } from "@angular/core"; import { FirstContentInterface } from "./demo-interception-parent.component"; @Component({ selector: "demo-interception-content", template: ` <div>{{ value }}</div> ` }) export class DemoInterceptionContentComponent implements FirstContentInterface { @Input() value: string; }
demo-interception-parent.component.ts
import { Component, OnInit, OnDestroy, OnChanges, DoCheck, AfterContentChecked, AfterContentInit, ContentChild, ViewChild, AfterViewInit, AfterViewChecked, Input, SimpleChange } from "@angular/core"; import { DemoInterceptinChildComponent } from "./demo-interception-child.component"; export interface FirstContentInterface { value: string; } @Component({ selector: "demo-interception-parent", template: ` <div> <ng-content select=".firstContent"></ng-content> <demo-interception-child [value]="'child value'" ></demo-interception-child> </div> `, directives: [ DemoInterceptinChildComponent ] }) export class DemoInterceptionParentComponent implements OnInit, OnDestroy, OnChanges, DoCheck, AfterContentChecked, AfterContentInit, AfterViewInit, AfterViewChecked { @Input() value: string; ngOnInit() { console.log("init"); } ngOnDestroy() { console.log("destroy"); } //note : 比如之前的 dirty check, 動不動就會跑的, 小心性能哦 ngDoCheck() { console.log("doCheck"); } //note : 當input屬性發生變化的時候除非, 使用簡單的 === 若是是對象的話不會作深沉對比哦 ngOnChanges(changes: { [propertyName: string]: SimpleChange }) { console.log("onChange"); //console.log(changes); for (let propName in changes) { let chng = changes[propName]; let cur = JSON.stringify(chng.currentValue); let prev = JSON.stringify(chng.previousValue); //console.log(`${propName}: currentValue = ${cur}, previousValue = ${prev}`); } } //教程是使用 class 而非 Interface 的 //e.g. : @ContentChild(ComponentClass) contentChild: ComponentClass; //可是呢,實際開發的時候, 內部組件不可能預測到外部將傳進來什麼組件 //也不可能要求傳進來的組件必須繼承任何特定的類 //咱們頂多能要求他實現咱們的接口而已. //"firstContentRef" 這個是經過模板引用來實現的,也算規範, 教程有說能夠用模板引用 @ContentChild("firstContentRef") contentChild: FirstContentInterface; ngAfterContentChecked() { this.contentChild.value = "new content child value"; console.log("afterContentChecked") } ngAfterContentInit() { console.log("afterContentInit") } //viewChild 攔截是在整個 view 作好以後纔出發的 //因此若是咱們想在目前這個 parent component 中對其作調整的話 //咱們必須寫一個 setTimeout 使它在 digest 一輪作更新 @ViewChild(DemoInterceptinChildComponent) viewChild: DemoInterceptinChildComponent; ngAfterViewInit() { console.log('afterViewInit'); } private record = 0; ngAfterViewChecked() { console.log(this.viewChild.value); //固然須要 if else 啦, 否則不是死循環嗎.. if (this.record == 0) { this.record = 1; setTimeout(() => { this.viewChild.value = "888"; }); } console.log('afterViewChecked'); } }
demo-interception-child.component.ts
import { Component, Input } from "@angular/core"; @Component({ selector: "demo-interception-child", template : `<div>{{ value }}</div>` }) export class DemoInterceptinChildComponent { @Input() value: string; }
組件通信的幾個方式
@Input()
@Output()
service
@Input setter or ngOnChanges ( 針對引用的話, 修改內部是不會觸發的哦, 必定要連引用都換掉才行, immuatable 概念)
local variable (refer : https://angular.cn/docs/ts/latest/guide/template-syntax.html)
ViewChild, ViewContent
依賴注入 parent component ( 若是parent是抽象或則有循環引用的話要 refer : https://angular.cn/docs/ts/latest/cookbook/dependency-injection.html )
as 是 let 的嚴格語法糖,`foo as bar` 就是 `let bar = foo`,不過二者省略內容時的自動補全不一樣,`as bar` 會被補全爲 `<directiveName> as bar` 而 `let bar` 會被補全爲 `let bar = $implicit`。。一個很常見的用戶誤解是認爲 as 和前面的表達式有關係,*ngIf="condition as value" 會被直接斷句爲 *ngIf="condition; as value" 然後補全爲 *ngIf="condition; ngIf as value",換成 let 語法就是 *ngIf="condition; let value = ngIf"。。