angular的裝飾器
類裝飾器(class decorator)
裝飾器會出現在類定義的緊前方,用來聲明該類具有指定的類型,並且提供適合該類型的元數據。
可以用下列裝飾器來聲明Angular的類:
- @Component()
- @Directive()
- @Pipe()
- @Injectable()
- @NgModule()
類字段裝飾器(class field decorator)
出現在類定義中屬性緊前方的裝飾器語句,用來聲明該字段的類型。比如@Input 和 @Output
生命周期鈎子
每個組件都有一個被Angular管理的生命周期
組件生命周期鈎子概覽
指令和組件的實例有一個生命周期:當Angular新建、更新和銷毀它們時觸發。
每個接口都有唯一的一個鈎子方法,它們的名字是由接口名再加上ng前綴構成的。比如,OnInit接口的鈎子方法叫做ngOnInit,Angular在創建組件后立刻調用它:
export class PeekABoo implements OnInit { constructor(private logger: LoggerService) { } // implement OnInit's `ngOnInit` method ngOnInit() { this.logIt(`OnInit`); } logIt(msg: string) { this.logger.log(`#${nextId++} ${msg}`); } }
沒有指令或者組件會實現所有這些接口,並且有些鈎子只對組件有意義。只有在指令/組件中定義過的那些鈎子方法才會被Angular調用。
生命周期的順序
當Angular使用構造函數新建一個組件或指令后,就會按下面的順序在特定時刻調用這些生命周期鈎子方法
ngOnChanges()
當Angular(重新)設置數據綁定輸入屬性時響應。該方法接受當前和上一屬性值的SimpleChanges對象
在ngOnInit()之前以及所綁定的一個或多個輸入屬性的值發生變化時都會調用。
ngOnInit()
在Angular第一次顯示數據綁定和設置指令/組件的輸入屬性之后,初始化指令/組件
在第一輪ngOnChanges()完成之后調用,只調用一次。
ngDoCheck()
檢測,並在發生Angular無法或不願意自己檢測的變化時做出反應
在每個變更檢測周期中,緊跟在ngOnChanges()和ngOnInit()后面調用。
ngAfterContentInit()
當Angular把外部內容投影進組件/指令的視圖之后調用
第一次ngDoCheck()之后調用,只調用一次。
ngAfterContentChecked()
每當Angular完成被投影組件內容的變更檢測之后調用
ngAfterContentInit()和每次ngDoCheck()之后調用
ngAfterViewInit()
當Angular初始化完組件視圖及其子視圖之后調用
第一次ngAfterContentChecked之后調用,只調用一次
ngAfterViewChecked()
每當Angular做完組件視圖和子視圖的變更檢測之后調用
ngAfterViewInit()和每次ngAfterContentChecked()之后調用
ngOndestroy()
每當Angular每次銷毀指令/組件之前調用並清掃。在這兒反訂閱可觀察對象和分離事件處理器,以防內存泄漏。
在Angular銷毀指令/組件之前調用。
在TypeScript指令類中添加接口是一項最佳實踐,它可以獲得強類型和IDE等編輯器帶來的好處。
Angular的其它子系統除了有這些組件鈎子外,還可能有它們自己的生命周期鈎子
Angular中的依賴注入
參考地址:https://angular.cn/guide/dependency-injection
依賴注入(DI)是一種重要的應用設計模式。Angular有自己的DI框架,在設計應用時常會用到它,以提升它們的開發效率和模塊化程度。
依賴,是當類需要執行其功能時,所需要的服務或對象。DI是一種編碼模式,其中的類會從外部源中請求獲取依賴,而不是自己創建它們。
這里有一個英雄管理的例子,省略……
這種方法在原型階段有用,但是不夠健壯、不利於維護。一旦你想要測試該組件或想要從遠程服務器獲得英雄列表,就不得不修改HeroesListComponent的實現,並且替換每一處使用了HEROES模擬數據的地方。
創建和注冊可注入的服務
DI框架讓你能從一個可注入的服務類(獨立文件)中為組件提供數據。為了演示,我們還會創建一個用來提供英雄列表的、可注入的服務類,並把它注冊為該服務的提供商。
@Injectable()是每個Angular服務定義中的基本要素。
用服務提供商配置注入器
我們創建的類提供了一個服務。@Injectable()裝飾器把它標記為可供注入的服務,不過在你使用該服務的provider提供商配置好的Angular的依賴注入器之前,Angular實際上無法將其注入到任何位置。
該注入器負責創建服務實例,並把它們注入到像HeroListComponent這樣的類中。你很少需要自己創建Angular的注入器。Angular會在執行應用時為你創建注入器,第一個注入器是根注入器,創建於啟動過程中。
提供商會告訴注入器如何創建該服務。要想讓注入器能夠創建服務(或提供其他類型的依賴),你必須使用某個提供商配置好注入器。
提供商可以是服務器本身,因此注入器可以使用new來創建實例。你還可以定義多個類,以不同的方式提供同一個服務,並使用不同的提供商來配置不同的注入器。
注入器是可繼承的,這意味着如果指定的注入器無法解析某個依賴,它就會請求父注入器來解析它。組件可以從它自己的注入器來獲取服務、從其祖先組件的注入器中獲取,從其父NgModule的注入器中獲取,或從root注入器中獲取。
你可以在三種位置之一設置元數據,以便在應用的不同層級使用提供商來配置注入器:
在服務本身的@Injectable()裝飾器中
在NgModule的@NgModule()裝飾器中
在組件的@Component()裝飾器中
@Injectable()裝飾器具有一個名叫providedIn的元數據選項,在那里你可以指定把被裝飾類的提供商放到root注入器中,或某個特定NgModule的注入器中。
@NgModule()和@Component()裝飾器都有用一個providers元數據選項,在那里你可以配置NgModule級或組件級的注入器。
所有組件都是指令,而providers選項是從@Directive()中繼承來的。你也可以與組件一樣的級別為指令、管道配置提供商。
注入服務
HeroListComponent要想從HeroService中獲取英雄列表,就得要求注入HeroService,而不是自己使用new來創建自己的HeroService實例。
你可以通過制定帶有依賴類型的構造函數參數來要求Angular在組件的構造函數中注入依賴項。
下面的代碼是HeroListComponent的構造函數,它要求注入HeroService。
當然,HeroListComponent還應該使用注入的這個HeroService做一些事情。
import { Component } from '@angular/core'; import { Hero } from './hero'; import { HeroService } from './hero.service' @Component({ selector: 'app-hero-list', template: ` <div *ngFor="let hero of heroes"> {{hero.id}} - {{hero.name}} ({{hero.isSecret ? 'secret' : 'public'}}) </div> ` }) export class HeroListComponent { heroes: Hero[]; constructor(heroService: HeroService) { this.heroes = heroService.getHeroes(); } }
對比一下沒有注入服務的代碼如下:
import { Component } from '@angular/core'; import { HEROES } from './mock-heroes'; @Component({ selector: 'app-hero-list', template: ` <div *ngFor="let hero of heroes"> {{hero.id}} - {{hero.name}} </div> ` }) export class HeroListComponent { heroes = HEROES; }
必須在某些父注入器中提供HeroService。
HeroListComponent並不關心HeroService來自哪里。如果你決定在AppModule中提供HeroService,也不必修改HeroListComponent.
注入器樹與服務實例
在某個注入器的范圍內,服務是單例的。也就是說,在指定的注入器中最多只有某個服務的最多一個實例。
應用只有一個根注入器。在root或AppModule級提供UserService意味着它注冊到了根注入器上。在整個應用中只有一個UserService實例,每個要求注入UserService的類都會得到這一個服務實例。除非你在子注入器中配置了另一個提供商。
Angular DI具有分層注入體系,這意味着下及注入器也可以創建它們自己的服務實例。Angular會有規律的創建下級注入器。每當Angular創建一個在@Component()中指定了providers的組件實例時,它也會為該實例創建一個新的子注入器。類似的,在運行期間加載一個新的NgModule時,Angular也可以為它創建一個擁有自己的提供商的注入器。
子模塊和組件注入器彼此獨立,並且會為所提供的服務分別創建自己的實例。當Angular銷毀NgModule或組件實例時,也會銷毀這些注入器以及注入器中的那些服務實例。
借助注入器繼承機制,你仍然可以把全應用級的服務注入到這些組件中。組件的注入器是其父組件注入器的子節點,它會繼承所有的祖先注入器,其終點則是應用的根注入器。Angular可以注入該繼承譜系中任何一個注入器提供的服務。
比如,Angular既可以把HeroComponent中提供的HeroService注入到HeroListComponent,也可以注入AppModule中提供的UserService
組件
顯示數據
https://angular.cn/guide/displaying-data
使用插值顯示組件屬性
import { Component } from '@angular/core'; @Component({ selector: 'hero-component', template: ` <h1>{{title}}</h1> <h3>我最喜歡的英雄是:{{myHero}}</h3> <p>英雄們:</p>` }) export class HeroComponent { title = 'Tour of Heroes'; myHero = '孫紅雷'; }
為數據創建一個類
(內容省略)
小結:
- 帶有雙花括號的插值來顯示一個組件屬性
- 用ngFor顯示數組
- 用一個TypeScript類來為你的組件描述模型數據並顯示模型的屬性
- 用ngIf根據一個布爾表達式有條件地顯示一段HTML
模板語法
表達式也可以引用模板中的上下文屬性,例如模板輸入變量,
let customer
,或模板引用變量 #customerInput
。
<label>Type something: <input #customerInput>{{customerInput.value}} </label>
表達式使用指南
當使用模板表達式時,請遵循下列指南:
- 非常簡單
- 執行迅速
- 沒有可見的副作用
簡單
雖然也可以寫復雜的模板表達式,不過最好避免那樣做。
屬性名或方法調用應該時常態,但偶然使用邏輯取反!也是可以的。其他情況下,應該把應用程序和業務邏輯限制在組件中,這樣它才能更容易開發和測試。
快速執行
Angular會在每個變更檢測周期后執行模板表達式。變更檢測周期會被多種異步活動觸發,比如Promise解析、HTTP結果、定時器時間、按鍵或鼠標移動。
表達式應該快速結束,否則用戶就會感到拖沓,特別是在較慢的設備上。當計算代價較高時,應該考慮緩存哪些從其他值計算得出的值。
沒有可見的副作用
模板表達式除了目標屬性的值以外,不應該改變應用的任何狀態。
這條規則是Angular“單向數據流”策略的基礎。永遠不用擔心讀取組件值可能改變另外的顯示值。在一次單獨的渲染過程中,視圖應該總是穩定的。
冪等的表達式是最理想的,因為它沒有副作用,並且可以提高Angular的變更檢測性能。用Angular術語來說,冪等表達式總會返回完全相同的東西,除非其依賴值之一發生了變化。
在單獨的一次事件循環中,被依賴的值不應該改變。如果冪等的表達式返回一個字符串或數字,連續調用它兩次,也應該返回相同的字符串或數字。如果冪等的表達式返回一個對象(包括Date或Array),連續調用它兩次,也應該返回同一個對象的引用。
對於*ngFor,這種行為有一個例外。*ngFor具有trackBy功能,在迭代對象時它可以處理對象的相等性。
模板語句
模板語句用來響應由綁定目標(如HTML元素、組件或指令)觸發的事件。模板語句將在事件綁定一節看到,它出現在=號右側的引號中,就像這樣:(event)="statement".
<button (click)="deleteHero()">Delete hero</button>
模板語句有副作用。這是事件處理的關鍵。因為你要根據用戶的輸入更新應用狀態。
響應事件是Angular中“單向數據流”的另一面。在一次事件循環中,可以隨意改變任何地方的任何東西。
和模板表達式一樣,模板語句使用的語言也像JavaScript。模板語句解析器和模板表達式解析器有所不同,特別之處在於它支持基本賦值(=)和表達式鏈(;和,).
語句上下文可以引用模板自身上下文中的屬性。在下面的例子中,就把模板的$event對象、模板輸入變量(let hero)和模板引用變量(#heroForm)傳給了組件中的一個事件處理器方法。
綁定語法:概覽
數據綁定是一種機制,用來協調用戶可見的內容,特別是應用數據的值。雖然也可以手動從HTML中推送或拉取這些值,但是如果將這些任務轉交給綁定框架,應用就會更易於編寫、閱讀和維護。您只需聲明數據源和目標HTML元素之間的綁定關系就可以了,框架會完成其余的工作。
Angular提供了多種數據綁定方法。綁定類型可以分為三類,按數據流的方向分為:
從數據源到視圖
綁定類型:插值、屬性、Attribute、CSS類、樣式
分類:單向,從數據源到視圖
語法:
{{expression}} [target]="expression" bind-target="expression"
從視圖到數據源
綁定類型:事件
分類:從視圖到數據源的單向綁定
語法:
(target)="statement"
on-target="statement"
雙向:視圖到數據源到視圖
分類:雙向
語法:
[(target)]="expression"
bindon-target="expression"
HTML attribute與DOM property的對比
理解HTML屬性和DOM屬性之間的區別是了解Angular綁定如何工作的關鍵。Attribute是由HTML定義的。Property是從DOM節點訪問的
一些HTML Attribute可以1:1映射到Property;例如“id”
某些HTML Attribute沒有相應的Property。例如,aria-*
某些DOM Property沒有相應的Attribute。例如,textContent
重要的是要記住,HTML Attribute和DOM Property是不同的,就算它們具有相同的名稱也是如此。在Angular中,HTML Attribute的唯一作用是初始化元素和指令的狀態
模板綁定使用的是Property和事件,而不是Attribute
編寫數據綁定時,您只是在和目標對象的DOM Property和事件打交道
該通用規則可以幫助您建立HTML Attribute和DOM Property的思維模型:屬性負責初始化DOM屬性,然后完工。Property值可以改變;Attribute值則不能。
此規則有一個例外。可以通過setAttribute()來更改Attribute,接着它會重新初始化相應的DOM屬性。
范例1:<input>
當瀏覽器渲染<input type="text" value="Sarah"> 時,它會創建一個對應的DOM節點,其value Property已初始化為“Sarah”
<input type="text" value="Sarah">
當用戶在<input>中輸入Sally時,DOM元素的value Property將變為Sally。但是,如果使用input.getAttribute('value')查看HTML的Attribute value,則可以看到該attribute保持不變——它返回了Sarah
范例2:禁用按鈕
disabled Attribute是另一個例子。按鈕的disabled Property默認為false,因此按鈕是啟用的。
當你添加disabled Attribute時,僅僅它的出現就將按鈕的disabled Property初始化成了true,因此該按鈕就被禁用了
<button disabled>測試按鈕</button>
添加和刪除disabled Attribute會禁用和啟用該按鈕。但是Attribute的值無關緊要,這就是為什么您不能通過編寫<button disabled="false">仍被禁用</button>來啟用此按鈕的原因。
要控制按鈕的狀態,請設置disabled Property
雖然技術上說你可以設置[attr.disabled]屬性綁定,但是它們的值是不同的,Property綁定要求一個布爾值,而其相應的Attribute綁定則取決於該值是否為null
<input [disabled]="condition ? true : false"> <input [attr.disabled]="condition ? 'disabled' : null">
Property綁定比Attribute綁定更值觀。
綁定類型與綁定目標
數據綁定的目標是DOM中的對象。根據綁定類型,該目標可以是Property名(元素、組件或指令的)、事件名(元素、組件或指令的),有時是Attribute名。
綁定類型:屬性
目標:元素的property,組件的property,指令的property
語法示例:
<div> <img [src]="itemImageUrl" width="120"> <app-hero-detail [hero]="currentHero"></app-hero-detail> <div [ngClass]="{'special': isSpecial}"></div> </div>
綁定類型:事件
目標:元素的事件、組件的事件、指令的事件
語法示例:
<div> <button (click)="onSave()">保存</button> <app-hero-detail (deleteRequest)="deleteHero()"></app-hero-detail> <div (myClick)="clicked=$event" clickable>點擊我呀</div> </div>
綁定類型:雙向綁定
目標:事件與property
語法示例:
<input [(ngModel)]="name">
綁定類型:Attribute
目標:attribute(例外情況)、
語法示例:
<button [attr.aria-label]="help">help</button>
綁定類型:CSS類
目標:class property
<div [class.special]="isSpecial">Special</div>
綁定類型:樣式
目標:style property
<button [style.color]="isSpecial ? 'res' : 'green'"></button>
Property綁定[property]
使用Property綁定到目標元素或指令@Input()裝飾器的set型屬性。
單向輸入
Property綁定的值在一個方向上流動,從組件的Property變為目標元素的Property.
您不能使用屬性綁定從目標元素讀取或拉取值。同樣的,您也不能使用屬性綁定在目標元素上調用方法。如果元素要引發事件,則可以使用事件綁定來監聽它們。
例子1:img元素的src Property
<img [src]="itemImageUrl">
例子2:綁定到colSpan Property
<tr> <td [colSpan]="2">Span 2 columns</td> </tr>
例子3:button disabled Property
<button [disabled]="isUnchanged">禁用按鈕</button>
例子4:設置指令的屬性
<p [ngClass]="classes">[ngClass] binding to the classes property making this blue</p>
例子5:設置自定義組件的模型屬性——這是一種父級和子級組件進行通信的好辦法
<app-item-detail [childItem]="parentItem"></app-item-detail>
綁定目標
包裹在方括號中的元素屬性名標記着目標屬性。下列代碼中的目標屬性是image元素的src屬性
<img [src]="itemImageUrl">
還有一種使用bind-前綴的替代方案
<img bind-src="itemImageUrl">
從技術上講,Angular將這個名稱與指令的@Input()進行匹配,它來自指令的inputs數組中列出的Property名城之一或是用@Input裝飾的屬性。這些輸入都映射到指令自身的屬性。
如果名字沒有匹配上已知指令或元素的屬性,Angular就會報告“未知指令”的錯誤
盡管目標名稱通常是Property的名稱,但是在Angular中,有幾個常見屬性會自動將Attribute映射為Property。這些包括class/className,innerHtmll/innerHTML和tabindex/tabIndex
消除副作用
模板表達的計算應該沒有明顯的副作用。表達式語言本身或您編寫模板表達式的方式在一定程度上有所幫助。您不能為屬性綁定表達式中的任何內容賦值,也不能使用遞增和遞減運算符。
最佳實踐是堅持使用屬性和返回值並避免副作用的方法。
返回正確的類型
模板表達式的計算結果應該是目標屬性所需要的值類型。如果target屬性需要一個字符串,則返回一個字符串;如果需要一個數字,則返回一個數字;如果需要一個對象,則返回一個對象,以此類推。
在下面的例子中,ItemDetailComponent的childItem屬性需要一個字符串,而這正是你要發送給屬性綁定的內容
<app-item-detail [childItem]="parentItem"></app-item-detail>
一次性字符串初始化
當滿足下列條件時,應該省略括號:
目標屬性接受字符串值
字符串是一個固定值,您可以直接將其放入模板中
這個初始值永不改變
attribute、class和style綁定
模板語法為那些不太適用使用屬性綁定的場景提供了專門的單向數據綁定形式。
要在運行中的應用查看Attribute綁定、類綁定和樣式綁定
attribute綁定
可以直接使用Attribute綁定設置Attribute的值。一般來說,綁定時設置的是目標的Property,而Attribute綁定是唯一的例外,它創建和設置的是Attribute
<button [attr.aria-label]="actionName">{{actionName}} with Aria</button>
類綁定
下面是在普通HTML中不用綁定來設置class Attribute的方法:
<div class="foo bar">Some text</div>
創建單個類的綁定
<div [class.foo]="hasFoo"></div>
輸入類型:boolean | undefined | null
輸入值范例:true, false
創建多個類的綁定
<div [class]="classExpr"></div>
輸入類型1:string
輸入值范例:"my-class-1 my-class-2 my-class-3"
輸入類型2:{[key: string]:boolean | undefined | null}
輸入值范例:{foo: true, bar: false}
盡管此技術適用於切換單個類名,但在需要同時管理多個類名時請考慮使用NgClass指令
樣式綁定
普通HTML設置style屬性:
<div style="color: blue;">一些文字</div>
創建單個樣式的綁定 [style.width]="width"
<div [style.color]="colorName">一些文字</div>
<div [style.width]="width">單一樣式綁定</div> <div [style.width.px]="width">帶單位的單一樣式綁定</div>
多個樣式綁定 [style]="styleExpr"
<div [style]="styleExpr"></div>
styleExpr的值可以是字符串、對象或數組
styleExpr = "width:100px;height:100px" styleExpr = { width: '100px', height: '100px' } styleExpr = ['width', '100px']
樣式的優先級規則
一個HTML元素可以把它的CSS類列表和樣式值綁定到多個來源(例如,來自多個指令的宿主host綁定)
當對同一個類名或樣式屬性存在多個綁定時,Angular會使用一組優先級規則來解決沖突,並確定最終哪些類或樣式會應用到該元素中
樣式的優先級(從高到低)
模板綁定
1.屬性綁定,例如:
<div [class.foo]="hasFoo"></div> <div [style.color]="color"></div>
2.Map綁定,例如:
<div [class]="classExpr"></div> <div [style]="styleExpr"></div>
3.靜態值,例如:
<div class="foo"></div> <div style="color: blue"></div>
指令宿主綁定
1.屬性綁定,例如:
host: {'[class.foo]': 'hasFoo'}
host: {'[style.color]': 'color'}
2.Map綁定,例如:
host: {'[class]': 'classExpr'}
host: {'[style]': 'styleExpr'}
3.靜態值,例如:
host: {'class': 'foo'}
host: {'style': 'color:blue'}
組件宿主綁定
1.屬性綁定,例如:
host: {'[class.foo]':'hasFoo'}
host: {'[style.color]': 'color'}
2.Map綁定,例如:
host: {'[class]': 'classExpr'}
host: {'[style]': 'styleExpr'}
3.靜態值,例如:
host: {'class': 'foo'}
host: {'style': 'color:blue'}
某個類或樣式綁定越具體,它的優先級就越高
對具體類(例如[class.foo])的綁定優先於一般化的[class]綁定,對具體樣式(例如[style.bar])的綁定優先於一般化的[style]綁定。
委托優先級較低的樣式
更高優先級的樣式可以使用undefined值委托給低級的優先級樣式。雖然把style屬性設置為null可以確保該樣式被移除,但把它設置為undefined會導致Angular回退到該樣式的次高優先級。
管道
服務
模板
組件傳值
dom元素操作