- 單向從資料來源到檢視
{{expression}}
[target]="expression"
bind-target="expression"
複製程式碼
單向從檢視到資料來源
(target)="statement"
on-target="statement"
複製程式碼
雙向
[(target)]="expression"
bindon-target="expression"
複製程式碼
-
DOM property
的值可以改變;HTML attribute
的值不能改變。
在Angular
的世界中,attribute
唯一的作用是用來初始化元素和指令的狀態。
當進行資料繫結時,只是在與”元素和指令”的 “property
和事件”打交道,而attribute
就完全靠邊站了。 -
如果忘了加方括號,
Angular
會把這個表示式當做字串常量看待,並用該字串來初始化目標屬性。
下面這個例子把HeroDetailComponent
的prefix
屬性初始化為固定的字串”1+1″,而不是模板表示式”2″。Angular
設定它,然後忘記它。
<app-hero-detail prefix="1+1" [hero]="currentHero"></app-hero-detail>
複製程式碼
作為對比,[hero]
繫結是元件的 currentHero
屬性的活繫結,它會一直隨著更新。
- 插值表示式
{{...}}
,先對雙花括號中的表示式求值,再把求值的結果轉換成字串。
實際上,在渲染檢視之前,Angular
把這些插值表示式翻譯成了相應的屬性繫結。
- 注意:但資料型別不是字串時,就必須使用屬性繫結了。
<p><img src="{{heroImageUrl}}"> is the <i>interpolated</i> image.</p>
<p><img [src]="heroImageUrl"> is the <i>property bound</i> image.</p>
複製程式碼
- 不管是插值表示式還是屬性繫結,都不會允許帶有
script
標籤的HTML
洩漏到瀏覽器中,二者都只渲染沒有危害的內容。
src/app/app.component.ts
evilTitle = `Template <script>alert("evil never sleeps")</script>Syntax`;
src/app/app.component.html
<p><span>"{{evilTitle}}" is the <i>interpolated</i> evil title.</span></p>
<p>"<span [innerHTML]="evilTitle"></span>" is the <i>property bound</i> evil title.</p>
複製程式碼
attribute
繫結
一般情況下,我們通過屬性繫結來設定元素的屬性property
,而不用字串設定元素的attribute
。
但考慮ARIA
,SVG
和table
中的colspan/rowspan
等attribute
。 它們是純粹的attribute
,沒有對應的屬性可供繫結。
如果想寫出類似下面這樣的東西,就會暴露出痛點了:
<tr><td colspan="{{1 + 1}}">Three-Four</td></tr>
複製程式碼
會得到這個錯誤:
Template parse errors: Can`t bind to `colspan` since it isn`t a known native property
方括號中的部分不是元素的屬性名,而是由attr
字首,一個點 (.)
和 attribute
的名字組成
<tr><td [attr.colspan]="1 + 1">One-Two</td></tr>
複製程式碼
-
雙向資料繫結
( [(...)] )
想象盒子裡的香蕉來記住方括號套圓括號[()]
。
雙向繫結語法實際上是屬性繫結和事件繫結的語法糖。 -
內建屬性型指令
- a.通過繫結到
NgClass
,可以同時新增或移除多個類。
把ngClass
繫結到一個key:value
形式的控制物件(value
為boolean
值)
src/app/app.component.html
<div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special</div>
複製程式碼
src/app/app.component.ts
currentClasses: {};
setCurrentClasses() {
// CSS classes: added/removed per current state of component properties
this.currentClasses = {
`saveable`: this.canSave,
`modified`: !this.isUnchanged,
`special`: this.isSpecial
};
}
複製程式碼
- b.通過繫結
NgStyle
設定多個內聯樣式。NgStyle
需要繫結到一個key:value
控制物件。
src/app/app.component.ts
currentStyles: {};
setCurrentStyles() {
// CSS styles: set per current state of component properties
this.currentStyles = {
`font-style`: this.canSave ? `italic` : `normal`,
`font-weight`: !this.isUnchanged ? `bold` : `normal`,
`font-size`: this.isSpecial ? `24px` : `12px`
};
}
複製程式碼
src/app/app.component.html
<div [ngStyle]="currentStyles">
This div is initially italic, normal weight, and extra large (24px).
</div>
複製程式碼
- 模板輸入變數和模板引用變數
- 模板輸入變數
hero
前的let
關鍵字建立了一個名叫hero
的模板輸入變數。
這個變數的範圍被限制在所重複模板的單一例項上。事實上,你可以在其它內建結構型指令中使用同樣的變數名。 - 模板引用變數
#phone
的意思就是宣告一個名叫phone
的變數來引用<input>
元素。
模板引用變數的作用範圍是整個模板。不要在同一個模板中多次定義同一個變數名,否則它在執行期間的值是無法確定的。
<input #phone placeholder="phone number">
複製程式碼
- 你總是可以在”元件自己的模板”中繫結到元件的公共屬性,而不用管它們是否輸入(
Input
)屬性或輸出(Output
)屬性。
但Angular
需要@Input()
和@Output()
裝飾器來標記出那些允許被外部元件繫結到的屬性。
宣告輸入與輸出屬性:
@Input() hero: Hero;
@Output() deleteRequest = new EventEmitter<Hero>();
複製程式碼
另外:@Input get/set
寫法
_oprType: string =`VIEW`;
@Input("oprType")
get oprType(){
retrun this._oprType;
}
set oprType(oprType){
this._oprType = oprType;
...//其他邏輯處理
}
複製程式碼
json
管道對除錯繫結特別有用:
src/app/app.component.html (pipes-json)
<div>{{currentHero | json}}</div>
複製程式碼
它生成的輸出是這樣的:
{ "id": 0, "name": "Hercules", "emotion": "happy",
"birthdate": "1970-02-25T08:00:00.000Z",
"url": "http://www.imdb.com/title/tt0065832/",
"rate": 325 }
複製程式碼
- 生命週期
- 注意:建構函式在所有的生命週期鉤子之前執行
ngOnChanges()-》ngOnInit()-》ngDoCheck()-》ngAfterContentInit()-》ngAfterContentChecked()-》ngAfterViewInit()-》ngAfterViewChecked()-》ngOnDestroy()
ngOnInit
生命週期鉤子會在 DOM
更新操作執行前觸發
rxjs
知識
var subject = new Subject<string>();
subject.next(1);
subject.subscribe({
next: (v) => console.log(`observerA: ` + v)
});
//observerA: 1
複製程式碼
-
純(
pure
)管道與非純(impure
)管道
預設情況下,管道都是純的。
Angular
只有在它檢測到輸入值發生了純變更時才會執行純管道。
純變更:是指對原始型別值(String、Number、Boolean、Symbol
)的更改,或者對物件引用(Date、Array、Function、Object
)的更改。
Angular
會忽略物件內部的更改。 如果你更改了輸入日期(Date
)中的月份、往一個輸入陣列(Array
)中新增新值或者更新了一個輸入物件(Object
)的屬性,Angular
都不會呼叫純管道。
Angular
會在每個元件的變更檢測週期中執行非純管道。 非純管道可能會被呼叫很多次,和每個按鍵或每次滑鼠移動一樣頻繁。 -
ElementRef
import { Directive, ElementRef } from `@angular/core`;
@Directive({
selector: `[appHighlight]`
})
export class HighlightDirective {
constructor(el: ElementRef) {
el.nativeElement.style.backgroundColor = `yellow`;
}
}
複製程式碼
import
語句還從 Angular
的 core
庫中匯入了一個 ElementRef
符號。
你可以在指令的建構函式中注入 ElementRef
,來引用宿主 DOM
元素。
ElementRef
(對檢視中某個宿主元素的引用)通過其 nativeElement
屬性給了你直接訪問宿主 DOM
元素的能力。
- 注意:允許直接訪問
DOM
會導致你的應用在XSS
攻擊前面更加脆弱。當需要直接訪問
當需要直接訪問DOM
時,請把本API
作為最後選擇。
優先使用 Angular
提供的模板和資料繫結機制。
或者你還可以看看 Renderer2
(實現自定義渲染器),它提供了可安全使用的 API
—— 即使環境沒有提供直接訪問原生元素的功能。
import { Directive, ElementRef, Renderer2 } from `@angular/core`;
@Directive({
selector: `[appHighlight]`
})
export class HighlightDirective {
constructor(el: ElementRef, private renderer: Renderer2) {
//el.nativeElement.style.backgroundColor = `yellow`;
this.renderer.setStyle(this.el.nativeElement, "backgroundColor", `yellow`);
}
}
複製程式碼
HostListener
把一個事件繫結到一個宿主監聽器,並提供配置後設資料。
@HostListener(`mousedown`) onMouseEnter() {
this.highlight(this.highlightColor || this.defaultColor || `red`);
}
複製程式碼
mousedown
要監聽的事件。
當mousedown
事件發生時,Angular
就會執行所提供的處理器方法onMouseEnter
。
- 你可以根據屬性名在繫結中出現的位置來判定是否要加
@Input
。
當它出現在等號右側的模板表示式中時,它屬於模板所在的元件,不需要@Input
裝飾器。
當它出現在等號左邊的方括號([ ])
中時,該屬性屬於其它元件或指令,它必須帶有@Input
裝飾器。
<p [appHighlight]="color">Highlight me!</p>
複製程式碼
color
屬性位於右側的繫結表示式中,它屬於模板所在的元件。 該模板和元件相互信任。因此 color
不需要 @Input
裝飾器。
appHighlight
屬性位於左側,它引用了 HighlightDirective
中一個帶別名的屬性,它不是模板所屬元件的一部分,因此存在信任問題。 所以,該屬性必須帶 @Input
裝飾器。
bootstrap
—— 根元件,Angular
建立它並插入index.html
宿主頁面。- 結構型指令中,星號
(*)
寫法是個語法糖,Angular
會把它解開成一個<ng-template>
標記,包裹著宿主元素及其子元素。
Angular
會在真正渲染的時候填充<ng-template>
的內容,並且把<ng-template>
替換為一個供診斷用的註釋。
<ng-template let-hero="hero">
<div *ngIf="hero" [class.odd]="odd">({{i}}) {{hero.name}}</div>
</ng-template>
複製程式碼
- 注意:如果沒有使用結構型指令,而僅僅把一些別的元素包裝進
<ng-template>
中,那些元素就是不可見的(被註釋掉了)。
- 每個宿主元素上只能有一個結構型指令。比如:
*ngFor
和*ngIf
不能放在同一個宿主元素上。
有一個簡單的解決方案:把*ngFor
放在一個”容器”元素上,再包裝進*ngIf
元素。 這個元素可以使用ng-container
,以免引入一個新的HTML
層級。
<ul>
<ng-container *ngFor="let menu of menus">
<li *ngIf="!menu.children?.length">
</li>
</ng-container>
</ul>
複製程式碼
Angular
的 <ng-container>
是一個分組元素,但它不會汙染樣式或元素佈局,因為 Angular
壓根不會把它放進 DOM
中。
template: <ng-template #toolbarTemplate [ngTemplateOutletContext]="{ $implicit: this}"></ng-template>
class: @ViewChild("toolbarTemplate") toolbarTemplate: TemplateRef<any>
簡化寫法:
<ng-template [ngTemplateOutlet]="toolbarTemplate || defaultToolbarTemplate"
[ngTemplateOutletContext]="{ $implicit: this}"></ng-template>
複製程式碼
該指令用於基於已有的 TemplateRef 物件,插入對應的內嵌檢視。在應用 NgTemplateOutlet 指令時,我們可以通過 [ngTemplateOutletContext] 屬性來設定 EmbeddedViewRef 的上下文物件。繫結的上下文應該是一個物件,此外可通過 let語法來宣告繫結上下文物件屬性名。
angular6.x中ngTemplateOutlet指令的使用示例
-
表示式中的上下文變數是由”模板引用變數
#aa
“、”模板輸入變數let bb
“和”元件的成員cc
“疊加而成的。
優先順序:模板輸入變數>模板引用變數>指令的上下文變數>元件類的例項 -
模板表示式不能引用全域性名稱空間中的任何東西,比如
window
和document
。它們也不能呼叫console.log
或Math.max
。它們只能引用表示式上下文中的內容。 -
宣告式元件和入口元件:
宣告式元件會在模板中通過元件宣告的selector
(比如<cmb></cmb>
)載入元件。
入口元件entryComponents
主要有3類:
- 在
@NgModule
中的bootstrap
宣告的根元件 - 路由配置的元件
- 動態元件
exports
在模組a
的ngModule
中exports
出一組元件、指令和管道
-》模板b
匯入了模組a
-》模組b
下的所有元件的模板,都可以使用模組a
的ngModule
中exports
出的元件、指令和管道- 父子元件互動
<div class="seconds">{{timer.seconds}}</div>
<app-countdown-timer #timer></app-countdown-timer>
複製程式碼
這個本地變數方法#timer
是個簡單便利的方法。但是它也有侷限性,因為父元件-子元件的連線必須全部在父元件的html
模板中進行。父元件本身的ts
程式碼對子元件沒有訪問權。
當父元件類需要這種訪問時,可以把子元件作為 ViewChild
,注入到父元件裡面。
- 內容投影
ng-content
父元件頁面parent.html
<attchment-upload>abcde</attchment-upload>
複製程式碼
被父元件包含的子元件標籤頁面attchment-upload.html
<div>-- begins --</div>
<ng-content></ng-content>
<div>-- ends --</div>`
複製程式碼
輸出:
-- begins --
abcde
-- ends --
複製程式碼
以上<ng-content>
標籤是父元件頁面中外來內容的佔位符。 它指明在子元件標籤頁面的哪裡插入這些父元件的外來內容abcde
。
注意:不要在元件標籤的內部放任何內容 —— 除非你想把這些內容投影進這個元件中。
- NgTemplateOutlet基於已有的TemplateRef 物件,插入對應的內嵌檢視。
Angular 4.x NgTemplateOutlet
<ng-container *ngTemplateOutlet="greet"></ng-container>
<ng-template #greet><span>Hello</span></ng-template>
複製程式碼
或者
<ng-container *ngTemplateOutlet="contentTemplate"></ng-container>
contentTemplate:TemplateRef<any>
this.contentTemplate=...
//TemplateRef就是對應html中的ng-template標籤
複製程式碼
ngIf
寫法
<ng-container *ngIf="a.length==0; else elseTemplate">
...
</ng=container>
<ng-template #elseTemplate>
...
</ng-template>
複製程式碼
30.ngTemplateOutletContext
<ng-template let-rowData let-rowIndex="rowIndex" let-columns="columns">
<tr>
<td *ngFor="let col of columns;">
// 通過 [ngTemplateOutletContext] 屬性來設定 EmbeddedViewRef:commonBodyTemplate的上下文物件
<ng-container *ngTemplateOutlet="commonBodyTemplate;context:{$implicit:col,rowIndex:rowIndex,rowData:rowData,columns:columns}"></ng-container>
</td>
</tr>
</ng-template>
//若 let 語法未繫結任何屬性名(let-col),則上下文物件中 $implicit 屬性,對應的值col將作為預設值。
<ng-template #commonBodyTemplate let-col let-rowIndex="rowIndex" let-rowData="rowData">
<ng-container *ngIf="col.field==`idx`">
{{rowIndex+1}}
</ng-container>
<ng-container *ngIf="col.field==`attachSize`">
{{(rowData[col.field]||"--")+(rowData["attachSizeUnit"]||"")}}
</ng-container>
<ng-container *ngIf="editable;else elseTemplate">
<ng-container *ngIf="col.field==`fileName`">
<ng-container *ngIf="rowData[`attachmentId`]">
<a [href]="attachUrls[rowData[`attachmentId`]]" [download]="rowData[col.field]">{{rowData[col.field]||""}}</a>
</ng-container>
</ng-container>
<ng-container *ngIf="[`idx`,`attachSize`,`fileName`].indexOf(col.field)==-1">
{{rowData[col.field]||"--"}}
</ng-container>
</ng-container>
<ng-template #elseTemplate>
<ng-container *ngIf="[`idx`,`attachSize`].indexOf(col.field)==-1">
{{rowData[col.field]||"--"}}
</ng-container>
</ng-template>
</ng-template>
複製程式碼
在父頁面中嵌入<app-attachment></app-attachment>
報錯:"app-attachment" is not a unknown element.
原因:app-attachment
子頁面模板對應的ts
元件沒有包括在父頁面所在的module
中。
32.@Input(`companyId`) companyId: string;
子元件的初始化constructor
取不到companyId
值,ngOnInit()
可以
所以初始化放在ngOnInit
中。
33.響應式表單
this.formGroup=fb.group({
contractid:["123",[Validators.required]],
});
this.theDetailGroup.get("finProductName").setValue(this.finProductName);
this.formGroup.get("docmentId").valueChanges.subscribe(value=>{
if(this.existList.findIndex(item=>item.docmentId==value)>-1){
this.formGroup.get("docmentId").setErrors({duplicate:true});
}
});
this.formGroup.get("docmentId").value;
複製程式碼
34.this.fb.group({})
響應式表單控制元件繫結,應該在生命週期之前,即constructor
建構函式中初始化。因為如果放在ngOnInit
中繫結,在表單初始化前對錶單進行賦值操作,會報錯。
35.自定義模組中exports
使用
將附件宣告到公用模組
import { NgModule } from `@angular/core`;
import { CommonModule} from `@angular/common`;
import { AttachmentComponent } from `./attachment.component`;
@NgModule({
imports: [
CommonModule,
],
exports:[
CommonModule,
AttachmentComponent
],
providers:[],
declarations: [
AttachmentComponent
],
entryComponents:[
AttachmentComponent
]
})
export class SharedModule { }
複製程式碼
宿主元件所在的module
中引入公共模組
import { NgModule } from `@angular/core`;
import { CommonModule } from `@angular/common`;
import { SharedModule } from `src/app/shared`;
@NgModule({
declarations: [
],
imports: [
CommonModule,
SharedModule
],
entryComponents:[]
})
export class MineModule { }
複製程式碼
36.自定義雙向資料繫結
- 父元件
<app-base [(companyId)]="companyId"></app-base>
複製程式碼
- 子元件
app-base.ts
_companyId = ``;
@Output() companyIdChange = new EventEmitter();
@Input(`companyId`)
get companyId() {
return this._companyId;
}
set companyId(companyId) {
this._companyId = companyId;
this.companyIdChange.emit(companyId);
}
複製程式碼
注意:屬性名 + Change
是雙向繫結中Output
的固定寫法