本文章宗旨在於快速開始angular開發,或者快速瞭解angular開發注意事項的文章
環境搭建
- 腳手架安裝:npm i -g @angular/cli
- 新建專案:ng new my-app
如果安裝腳手架報錯,強制清理npm快取後重新安裝
元件與模板
當你下載好官方案列後,你可能對目錄都不太熟悉,先不要將關注點放在這裡,文章致力於如何快速上手一個angular專案。
理解模板表示式上下文
表示式中的上下文變數是由以下三種組成:
- 模板變數 (模板的 $event 物件、模板輸入變數 (let hero)和模板引用變數(#heroForm) )
- 指令的上下文變數(指令中的屬性)
- 元件的成員變數(元件實列)
當存在相同的表示式變數優先順序:模板變數>>指令的上下文變數>>元件的成員變數
import { Component } from '@angular/core';
//my-app/src/app/app.component.ts
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
private data0:Number = 1121;
data1 = '<div>dddddd</div>';
data2 = {
aaa:222
};
data3(){
};
data4 = null;
data5 = undefined;
data6 = [1,2,3]
}
複製程式碼
<div>
<div>data0 :{{data0}}</div>
<div>data1 :{{data1}}</div>
<div>data2 :{{data2}}</div>
<div>data3 :{{data3}}</div>
<div>data4 :{{data4}}</div>
<div>data5 :{{data5}}</div>
<div>data6 :{{data6}}</div>
<div>data7 :{{data7}}</div>
</div>
<!--
data0 :1121
data1 :<div>dddddd</div>
data2 :[object Object]
data3 :function () { }
data4 :
data5 :
data6 :1,2,3
data7 :
-->
複製程式碼
理解HTML attribute 與 DOM property
先來看一個列子
//html:<input type="text" value="a">
var outPutVal = function(){
console.log('getAttribute:',inO.getAttribute('value'));
console.log('inO.value:',inO.value);
}
window.onload = function(){
var inO = document.querySelect('input');
outPutVal(inO);
//getAttribute: a
//inO.value: a
document.onclick = function(){
//<input type="text" value="a"> 手動輸入value為aaaaa後列印
outPutVal(inO);
//getAttribute: a
//inO.value: aaaaa
}
}
複製程式碼
以上原生js展示了HTML attribute 與 DOM property 的區別:
- 少量 HTML attribute 和 property 之間有著 1:1 的對映,如 id。
- 有些 HTML attribute 沒有對應的 property,如 colspan。
- 有些 DOM property 沒有對應的 attribute,如 textContent。
angular中模板繫結是通過 property 和事件來工作的,而不是 attribute
特殊的attribute 繫結
[attr.aria-label]="actionName"
<td [attr.colspan]="1 + 1">
複製程式碼
指令
指令分為三種:
- 元件 — 擁有模板的指令
- 結構型指令 — 通過新增和移除 DOM 元素改變 DOM 佈局的指令
- 屬性型指令 — 改變元素、元件或其它指令的外觀和行為的指令。
屬性指令
- ngClass
- ngStyle
- ngModel
結構型指令
- ngIf
- ngFor
- ngSwitch
ng-template *號語法
<div *ngIf="hero" class="name">{{hero.name}}</div>
-----------------------------------
<ng-template [ngIf]="hero">
<div class="name">{{hero.name}}</div>
</ng-template>
複製程式碼
<div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById" [class.odd]="odd">
({{i}}) {{hero.name}}
</div>
-----------------------------------
<ng-template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd" [ngForTrackBy]="trackById">
<div [class.odd]="odd">({{i}}) {{hero.name}}</div>
</ng-template>
複製程式碼
在渲染檢視之前,Angular 會把 < ng-template > 及其包裹內容替換為一個註釋
ng-container
會將兄弟元素歸為一組並且ng-template 會無副作用的包裹內部元素
無副作用是什麼意思?(舉個例子:破壞了html結構!):
<p>
I turned the corner
<span *ngIf="hero">
and saw {{hero.name}}. I waved
</span>
and continued on my way.
</p>
---------------------------
<p>
I turned the corner
<ng-container *ngIf="hero">
and saw {{hero.name}}. I waved
</ng-container>
and continued on my way.
</p>
複製程式碼
元件--特殊的指令(擁有模板的指令)
ng g c components/A 建立元件
複製程式碼
元件通訊的幾種方式
1.輸入屬性 @Input()(父元件傳遞資料給子元件)
//a.component.ts
@Input()
inAttr:String;
private _name:String = '';
----------------------------------------------------
@Input()
set inAttr2(name:String){
this._name = name;
}
get inAttr2():String{
return this._name;
}
複製程式碼
2.輸出屬性 @Output()(子元件傳遞資料給父元件)
//子元件中
@Output()
myEvent:EventEmitter<DataType> = new EventEmitter();
this.myEvent.emit(Data);
//父元件中
(myEvent)="myHandleEvent($event)"
myHandleEvent(Data:DataType){
}
複製程式碼
3.中間人模式(兄弟元件通過公共的父元件傳值)
4.父元件獲得子元件引用 #child(只可以在元件模板中使用)
5. @ViewChild()父元件類插入子元件 (在元件類中使用)
- 被注入的子元件只有在 Angular 顯示了父元件檢視之後才能訪問(ngAfterViewInit生命週期中訪問)
- Angular 的單向資料流規則會阻止在同一個週期內更新父元件檢視。應用在顯示秒數之前會被迫再等一輪。
- 解決方法:使用 setTimeout() 來更改資料
@ViewChild(ChildComponent)
private childComponent: ChildComponent;
ngAfterViewInit() {
setTimeout(() => this.seconds = () => this.childComponent.changeData, 0);
}
複製程式碼
6.通過服務更改資料(任意元件結構中使用)
自定義指令
ng g d myDirective/demo
複製程式碼
- 屬性型指令
ElementRef:對檢視中某個原生元素的包裝器。
import { Directive, ElementRef, HostListener, Input } from '@angular/core';
@Directive({
selector: '[appDemo]'
})
export class DemoDirective {
//建構函式要宣告需要注入的元素 el: ElementRef。
constructor(private el:ElementRef) { }
// 註冊事件
@HostListener('click')
show(){
console.log(this.el.nativeElement);
console.log(this.ss);
}
//指令引數,當引數名與指令名相同時,可以直接賦值給指令
@Input()
ss:String = 'aaa';
}
複製程式碼
<button appDemo [ss]="bbb">click me</button>
複製程式碼
- 結構型指令
TemplateRef:取得 < ng-template > 的內容
ViewContainerRef: 訪問檢視容器
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
@Directive({ selector: '[appUnless]'})
export class UnlessDirective {
//第一次傳入true時不執行任何if分支,提升效能
private hasView = false;
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef) { }
@Input() set appUnless(condition: boolean) {
if (!condition && !this.hasView) {
//實列化一個試圖,並把它插入到該容器中
this.viewContainer.createEmbeddedView(this.templateRef);
this.hasView = true;
} else if (condition && this.hasView) {
this.viewContainer.clear();
this.hasView = false;
}
}
}
複製程式碼
管道
你可以將管道理解成將資料處理之後再顯示的一種操作符,如:
<p>{{ birthday}}</p>
<p>{{ birthday | date }}</p>
<!--鏈式呼叫管道-->
<p>{{ birthday | date | uppercase}}</p>
複製程式碼
angular內建的管道:
DatePipe、UpperCasePipe、LowerCasePipe、CurrencyPipe 和 PercentPipe.......
自定義管道
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({name: 'exponentialStrength'})
export class ExponentialStrengthPipe implements PipeTransform {
transform(value: number, exponent: string): number {
let exp = parseFloat(exponent);
return Math.pow(value, isNaN(exp) ? 1 : exp);
}
}
複製程式碼
純(pure)管道與非純(impure)管道
-
Angular 只有在它檢測到輸入值發生了純變更時才會執行純管道。 純變更是指對原始型別值(String、Number、Boolean、Symbol)的更改, 或者對物件引用(Date、Array、Function、Object)的更改。
-
Angular 會在每個元件的變更檢測週期中執行非純管道。 非純管道可能會被呼叫很多次,和每個按鍵或每次滑鼠移動一樣頻繁。
@Pipe({
name: 'flyingHeroesImpure',
pure: false
})
export class FlyingHeroesImpurePipe extends FlyingHeroesPipe {}
複製程式碼
依賴注入
依賴注入是實現控制反轉的一種方式,將物件的創造權交出去,它的好處可以在實際開發中體現出來,以下會介紹它
angular實現控制反轉的手段就是依賴注入
依賴注入的好處:
依賴注入會讓你用一種鬆耦合的方式去寫程式碼,易於除錯:
舉個例子:
當你的服務分為開發版本與線上版本時,你可能需要兩個不同的服務devDataSevice與proDataSevice, 當你不使用依賴注入時,你需要在多個元件中new 出這兩個物件來使用這兩個服務,線上上環境與測試環境之間切換時,你需要更改多個元件中的程式碼,如果使用依賴注入的形式,你只需要更改提供器中的程式碼就可以更改所有元件中的服務,這大大降低了程式碼的耦合程度並且提高了可維護性。
首先建立一個服務
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class HeroService {
constructor() { }
}
複製程式碼
服務提供商(注入器)
在哪裡書寫服務提供商:
-
在服務本身的 @Injectable() 裝飾器中的 providedIn 的後設資料選項
providedIn的值可以是'root'或者某個特定的NgModule
-
在 NgModule 的 @NgModule() 裝飾器中的providers後設資料選項
-
在元件的 @Component() 裝飾器中providers後設資料選項
-
在指令的 @Directive() 裝飾器中providers後設資料選項(元素級注入器)
providers中如何書寫:
provider:[ProductService]
provider:[{provide:ProductService,useClass:ProductService}]
provider:[{provide:ProductService,useClass:AnotherProductService}]
provider:[{provide:ProductService,useFactory:(引數A)=>{return ob},deps:[引數A]}]
provider:[{provide:"IS_DEV_ENV",useValue:{isDev:true}}]
複製程式碼
注入器冒泡
- Angular 會嘗試尋找該元件自己的注入器
- 如果該元件的注入器沒有找到對應的提供商,它就去尋找它父元件的注入器直到 Angular 找到一個合法的注入器或者超出了元件樹中的祖先位置為止。
- 如果超出了元件樹中的祖先還未找到,Angular 就會丟擲一個錯誤。
注入服務:
在建構函式中注入:
construct(private productService:ProductService){...};
複製程式碼
- @Optional()是對productService的裝飾器,當找不到服務的提供商時,引數值設為null
- @Host() 裝飾器會禁止在宿主元件以上的搜尋。宿主元件通常就是請求該依賴的那個元件。 不過,當該元件投影進某個父元件時,那個父元件就會變成宿主。
- @Inject()自定義提供商
- @Self() 和 @SkipSelf() 來修改提供商的搜尋方式
@angular/router路由
- 設定base標籤,告訴路由該如何合成導航
<!--index.html中的head標籤中加入<base href="/">來告訴路由該如何合成導航用的 URL-->
<base href="/">
複製程式碼
- 匯入路由配置
//app.module.ts
//匯入路由核心模組
import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [
{path:'**',component:AComponent}
];
@NgModule({
...
imports: [RouterModule.forRoot(routes)]
...
})
export class AppModule { }
複製程式碼
Routes路由配置介紹
-
path "**"表示匹配所有
-
redirectTo "表示要轉走的路徑"
-
pathMatch "full"表示匹配程度
-
component 表示要顯示的元件
-
data 資料傳遞
-
children:[] 子路由
-
canActivate:[PermissionGuard]
-
canDeactivate:[FocusGuard]
-
resolve:{Stock:StockResolve}
-
outlet 輔助路由
-
設定導航輸出位置
<router-outlet></router-outlet>
<!-- Routed components go here -->
複製程式碼
路由跳轉
- 宣告式跳轉
<a [routerLink]="['/path']" routerLinkActive="active">routelink</a>
<a [routerLink]="['./path']">routelink</a>
<a [routerLink]="['{outlets:{aux:'aaa'}}']">routelink</a> 輔助路由
http://localhost:4200/a(aux:aaa)
複製程式碼
- 命令式跳轉
Router可呼叫navigate與navigateByUrl()
路由資料傳遞:
//1.
[routerLink] = "['/path',1]"
//http://localhost:4200/path/1
// this.routeInfo.snapshot.queryParams
//2.
[routerLink]="['/b',1]" [queryParams]="{id:3}"
// http://localhost:4200/b/1?id=3
// this.routeInfo.snapshot.params
// 3.
{path:'a',component:AComponent,data:{id:666}}
//this.routeInfo.snapshot.queryParams
//this.routeInfo.snapshot.data
複製程式碼
ActivatedRoute 常用:this.routeInfo見上面
守衛路由
1.canActivate
export class PermissionGuard implements CanActivate{
canActivate(){
let hasPemission:boolean = Math.random() < 0.5;
return hasPemission;
}
}
複製程式碼
2.canDeactivate
export class FocusGuard implements CanDeactivate<CComponent>{
canDeactivate(component:CComponent){
if(component.isFoucs){
return true;
}else {
return confirm('不關注一下嘛?');
}
}
}
複製程式碼
3.resolve 讀取資料前
@Injectable()
export class StockResolve implements Resolve<Stock>{
constructor(
private route:Router
){}
resolve(route:ActivatedRouteSnapshot,state:RouterStateSnapshot){
return new Stock(1,'name');
}
}
複製程式碼
生命週期鉤子
當 Angular的 元件或指令, 新建、更新和銷燬時所觸發的鉤子函式
鉤子函式的執行順序
- constructor 這個不是生命週期鉤子,但是它一定是最先執行的
- ngOnChanges 輸入屬性被賦值時呼叫 (不可變物件的改變)
- ngOnInit 第一次顯示資料繫結和設定指令/元件的輸入屬性之後
- ngDoCheck 在每個變更檢測週期中,緊跟在 ngOnChanges() 和 ngOnInit() 後面呼叫
- ngAfterContentInit 外部內容投影進元件/指令的檢視之後呼叫
- ngAfterContentChecked 投影元件內容的變更檢測之後呼叫
- ngAfterViewInit 初始化完元件檢視及其子檢視之後呼叫
- ngAfterViewChecked 元件檢視和子檢視的變更檢測之後呼叫
- ngOnDestroy 次銷燬指令/元件之前呼叫
初始化階段
//1.constructor
//2.ngOnChanges
//3.ngOnInit
//4.ngDoCheck
//5.ngAfterContentInit
//6.ngAfterContentChecked
//7.ngAfterViewInit
//8.ngAfterViewChecked
複製程式碼
變化階段
//1.ngOnChanges
//2.ngDoCheck
//3.ngAfterContentChecked
//4.ngAfterViewChecked
複製程式碼
元件銷燬階段
//1.ngOnDestroy
// 在路由變更時改變
複製程式碼
ngAfterViewInit,ngAfterViewChecked
- 子元件組裝好父元件才會組裝
- 元件是在試圖組裝完畢呼叫
- 再此方法中不可以更改檢視資料
ngAfterContentInit,ngAfterContentChecked,投影
1.子元件
<div>
<ng-content></ng-content>
</div>
2.父元件
<SComponent>
<!--在此寫入的東西會投影到子元件-->
</SComponent>
複製程式碼
表單
- ReactiveFormsModule響應式表單
- FormsModule模板式表單
模板式表單
- 在angular中會自動加上ngForm來處理表單,如果不用angular來處理則加上ngNoForm
- 在表單中加上#myForm="ngForm",則可以在頁面使用{{myForm.value | json}}去檢測表單中有ngModule的value 物件名為name值
NgForm 對應 FormGroup
ngModel 對應 FormControl
ngModelGroup 對應 FormArray
<form #myForm="ngForm" action="/regist" (ngSubmit)="createUser(myForm.value)" method="post">
<div>
<input ngModel name="a" type="text" required pattern="[a-zA-Z0-9]+">
</div>
<div>
second:<input ngModel #second="ngModel" name="a" type="text" required pattern="[a-zA-Z0-9]+">
</div>
<div>
<input ngModel name="b" type="text" required pattern="[a-zA-Z0-9]+">
</div>
<div ngModelGroup="tow">
<input ngModel name="a" type="text">
<input ngModel name="b" type="text">
</div>
<button type="submit">提交</button>
</form>
<div>
{{myForm.value | json}}
<br>
second值是:{{second.value}}
</div>
複製程式碼
響應式表單
private nickName = new FormControl('tom');
private passwordInfo = new FormGroup({
password: new FormControl(),
passwordConfirm:new FormControl()
});
private email = new FormArray([
new FormControl('a@a.com'),
new FormControl('b@b.com')
]);
複製程式碼
FormControl
管理單體表單控制元件的值和有效性狀態
FormGroup
管理一組 AbstractControl 例項的值和有效性狀態
FormArray
管理一組 AbstractControl 例項的值和有效性狀態
<form [formGroup]="formModel" action="/regist" (Submit)="createUser()" method="post">
<input formControlName="nikname">
<ul formArrayName="emails">
<li *ngFor="let email of formModel.get('emails').controls;let i = index;">
<input [formControlName]="i">
</li>
</ul>
<button >增加email.....</button>
<input formControlName="emails">
<div formGroupName="passwordInfo">
<input formControlName="password">
<input formControlName="passwordConfirm">
</div>
</form>
複製程式碼
FormBuilder快捷語法
private formModel:FormGroup;
private fb:FormBuilder = new FormBuilder();
/*this.formModel = new FormGroup({
nikname:new FormControl(),
emails:new FormArray([
new FormControl()
]),
mobile:new FormControl(),
passwordInfo:new FormGroup({
password:new FormControl(),
passwordConfirm:new FormControl()
})
});*/
this.formModel = this.fb.group({
nikname:[''],
emails:this.fb.array([
['']
]),
mobile:[''],
passwordInfo:this.fb.group({
password:[''],
passwordConfirm:['']
})
});
複製程式碼
angular表單 校驗器
//自定義校驗器
xxx(param:AbstractControl):{[key:string]:any}{
return null;
}
// eg:
moblieValid(moblie:FormControl):any{
..........
// return null;表示成功
// return {...};不成功
}
//預定義校驗器
Validators.required ......
nikname:["xxxx",[Validators.required,.....]]
.....................
let niknameValid;boolean = this.formModel.get('nikname').valid;
passwordInfo:this.fb.group({
password:[''],
passwordConfirm:['']
},{validator:this.passwordValidator}) //一次校驗多個欄位
複製程式碼
顯示錯誤資訊
<div [hidden]="!formModel.hasError('required','nickname')"></div>
複製程式碼