深入瞭解Angular(新手入門指南)

2022-12-06 22:04:01
本篇文章帶大家深入瞭解Angular,分享最全的新手入門指南,希望對大家有所幫助!

前端(vue)入門到精通課程,老師線上輔導:聯絡老師
Apipost = Postman + Swagger + Mock + Jmeter 超好用的API偵錯工具:

Angular概述

Angular 是谷歌開發的一款開源的 web 前端框架,基於 TypeScript 。【相關教學推薦:《》】

和 react 與 vue 相比, Angular 更適合中大型企業級專案。

Angular程式架構

在這裡插入圖片描述

Angular優勢

  • 可伸縮性:基於RxJS 、immutable.js和其他推播模型,能適應海量資料需求
  • 跨平臺:漸進式應用(高效能、離線使用、免安裝),原生(Ionic),桌面端
  • 生產率:模版(通過簡單而強大的模版語法,快速建立UI檢視),CLI(快速進入構建環節、新增元件和測試,然後立即部署)
  • 測試:單元測試(支援Karma、Jasmine等工具進行單元測試),端到端測試(支援Protractor等工具進行端到端測試)

@angular/cli腳手架

ng new 新建專案

  • ——routing 設定路由
  • ——style=css|scss|less 設定css樣式

ng serve 啟動專案

  • ——port 4200 埠號,預設4200
  • ——open 自動開啟瀏覽器

ng build 打包專案

  • ——aot 預編譯
  • ——prod 壓縮打包
  • ——base-href=/static/

ng generate 建立模組/元件/服務

  • module ——routing 建立模組
  • component 建立元件
  • service / 建立服務

檔案載入順序

main.ts => app.module.ts => app.component.ts => index.html => app.component.html

專案目錄結構

|-- project
	|-- .editorconfig // 用於在不同編輯器中統一程式碼風格
	|-- .gitignore // git中的忽略檔案列表
	|-- README.md // markdown格式的說明檔案
	|-- angular.json // angular的組態檔
	|-- browserslist // 用於設定瀏覽器相容性的檔案
	|-- karma.conf.js // 自動化測試框架Karma的組態檔
	|-- package-lock.json // 依賴包版本鎖定檔案
	|-- package.json // npm的包定義檔案
	|-- tsconfig.app.json // 用於app專案的ts組態檔
	|-- tsconfig.json // 整個工作區的ts組態檔
	|-- tsconfig.spec.json // 用於測試的ts組態檔
	|-- tslint.json // ts的程式碼靜態掃描設定
	|-- e2e // 自動化整合測試目錄
	|-- src // 原始碼目錄
 
|-- src // 原始碼目錄
	|-- favicon.ico // 收藏圖示
	|-- index.html // 單頁應用到宿主HTML
	|-- main.ts // 入口 ts 檔案
	|-- polyfills.ts // 用於不同瀏覽器的相容指令碼載入
	|-- styles.css // 整個專案的全域性css
	|-- test.ts // 測試入口
	|-- app // 工程原始碼目錄
	|-- assets // 資源目錄
	|-- environments // 環境設定
		|-- environments.prod.ts // 生產環境
		|-- environments.ts // 開發環境
登入後複製

Angular模組

在 app.module.ts 中定義 AppModule,這個根模組會告訴 Angular 如何組裝應用。

在這裡插入圖片描述

@NgModule 裝飾器

@NgModule 接受一個後設資料物件,告訴 Angular 如何編譯和啟動應用

設計意圖

  • 靜態的後設資料(declarations)
  • 執行時的後設資料(providers)
  • 組合與分組(imports 和 exports)

後設資料

  • declarations 陣列:模組擁有的元件、指令或管道,注意每個元件/指令/管道只能在一個模組中宣告
  • providers 陣列: 模組中需要使用的服務
  • imports 陣列:匯入本模組需要的依賴模組,注意是模組
  • exports 陣列: 暴露給其他模組使用的元件、指令或管道等
  • bootstrap 陣列:指定應用的主檢視(稱為根元件)通過引導根 AppModule 來啟動應用,即專案剛載入時選擇讀哪個元件
  • entryComponents 陣列:一般用於動態元件

內建模組

常用的有:核心模組、通用模組、表單模組、網路模組等

在這裡插入圖片描述

自定義模組

當專案比較小的時候可以不用自定義模組

但是當專案非常龐大的時候,把所有的元件都掛載到根模組裡面就不太合適了

所以可以使用自定義模組來組織專案,並且通過自定義模組可以實現路由的懶載入

模組的tips

匯入其他模組時,需要知道使用該模組的目的

  • 如果是元件,那麼需要在每一個需要的模組中都進行匯入
  • 如果是服務,那麼一般來說在根模組匯入一次即可

需要在每個需要的模組中進行匯入的

  • CommonModule : 提供繫結、*ngIf 和 *ngFor 等基礎指令,基本上每個模組都需要匯入它
  • FormsModule / ReactiveFormsModule : 表單模組需要在每個需要的模組匯入
  • 提供元件、指令或管道的模組

只在根模組匯入一次的

  • HttpClientModule / BrowerAnimationsModule NoopAnimationsModule
  • 只提供服務的模組

Angular元件

在這裡插入圖片描述

  • 元件是 Angular 的核心,是 Angular 應用中最基本的 UI 構造塊,控制螢幕上被稱為檢視的一小片區域
  • 元件必須從屬於某個 NgModule 才能被其他元件或應用使用
  • 元件在 @NgModule 後設資料的 declarations 欄位中參照

@Component 後設資料

  • selector :選擇器,選擇相匹配的HTML裡的指令模版
  • templateUrl :將選擇器中匹配的指令同級替換成值的模版
  • template :內嵌模版,直接可以在裡面寫HTML模版
  • styleUrls :對應模版的樣式,為一個陣列,可以引入多個css樣式控制元件
  • encapsulation:元件樣式封裝策略
@Component({
  selector: 'app-xxx',
  templateUrl: 'XXX',
  styleUrls: ['XXX'],
  encapsulation:ViewEncapsulation.Emulated  // 不寫則預設該值,表示該元件樣式只作用於元件本身,不影響全域性樣式,在 head 中生成單獨的 style 標籤
})
登入後複製

資料繫結

  • 資料繫結 {{data}}

  • 屬性繫結 [id]="id",其中[class.樣式類名]=「判斷表示式」是在應用單個class樣式時的常用技巧

  • 事件繫結 (keyup)="keyUpFn($event)"

  • 樣式繫結可以用 :host 這樣一個偽類選擇器,繫結的樣式作用於元件本身

  • 雙向資料繫結 [(ngModel)]

    // 注意引入:FormsModule
    import { FormsModule } from '@angular/forms';
    
    <input type="text" [(ngModel)]="inputValue"/> {{inputValue}}
    
    // 其實是一個語法糖
    [ngModel]="username" (ngModelChange)="username = $event"
    登入後複製

髒值檢測

髒值檢測:當資料改變時更新檢視(DOM)

如何進行檢測:檢測兩個狀態值(當前狀態和新狀態)

何時觸發髒值檢測:瀏覽器事件(clickmouseoverkeyup等)、setTimeout()setInterval()、HTTP請求

Angular 有兩種變更檢測策略:DefaultOnPush

可以通過在@Component後設資料中設定changeDetection: ChangeDetectionStrategy.OnPush進行切換

Default

優點:每一次有非同步事件發生,Angular 都會觸發變更檢測,從根元件開始遍歷其子元件,對每一個元件都進行變更檢測,對dom進行更新。

缺點:有很多元件狀態沒有發生變化,無需進行變更檢測。如果應用程式中元件越多,效能問題會越來越明顯。

OnPush

優點:元件的變更檢測完全依賴於元件的輸入(@Input),只要輸入值不變就不會觸發變更檢測,也不會對其子元件進行變更檢測,在元件很多的時候會有明顯的效能提升。

缺點:必須保證輸入(@Input)是不可變的(可以用Immutable.js解決),每一次輸入變化都必須是新的參照。

父子元件通訊

在這裡插入圖片描述

父元件給子元件傳值 @input

父元件不僅可以給子元件傳遞簡單的資料,還可把自己的方法以及整個父元件傳給子元件。

// 父元件呼叫子元件的時候傳入資料
<app-header [msg]="msg"></app-header>

// 子元件引入 Input 模組
import { Component, OnInit ,Input } from '@angular/core';

// 子元件中 @Input 裝飾器接收父元件傳過來的資料
export class HeaderComponent implements OnInit {
  @Input() msg:string
	constructor() { }
	ngOnInit() { }
}

// 子元件中使用父元件的資料
<h2>這是頭部元件--{{msg}}</h2>
登入後複製

**子元件觸發父元件的方法 @Output **

// 子元件引入 Output 和 EventEmitter
import { Component,OnInit,Input,Output,EventEmitter} from '@angular/core';

// 子元件中範例化 EventEmitter
// 用 EventEmitter 和 @Output 裝飾器配合使用 <string> 指定型別變數
@Output() private outer=new EventEmitter<string>();

// 子元件通過 EventEmitter 物件 outer 範例廣播資料
sendParent(){
  this.outer.emit('msg from child')
}

// 父元件呼叫子元件的時候,定義接收事件,outer 就是子元件的 EventEmitter 物件 outer
<app-header (outer)="runParent($event)"></app-header>

// 父元件接收到資料會呼叫自己的 runParent, 這個時候就能拿到子元件的資料
// 接收子元件傳遞過來的資料
  runParent(msg:string){
   alert(msg);
}
登入後複製

父元件通過 ViewChild 主動呼叫子元件DOM和方法

// 給子元件定義一個名稱
<app-footer #footerChild></app-footer>

// 引入 ViewChild
import { Component, OnInit ,ViewChild} from '@angular/core';

// ViewChild 和子元件關聯起來
@ViewChild('footerChild') footer;

// 呼叫子元件
run(){
   this.footer.footerRun();
}
登入後複製

投影元件

在這裡插入圖片描述

由於元件過度巢狀會導致資料冗餘和事件傳遞,因此引入投影元件的概念

投影元件 ng-content 作為一個容器元件使用

主要用於元件動態內容的渲染,而這些內容沒有複雜的業務邏輯,也不需要重用,只是一小部分 HTML 片段

使用 ng-content 指令將父元件模板中的任意片段投影到它的子元件上

元件裡面的 ng-content 部分可以被元件外部包裹的元素替代

// 表現形式: <ng-content select="樣式類/HTML標籤/指令"></ng-content>

<ng-content select="[appGridItem]"></ng-content>
登入後複製

select 表明包含 appGridItem 的指令的元素才能投影穿透過來

Angular指令

在這裡插入圖片描述

指令可以理解為沒有模版的元件,它需要一個宿主元素(Host)

推薦使用方括號 [] 指定 Selector,使它變成一個屬性

@Directive({
selector: '[appGridItem]'
})
登入後複製

內建屬性型指令

NgClass

ngClass 是自由度和拓展性最強的樣式繫結方式

<div [ngClass]="{'red': true, 'blue': false}">
  這是一個 div
</div>
登入後複製

NgStyle

ngStyle由於是嵌入式樣式,因此可能會覆蓋掉其他樣式,需謹慎

<div [ngStyle]="{'background-color':'green'}">你好 ngStyle</div>
登入後複製

NgModel

// 注意引入:FormsModule
import { FormsModule } from '@angular/forms';

<input type="text" [(ngModel)]="inputValue"/> {{inputValue}}
登入後複製

內建結構型指令

ngIf

ngIf 根據表示式是否成立,決定是否展示 DOM 標籤

<p *ngIf="list.length > 3">這是 ngIF 判斷是否顯示</p>
登入後複製

ngIf else

<div *ngIf="show else ElseContent">這是 ngIF 內容</div>
<ng-template #ElseContent>
  <h2>這是 else 內容</h2>
</ng-template>

// 結構性指令都依賴於 ng-template,*ngIf 實際上就是 ng-template 指令的 [ngIf] 屬性。
登入後複製

ngFor

<ul>
  <li *ngFor="let item of list;let i = index;">
     {{item}} --{{i}}
  </li>
</ul>
登入後複製

ngSwitch

<ul [ngSwitch]="score">
   <li *ngSwitchCase="1">已支付</li>
   <li *ngSwitchCase="2">已確認</li>
   <li *ngSwitchCase="3">已發貨</li>
   <li *ngSwitchDefault>已失效</li>
</ul>
登入後複製

指令事件樣式繫結

@HostBinding 繫結宿主的屬性或者樣式

@HostBinding('style.display') display = "grid";

// 用樣式繫結代替rd2的 this.setStyle('display','grid');
登入後複製

@HostListener 繫結宿主的事件

@HostListener('click',['$event.target'])

// 第一個引數是事件名,第二個是事件攜帶引數
登入後複製

Angular生命週期

生命週期函數通俗的講就是元件建立、元件更新、元件銷燬的時候會觸發的一系列的方法

當 Angular 使用建構函式新建一個元件或指令後,就會按下面規定的順序在特定時刻呼叫生命週期勾點

  • constructor :建構函式永遠首先被呼叫,一般用於變數初始化以及類範例化

  • ngOnChanges :被繫結的輸入屬性變化時被呼叫,首次呼叫一定在 ngOnInit 之前。輸入屬性發生變化是觸發,但元件內部改變輸入屬性是不會觸發的。注意:如果元件沒有輸入,或者使用它時沒有提供任何輸入,那麼框架就不會呼叫 ngOnChanges

  • ngOnInit :元件初始化時被呼叫,在第一輪 ngOnChanges 完成之後呼叫,只呼叫一次。使用 ngOnInit 可以在建構函式之後馬上執行復雜的初始化邏輯,同時在 Angular 設定完輸入屬性之後,可以很安全的對該元件進行構建

  • ngDoCheck :髒值檢測時呼叫,在變更檢測週期中 ngOnChanges 和 ngOnInit 之後

    • ngAfterContentInit :內容投影ng-content完成時呼叫,只在第一次 ngDoCheck 之後呼叫

    • ngAfterContentChecked: 每次完成被投影元件內容的變更檢測之後呼叫(多次)

    • ngAfterViewInit :元件檢視及子檢視初始化完成時呼叫,只在第一次 ngAfterContentChecked 呼叫一次

    • ngAfterViewChecked: 檢測元件檢視及子檢視變化之後呼叫(多次)

  • ngOnDestroy 當元件銷燬時呼叫,可以反訂閱可觀察物件和分離事件處理器,以防記憶體漏失

Angular路由

路由(導航)本質上是切換檢視的一種機制,路由的導航URL並不真實存在

Angular 的路由借鑑了瀏覽器URL變化導致頁面切換的機制

Angular 是單頁程式,路由顯示的路徑不過是一種儲存路由狀態的機制,這個路徑在 web 伺服器上不存在

路由基本設定

/**
 * 在功能模組中定義子路由後,只要匯入該模組,等同於在根路由中直接定義
 * 也就是說在 AppModule 中匯入 HomeModule 的時候,
 * 由於 HomeModule 中匯入了 HomeRouting Module
 * 在 HomeRoutingModule 中定義的路由會合併到根路由表
 * 相當於直接在根模組中定義下面的陣列。
 * const routes = [{
 *   path: 'home',
 *   component: HomeContainerComponent
 * }]
 */

const routes: Routes = [
  {path: 'home', component: HomeComponent},
  {path: 'news', component: NewsComponent},
  {path: 'newscontent/:id', component: NewscontentComponent},  // 設定動態路由
  {
    path: '',
    redirectTo: '/home',  // 重定向
    pathMatch: 'full'
	},
  //匹配不到路由的時候載入的元件 或者跳轉的路由
  {
     path: '**', /*任意的路由*/
     // component:HomeComponent
     redirectTo:'home'
  }
]

@NgModule({
  /**
   * 根路由使用 `RouterModule.forRoot(routes)` 形式。
   * 而功能模組中的路由模組使用 `outerModule.forChild(routes)` 形式。
   * 啟用路由的 debug 跟蹤模式,需要在根模組中設定 `enableTracing: true`
   */
  imports: [RouterModule.forRoot(routes, { enableTracing: true })],
  exports: [RouterModule]
})
export class AppRoutingModule { }
登入後複製

啟用路由

找到 app.component.html 根元件模板,設定 router-outlet

通過模版屬性存取路由,即路由連結 routerLink

<h1>
  <a [routerLink]="['/home']">首頁</a>
  <a [routerLink]="['/home',tab.link]">首頁</a><!-- 路徑引數 -->
  <a [routerLink]="['/home',tab.link,{name:'val1'}]">首頁</a> <!-- 路徑物件引數 -->
  <a [routerLink]="['/home']" [queryParams]="{name:'val1'}">首頁</a> <!-- 查詢引數 -->
</h1>
<router-outlet></router-outlet>  <!-- 路由插座,佔位標籤 -->
<!--
  路由顯示的內容是插入到 router-outlet 的同級的下方節點
  而不是在 router-outlet 中包含
-->
<!--
  當事件處理或者達到某個條件時,可以使用手動跳轉
	this.router.navigate(['home']); 
	this.router.navigate(['home',tab.link]); 
	this.router.navigate(['home',tab.link,{name:'val1'}]); 
	this.router.navigate(['home'],{queryParams:{name:'val1'}}); 
-->
登入後複製

控制路由啟用狀態的樣式 routerLinkActive

<h1>
    <a routerLink="/home" routerLinkActive="active">首頁</a>
    <a routerLink="/news" routerLinkActive="active">新聞</a>
</h1>

<h1>
   <a [routerLink]="[ '/home' ]" routerLinkActive="active">首頁</a>
   <a [routerLink]="[ '/news' ]" routerLinkActive="active">新聞</a>
</h1>

.active{
   color:red;
}
登入後複製

路由引數

路徑引數讀取

this.route.paramsMap.subscribe(params => {...})
登入後複製

查詢引數讀取

this.route.queryParamsMap.subscribe(params => {...})
登入後複製

路由傳遞一個引數及其接收方法:

傳遞引數:path:’info/:id’

接收引數:

constructor(private routerInfo: ActivatedRoute){}
ngOnInit(){
	this.routerInfo.snapshot.params['id']
}
登入後複製

路由傳遞多個引數及其接收方法:

傳遞:[queryParams]=‘{id:1,name:‘crm’}’

接收引數:

constructor(private routerInfo: ActivatedRoute){}
ngOnInit(){
	this.routerInfo.snapshot.params['id']
	this.routerInfo.snapshot.params['name']
}
登入後複製

路由懶載入

懶載入子模組,子模組需要設定路由設定啟動子模組 loadChildren

const routes: Routes = [
    {path:'user',loadChildren:'./module/user/user.module#UserModule' },
    {path:'product',loadChildren:'./module/product/product.module#ProductModule'},
    {path:'article',loadChildren:'./module/article/article.module#ArticleModule'},
    {path:'**',redirectTo:'user'}
];

// 上面好像會報錯 Error find module 
// 設定懶載入
const routes: Routes = [
    {path:'user',loadChildren:()=>import('./module/user/user.module').then(mod=>mod.UserModule)},
    {path:'article',loadChildren:()=>import('./module/article/article.module').then(mod=>mod.ArticleModule)},
    {path:'product',loadChildren:()=>import('./module/product/product.module').then(mod=>mod.ProductModule)},
    {path:'**',redirectTo:'user'}
];
登入後複製

Angular服務

元件不應該直接獲取或儲存資料,應該聚焦於展示資料,而把資料存取的職責委託給某個服務

獲取資料和檢視展示應該相分離,獲取資料的方法應該放在服務中

類似 VueX,全域性的共用資料(通用資料)及非父子元件傳值、共用資料放在服務中

元件之間相互呼叫各元件裡定義的方法

多個元件都用的方法(例如資料快取的方法)放在服務(service)裡

import { Injectable } from '@angular/core';
@Injectable({
  providedIn: 'root',
})
export class HeroService {
  aa = 'abc';
  constructor(){ }
  ngOnInit(){ }
}

import { HeroService } from '../../../services/hero/hero.service';
export class AComponent implements OnInit{
  constructor(private heroService : HeroService) {} //範例化
  ngOnInit(){
    console.log(this.heroService.aa)
  }
}
登入後複製

@Injectable()裝飾器

在 Angular 中,要把一個類定義為服務,就要用 @Injectable() 裝飾器來提供後設資料,以便讓 Angular 把它作為依賴注入到元件中。

同樣,也要使用 @Injectable () 裝飾器來表明一個元件或其它類(比如另一個服務、管道或 NgModule)擁有一個依賴。

@Injectable () 裝飾器把這個服務類標記為依賴注入系統的參與者之一,它是每個 Angular 服務定義中的基本要素。

在未設定好 Angular 的依賴注入器時,Angular 實際上無法將它注入到任何位置。

@Injectable () 裝飾器具有一個名叫 providedIn 的後設資料選項,providedIn 設定為 'root',即根元件中,那麼該服務就可以在整個應用程式中使用了。

providedIn 提供這些值:‘root''platform''any'null

對於要用到的任何服務,必須至少註冊一個提供者。

服務可以在自己的後設資料中把自己註冊為提供者,可以讓自己隨處可用,也可以為特定的模組或元件註冊提供者。

要註冊提供者,就要在服務的 @Injectable () 裝飾器中提供它的後設資料,或者在 @NgModule ()@Component () 的後設資料中。

在元件中提供服務時,還可以使用 viewProdiversviewProviders 對子元件樹不可見

可以使用不同層級的提供者來設定注入器,也表示該服務的作用範圍

  • Angular 建立服務預設採用的方式:在服務本身的 @Injectable () 裝飾器中

  • 該服務只在某服務中使用:在 NgModule 的 @NgModule () 裝飾器中

  • 該服務在某元件中使用:在元件的 @Component () 裝飾器中

依賴注入

在專案中,有人提供服務,有人消耗服務,而依賴注入的機制提供了中間的介面,並替消費者建立並初始化處理

消費者只需要知道拿到的是完整可用的服務就好,至於這個服務內部的實現,甚至是它又依賴了怎樣的其他服務,都不需要關注。

Angular 通過 service共用狀態,而這些管理狀態和資料的服務便是通過依賴注入的方式進行處理的

Angular 的 service 的本質就是依賴注入,將service作為一個Injector注入到component

歸根到底,很多時候我們建立服務,是為了維護公用的狀態和資料,通過依賴注入的方式來規定哪些元件可共用

在這裡插入圖片描述

正是因為 Angular 提供的這種依賴注入機制,才能在建構函式中直接宣告範例化

  constructor(private heroService : HeroService) {} // 依賴注入
登入後複製

在這裡插入圖片描述

先看一下 Angular 中 TS 單檔案的注入

// 首先寫 @injectable 我們需要注入的東西,比如說 product
@Injectable()
class Product {
  constructor(
    private name: string,
    private color: string,
    private price: number,
  ) { }
}

class PurchaseOrder {
  constructor(private product: Product){ }
}
 
export class HomeGrandComponent implements OnInit {
  constructor() { }
  ngOnInit() {
    // 構造一個 injector 用 create 方法 裡面 providers 陣列中寫我們需要構造的東西
    const injector = Injector.create({
      providers: [
        {
          provide: Product,
          // 構造 Product 在 useFactory 中就會把上面定義的 product 注入到這裡
          useFactory: () => {
            return new Product('大米手機', '黑色', 2999);
          },
          deps: []
        },
        {
          provide: PurchaseOrder,
          deps: [Product]
        },
        {
          provide: token,
          useValue: { baseUrl: 'http://local.dev' }
        }
      ]
    }); 
    console.log('injector獲取product', injector.get(PurchaseOrder).getProduct);
    console.log(injector.get(token));
  }
登入後複製

再看一下Angular 中 module 模組的注入

// .service.ts 中 @Injectable () 依賴注入
@Injectable()
export class HomeService {
  imageSliders: ImageSlider[] = [
    {
      imgUrl:'',
      link: '',
      caption: ''
    }
  ]
  getBanners() {
    return this.imageSliders;
  }
}

// 使用模組對應的.module.ts 中
@NgModule({
  declarations: [
    HomeDetailComponent,
  ],
  providers:[HomeService], // 在 providers 直接寫對應服務,直接將服務注入模組
  imports: [SharedModule, HomeRoutingModule]
})
登入後複製

不管是在元件內還是在模組內,我們使用 providers 的時候,就是進行了一次依賴注入的註冊和初始化

其實模組類(NgModule)也和元件一樣,在依賴注入中是一個注入器,作為容器提供依賴注入的介面

NgModule 使我們不需要在一個元件中注入另一個元件,通過模組類(NgModule)可以進行獲取和共用

Angular 管道

Angular 管道是編寫可以在 HTML 元件中宣告的顯示值轉換的方法

管道將資料作為輸入並將其轉換為所需的輸出

管道其實就是過濾器,用來轉換資料然後顯示給使用者

管道將整數、字串、陣列和日期作為輸入,用 | 分隔,然後根據需要轉換格式,並在瀏覽器中顯示出來

在插值表示式中,可以定義管道並根據情況使用

Angular 應用程式中可以使用許多型別的管道

內建管道

  • String -> String
    • UpperCasePipe 轉換成大寫字元
    • LowerCasePipe 轉換成小寫字元
    • TitleCasePipe 轉換成標題形式,第一個字母大寫,其餘小寫
  • Number -> String
    • DecimalPipe 根據數位選項和區域設定規則格式化值
    • PercentPipe 將數位轉換為百分比字串
    • CurrencyPipe 改變人名幣格式
  • Object -> String
    • JsonPipe 物件序列化
    • DatePipe 日期格式轉換
  • Tools
    • SlicePipe 字串擷取
    • AsyncPipe 從非同步回執中解出一個值
    • I18nPluralPipe 複數化
    • I18nSelectPipe 顯示與當前值匹配的字串

使用方法

<div>{{ 'Angular' | uppercase }}</div>  <!-- Output: ANGULAR -->

<div>{{ data | date:'yyyy-MM-dd' }}</div>  <!-- Output: 2022-05-17 -->

<div>{{ { name: 'ccc' } | json }}</div>  <!-- Output: { "name": "ccc" } -->

<!-- 
	管道可以接收任意數量的引數,使用方式是在管道名稱後面新增: 和引數值
	若需要傳遞多個引數則引數之間用冒號隔開 
-->

<!-- 可以將多個管道連線在一起,組成管道鏈對資料進行處理 -->
<div>{{ 'ccc' | slice:0:1 | uppercase }}</div>
登入後複製

自定義管道

管道本質上就是個類,在這個類裡面去實現 PipeTransfrom 介面的 transform 這個方法

  • 使用 @Pipe 裝飾器定義 Pipemetadata 資訊,如 Pipe 的名稱 - 即 name 屬性
  • 實現 PipeTransform 介面中定義的 transform 方法
// 引入PipeTransform是為了繼承transform方法
import { Pipe, PipeTransform } form '@angular/core'; 
// name屬性值慣用小駝峰寫法, name的值為html中 | 後面的名稱
@Pipe({ name: 'sexReform' }) 
export class SexReformPipe implements PipeTransform {
    transform(value: string, args?: any): string {
    // value的值為html中 | 前面傳入的值, args為名稱後傳入的引數
        switch(value){
            case 'male': return '男';
            case 'female': return '女';
            default: return '雌雄同體';
        } 
    }
}

// demo.component.ts
export Class DemoComponent {
    sexValue = 'female';
}

// demo.component.html
<span>{{ sexValue | sexReform }}</span>

// 瀏覽器輸出
女

// 管道可以鏈式使用,還可以傳參
<span> {{date | date: 'fullDate' | uppercase}} </span>
// 每一個自定義管道都需要實現 PipeTransform 介面,這個介面非常簡單,只需要實現 transform 方法即可。
// transform()方法引數格式 - transform(value: string, args1: any, args2?: any): 
// value為傳入的值(即為需要用此管道處理的值, | 前面的值); 
// args 為傳入的引數(?:代表可選);
// html 中使用管道格式 - {{ 資料 | 管道名 : 引數1 : 引數2 }}
// 與 component 一樣,pipe 需要先在 declarations 陣列中宣告後使用
登入後複製

Angular操作DOM

原生JS操作

ngAfterViewInit(){
   var boxDom:any=document.getElementById('box');
   boxDom.style.color='red';
}
登入後複製

ElementRef

ElementRef 是對檢視中某個原生元素的包裝類

因為 DOM 元素不是 Angular 中的類,所以需要一個包裝類以便在 Angular 中使用和標識其型別

ElementRef 的背後是一個可渲染的具體元素。在瀏覽器中,它通常是一個 DOM 元素

class ElementRef<T> {
  constructor(nativeElement: T)
  nativeElement: T  //背後的原生元素,如果不支援直接存取原生元素,則為 null(比如:在 Web Worker 環境下執行此應用的時候)。
}
登入後複製

當需要直接存取 DOM 時,請把本 API 作為最後選擇 。優先使用 Angular 提供的模板和資料繫結機制

如果依賴直接存取 DOM 的方式,就可能在應用和渲染層之間產生緊耦合。這將導致無法分開兩者,也就無法將應用釋出到 Web Worker 中

ViewChild

使用模板和資料繫結機制,使用 @viewChild

// 模版中給 DOM 起一個參照名字,以便可以在元件類或模版中進行參照 <div #myattr></div>

// 引入 ViewChild
import { ViewChild,ElementRef } from '@angular/core';

// 用 ViewChild 繫結 DOM	
@ViewChild('myattr') myattr: ElementRef;

// 在 ngAfterViewInit 生命週期函數裡可以很安全的獲取 ViewChild 參照的 DOM
ngAfterViewInit(){
   let attrEl = this.myattr.nativeElement;
}
登入後複製

父元件中可以通過 ViewChild 呼叫子元件的方法

// 給子元件定義一個名稱
<app-footer #footerChild></app-footer>

// 引入 ViewChild
import { Component, OnInit ,ViewChild} from '@angular/core';

// ViewChild 和子元件關聯起來 
// 如果想參照模版中的 Angular 元件,ViewChild 中可以使用參照名,也可以使用元件型別
@ViewChild('footerChild') footer;

// @ViewChild('imageSlider', { static: true }) // static指定是動態還是靜態,在*ngFor或者*ngIf中是動態,否則即為靜態,動態為 true

// 呼叫子元件
run(){
   this.footer.footerRun();
}
登入後複製

參照多個模版元素,可以用@ViewChildren,在ViewChildren中可以使用參照名

或者使用 Angular 元件/指令的型別,宣告型別為 QueryList<?>

<img
  #img
  *ngFor="let slider of sliders"
  [src]="slider.imgUrl"
  [alt]="slider.capiton"
>

// 使用 ViewChildren 參照獲取
@ViewChildren(’img‘);

// 使用型別參照獲取
imgs: QueryList<ElementRef>;
登入後複製

Renderer2

Renderer2 是 Angular 提供的操作 element 的抽象類,使用該類提供的方法,能夠實現在不直接接觸 DOM 的情況下操作頁面上的元素。

Renderer2 的常用方法:

  • addClass /removeClassdirective 的宿主元素新增或刪除 class
import { Directive, Renderer2, ElementRef, OnInit } from '@angular/core';

@Directive({
    selector: '[testRenderer2]'
})

export class TestRenderer2Directive implements OnInit {
    constructor(private renderer: Renderer2, private el: ElementRef) {} // 範例化

    ngOnInit() {
    this.renderer.addClass(this.el.nativeElement, 'test-renderer2');
    // this.renderer.removeClass(this.el.nativeElement, 'old-class');
    }
}
登入後複製
  • createElement /appendChild/createText 建立 DIV 元素,插入文字內容,並將其掛載到宿主元素上
import { Directive, Renderer2, ElementRef, OnInit } from '@angular/core';

constructor(private renderer: Renderer2, private el: ElementRef) {}

ngOnInit() {
    const div = this.renderer.createElement('div');
    const text = this.renderer.createText('Hello world!');
    
    this.renderer.appendChild(div, text);
    this.renderer.appendChild(this.el.nativeElement, div);
}
登入後複製
  • setAttribute /removeAttribute 在宿主元素上新增或刪除 attribute
import { Directive, Renderer2, ElementRef, OnInit } from '@angular/core';

constructor(private renderer: Renderer2, private el: ElementRef) {}

ngOnInit() {
    this.renderer.setAttribute(this.el.nativeElement, 'aria-hidden', 'true');
}
登入後複製
  • setStyle /removeStyle 在宿主元素上新增 inline-style
import { Directive, Renderer2, ElementRef, OnInit } from '@angular/core';

constructor(private renderer: Renderer2, private el: ElementRef) {}

ngOnInit() {
    this.renderer.setStyle(
        this.el.nativeElement,
        'border-left',
        '2px dashed olive'
    );
}
登入後複製

移除 inline-style :

constructor(private renderer: Renderer2, private el: ElementRef) {}

ngOnInit() {
    this.renderer.removeStyle(this.el.nativeElement, 'border-left');
}
登入後複製
  • setProperty 設定宿主元素的 property 的值
constructor(private renderer: Renderer2, private el: ElementRef) {}

ngOnInit() {
    this.renderer.setProperty(this.el.nativeElement, 'alt', 'Cute alligator');
}
登入後複製

直接操作DOM,Angular不推薦。儘量採用 @viewChildrenderer2 組合,Angular推薦使用 constructor(private rd2: Renderer2) {} 依賴注入,

import {
  Component,
  OnInit,
  Renderer2,
  ViewChild,
} from '@angular/core';
import { AboxItemComponent } from './abox-item/abox-item.component';
 
@Component({
  selector: 'app-abox',
  templateUrl: './abox.component.html',
  styleUrls: ['./abox.component.less'],
})
export class AboxComponent implements OnInit {
  private container;
  activeIndex: number;
  @ViewChild('containers') containers: any;
  constructor(private rd2: Renderer2) {}
 
  ngOnInit(): void {}
 
  ngAfterViewInit(): void {
    this.container = this.containers.nativeElement;
    this.initCarouselWidth();
  }
    
  initCarouselWidth() {
    this.rd2.setStyle(this.container, 'width', '100px');
  }
}
登入後複製

Angular網路請求

HttpClient

需匯入 HttpClientModule ,只在根模組中匯入,並且整個應用只需匯入一次,不用在其他模組匯入

在建構函式中注入HttpClientget/post方法對應HTTP方法,這些方法是泛型的,可以直接把返回的JSON轉換成對應型別。若是不規範的請求,使用request方法

返回的值是 Observable,必須訂閱才會傳送請求,否則不會傳送

get 請求資料

// 在 app.module.ts 中引入 HttpClientModule 並注入
import {HttpClientModule} from '@angular/common/http';
imports: [
  BrowserModule,
  HttpClientModule
]

// 在用到的地方引入 HttpClient 並在建構函式宣告
import {HttpClient} from "@angular/common/http";
constructor(private http: HttpClient,private cd: ChangeDetectorRef) { } // 依賴注入

// get 請求資料
var api = "http://baidu.com/api/productlist";
this.http.get(api).subscribe(response => {
  console.log(response);
  this.cd.markForCheck();
  // 如果改變了髒值檢測的變更原則 changeDetection: ChangeDetectionStrategy.OnPush
  // 則需要使用 this.cd.markForCheck() 手動提醒 Angular 這裡需要進行髒值檢測
});
登入後複製

post 提交資料

// 在 app.module.ts 中引入 HttpClientModule 並注入
import {HttpClientModule} from '@angular/common/http';
imports: [
   BrowserModule,
   HttpClientModule
]

// 在用到的地方引入 HttpClient 、HttpHeaders 並在建構函式宣告 HttpClient
import {HttpClient,HttpHeaders} from "@angular/common/http";
constructor(private http:HttpClient) { } // 範例化

// post 提交資料
const httpOptions = {
    headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
var api = "http://127.0.0.1:4200/doLogin";
this.http.post(api,{username:'瑞萌萌',age:'22'},httpOptions).subscribe(response => {
		console.log(response);
});
登入後複製

Jsonp請求資料

// 在 app.module.ts 中引入 HttpClientModule、HttpClientJsonpModule 並注入
import {HttpClientModule,HttpClientJsonpModule} from'@angular/common/http';
imports: [
   BrowserModule,
   HttpClientModule,
   HttpClientJsonpModule
]

// 在用到的地方引入 HttpClient 並在建構函式宣告
import {HttpClient} from "@angular/common/http";
constructor(private http:HttpClient) { } // 範例化

// jsonp 請求資料
var api = "http://baidu.com/api/productlist";
this.http.jsonp(api,'callback').subscribe(response => {
   console.log(response);
});
登入後複製

攔截器

Angular 攔截器是 Angular 應用中全域性捕獲和修改 HTTP 請求和響應的方式,例如攜帶 Token 和捕獲 Error

前提是隻能攔截使用 HttpClientModule 發出的請求,如果使用 axios 則攔截不到

建立攔截器

// 使用命令 ng g interceptor name,在這裡建立攔截器 ng g interceptor LanJieQi
// cli 生成攔截器是沒有簡寫方式的

import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor
} from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable()
export class LanJieQiInterceptor implements HttpInterceptor {
  constructor() {}
  // 預設的 intercept() 方法只是單純的將請求轉發給下一個攔截器(如果有),並最終返回 HTTP 響應體的 Observable
  // request: HttpRequest<unknown> 表示請求物件,包含了請求相關的所有資訊,unknown指定請求體body的型別
  // next: HttpHandler 請求物件修改完成,將修改後的請求物件通過next中的handle方法傳回真正傳送請求的方法中
  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> 	{
    // next 物件表示攔截器連結串列中的下一個攔截器(在應用中可以設定多個攔截器)
    return next.handle(request);
  }
}
登入後複製

注入攔截器

// 在 @NgModule 模組中注入攔截器
// 攔截器也是一個由 Angular 依賴注入 (DI) 系統管理的服務,也必須先提供這個攔截器類,才能使用它
// 由於攔截器是 HttpClient 服務的依賴,所以必須在提供 HttpClient 的同一個(或其各級父注入器)注入器中提供這些攔截器
@NgModule({
  imports: [
    HttpClientModule
    // others...
  ],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: LanJieQiInterceptor,
      // multi: true 表明 HTTP_INTERCEPTORS 是一個多重提供者的令牌,表示這個令牌可以注入多個攔截器
      multi: true
    },
  ],
  bootstrap: [AppComponent]
})

export class AppModule { }
登入後複製

請求頭攔截

@Injectable()export class LanJieQiInterceptor implements HttpInterceptor {
  constructor() {}
  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> 	{
    // 為了統一設定請求頭,需要修改請求
    // 但 HttpRequest 和 HttpResponse 範例的屬性卻是唯讀(readonly)的
    // 所以修改前需要先 clone 一份,修改這個克隆體後再把它傳給 next.handle()
    let req = request.clone({
    	setHeaders:{
      	token:"123456" // 在請求頭中增加 token:123456
    	}
			// setHeaders 和 headers: request.headers.set('token', '123456') 一致
  	})
  	return next.handle(req)// 將修改後的請求返回給應用
  }}
登入後複製

響應捕獲

@Injectable()
export class LanJieQiInterceptor implements HttpInterceptor {
  constructor() {}
  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> 	{
    // 為了統一設定請求頭,需要修改請求
    // 但 HttpRequest 和 HttpResponse 範例的屬性卻是唯讀(readonly)的
    // 所以修改前需要先 clone 一份,修改這個克隆體後再把它傳給 next.handle()
    let req = request.clone({
    	setHeaders:{
      	token:"123456" // 在請求頭中增加 token:123456
    	}
			// setHeaders 和 headers: request.headers.set('token', '123456') 一致
  	})
  	return next.handle(req)// 將修改後的請求返回給應用
  }
}
登入後複製

如果有多個攔截器,請求順序是按照設定順序執行,響應攔截則是相反的順序

如果提供攔截器的順序是先 A再 B再 C,那麼請求階段的執行順序就是 A->B->C,而響應階段的執行順序則是 C->B->A

Angular表單

模版驅動表單

模板驅動表單在往應用中新增簡單的表單時非常有用,但是不像響應式表單那麼容易擴充套件

如果有非常基本的表單需求和簡單到能用模板管理的邏輯,就使用模板驅動表單

響應式表單和模板驅動表單共用了一些底層構造塊:

FormControl 範例用於追蹤單個表單控制元件的值和驗證狀態

FormGroup 用於追蹤一個表單控制元件組的值和狀態

FormArray 用於追蹤表單控制元件陣列的值和狀態,有長度屬性,通常用來代表一個可以增長的欄位集合

ControlValueAccessor 用於在 Angular 的 FormControl 範例和原生 DOM 元素之間建立一個橋樑

FormControlFormGroup 是 angular 中兩個最基本的表單物件

FormControl 代表單一的輸入欄位,它是 Angular 表單中最小單員,它封裝了這些欄位的值和狀態,比如是否有效、是否髒(被修改過)或是否有錯誤等

FormGroup 可以為一組 FormControl 提供總包介面(wrapper interface),來管理多個 FormControl

當我們試圖從 FormGroup 中獲取 value 時,會收到一個 「鍵值對」 結構的物件

它能讓我們從表單中一次性獲取全部的值而無需逐一遍歷 FormControl,使用起來相當順手

FormGroupFormControl 都繼承自同一個祖先 AbstractControltractControl(這是 FormControlFormGroupFormArray 的基礎類別)

首先載入 FormsModule

// 先在 NgModule 中匯入了 FormsModule 表單庫
// FormsModule 為我們提供了一些模板驅動的指令,例如:ngModel、NgForm
import { 
  FormsModule
} from '@angular/forms'; 

@NgModule({ 
  declarations: [ 
    FormsDemoApp, 
    DemoFormSku, 
    // ... our declarations here 
  ], 
  imports: [ 
    BrowserModule, 
    FormsModule,
  ], 
  bootstrap: [ FormsDemoApp ] 
}) 
class FormsDemoAppModule {}
登入後複製

接下來建立一個模版表單

 <div>
      <h2>基礎表單:商品名稱</h2>
      <form #f="ngForm" (ngSubmit)="onSubmit(f.value)">
        <div class="sku">
          <label for="skuInput">商品名稱:</label>
          <input
            type="text"
            id="skuInput"
            placeholder="商品名稱"
            name="sku" //使用form時必須定義,可以理解為當前控制元件的名字
            ngModel
          />
        </div>
        <button>提交</button>
      </form>
    </div>
登入後複製

我們匯入了 FormsModule,因此可以在檢視中使用 NgForm

當這些指令在檢視中可用時,它就會被附加到任何能匹配其 selector 的節點上

NgForm 做了一件便利但隱晦的工作:它的選擇器包含 form 標籤(而不用顯式新增 ngForm 屬性)

這意味著當匯入 FormsModule 時候,NgForm 就會被自動附加到檢視中所有的標籤上

NgForm 提供了兩個重要的功能:

  • 一個 ngFormFormGroup 物件
  • 一個輸出事件 (ngSubmit)
 <form #f="ngForm" (ngSubmit)="onSubmit(f.value)" >
 <!-- 
	這裡使用了 #f=「ngForm」,#v=thing 的意思是我們希望在當前檢視中建立一個區域性變數
	這裡為檢視中的 ngForm 建立了一個別名,並繫結到變數 #f
	這個 ngForm 是由 NgForm 指令匯出的
	ngForm 的型別的物件是 FormGroup 型別的
	這意味著可以在檢視中把變數 f 當作 FormGroup 使用,而這也正是我們在輸出事件 (ngSubmit) 中的使用方法
	在表單中繫結 ngSubmit 事件 (ngSubmit)=「onSubmit (f.value)「
	(ngSubmit) 來自 NgForm 指令
	onSubmit() 將會在元件類中進行定義
	f 就是 FormGroup ,而 .value 會以鍵值對的形式返回 FormGroup 中所有控制元件的值
	
	總結:當提交表單時,將會以該表單的值作為引數,呼叫元件範例上的 `onSubmit` 方法
-->
登入後複製

NgModel 會建立一個新的 FormControl 物件,把它自動新增到父 FormGroup 上(這裡也就是 form 表單物件)

並把這個 FormControl 物件繫結到一個 DOM 上

也就是說,它會在檢視中的 input 標籤和 FormControl 物件之間建立關聯

這種關聯是通過 name 屬性建立的,在本例中是 "name"

響應式表單

使用 ngForm 構建 FormControlFormGroup 很方便,但是無法提供客製化化選項,因此引入響應式表單

響應式表單提供了一種模型驅動的方式來處理表單輸入,其中的值會隨時間而變化

使用響應式表單時,通過編寫 TypeScript 程式碼而不是 HTML 程式碼來建立一個底層的資料模型

在這個模型定義好以後,使用一些特定的指令將模板上的 HTML 元素與底層的資料模型連線在一起

FormBuilder 是一個名副其實的表單構建助手(可以把他看作一個 「工廠」 物件)

在先前的例子中新增一個 FormBuilder,然後在元件定義類中使用 FormGroup

// 先在 NgModule 中匯入了 ReactiveFormsModule 表單庫
import { 
  ReactiveFormsModule 
} from '@angular/forms'; 
@NgModule({
  imports: [
    FormsModule,
    ReactiveFormsModule
  ]
}) 

// 使用 formGroup 和 formControl 指令來構建這個元件,需要匯入相應的類
import { 
  FormBuilder, 
  FormGroup,
  ReactiveFormsModule
} from '@angular/forms'; 

// 在元件類上注入一個從 FormBuilder 類建立的物件範例,並把它賦值給 fb 變數(來自建構函式)
export class DemoFormSkuBuilder { 
  myForm: FormGroup;  // myForm 是 FormGroup 型別
  constructor(fb: FormBuilder) { 
    // FormBuilder 中的 group 方法用於建立一個新的 FormGroup
    // group 方法的引數是代表組內各個 FormControl 的鍵值對
    this.myForm = fb.group({  // 呼叫 fb.group () 來建立 FormGroup
      // 設定一個名為 sku 的控制元件,控制元件的預設值為 "123456"
      'sku': ['123456'] 
    }); 
  }
  onSubmit(value: string): void { 
    console.log('submit value:', value); 
  } 
}
登入後複製

在檢視表單中使用自定義的 FormGroup

<h2 class="ui header">Demo Form: Sku with Builder</h2>
<!--  
	當匯入 FormsModule 時,ngForm 就會自動建立它自己的 FormGroup
	但這裡不希望使用外部的 FormGroup,而是使用 FormBuilder 建立這個 myForm 範例變數
	Angular提供了 formGroup 指令,能讓我們使用現有的 FormGroup
	NgForm 不會應用到帶 formGroup 屬性的節點上
	這裡我們告訴Angular,想用 myForm 作為這個表單的 FormGroup
-->
<form [formGroup]="myForm" 
  <label for="skuInput"> SKU </label> 
  <input type="text" 
     id="skuInput" 
     placeholder="SKU" 
     [formControl]="myForm.controls['sku']">
<!--  
	將 FormControl 繫結到 input 標籤上 : 
	ngModel 會建立一個新的 FormControl 物件並附加到父 FormGroup 中
	但在例子中,我們已經用 FormBuilder 建立了自己的 FormControl
	要將現有的 FormControl 繫結到 input 上,可以用 formControl 指令
	將 input 標籤上的 formControl 指令指向 myForm.controls 上現有的 FormControl 控制元件 sku  
-->
登入後複製

記住以下兩點:

  1. 如果想隱式建立新的 FormGroup 和 FormControl,使用:ngForm、ngModel
  2. 如果要繫結一個現有的 FormGroup 和 FormControl,使用:formGroup、formControl

表單驗證

使用者輸入的資料格式並不總是正確的,如果有人輸入錯誤的資料格式,我們希望給他反饋並阻止他提交表單

因此,我們要用到驗證器,由 validators 模組提供

Validators.required 是最簡單的驗證,表明指定的欄位是必填項,否則就認為 FormControl 是無效的

如果 FormGroup 中有一個 FormControl 是無效的, 那整個 FormGroup 都是無效的

要為 FormControl 物件分配一個驗證器 ,可以直接把它作為第二個引數傳給 FormControl 的建構函式

const control = new FormControl('name', Validators.required);

// 在元件定義類中使用 FormBuilder
  constructor(fb: FormBuilder) { 
    this.myForm = fb.group({ 
      'name': ['',Validators.required] 
    }); 
    this.name = this.myForm.controls['name']; 
  }
登入後複製

在檢視中檢查驗證器的狀態,並據此採取行動

template:`<div>
      <h2>商品表單:商品名稱</h2>
      <form [formGroup]="myForm" (ngSubmit)="onSubmit(myForm)">
        <div>
          <label for="nameInput">商品名稱:</label>
          <input
            type="text"
            id="nameInput"
            placeholder="請輸入名稱"
            [formControl]="myForm.controls['name']"
          />
          <div style="color:red" *ngIf="!name.valid">
            名稱無效
          </div>
          <div style="color:red" *ngIf="name.hasError('textinvalid')">
            名稱不是以「123」開頭
          </div>
          <div *ngIf="name.dirty">
            資料已變動
          </div>
        </div>
        <div>
          <label for="codeInput">商品料號:</label>
          <input
            type="text"
            id="codeInput"
            placeholder="請輸入料號"
            [formControl]="myForm.controls['code']"
          />
          <div
            style="color:red"
            *ngIf="myForm.controls.code.hasError('required')"
          >
            該項必填
          </div>
          <div
            style="color:red"
            *ngIf="myForm.controls.code.hasError('pattern')"
          >
            只可輸入數位和英文
          </div>
        </div>
        <div style="color:green" *ngIf="myForm.isvalid">
          表單無效
        </div>
        <div style="color:green" *ngIf="myForm.valid">
          表單有效
        </div>
        <button type="submit">提交</button>
      </form>
    </div>`
export class NonInWarehouseComponent implements OnInit {
  myForm: FormGroup;
  name: AbstractControl;
  constructor(fb: FormBuilder) {
    this.myForm = fb.group({
      name: ['牛奶', Validators.compose([Validators.required, textValidator])],
      code: ['', [Validators.required, Validators.pattern('^[A-Za-z0-9]*$')]],
    });
    this.name = this.myForm.controls.name;
  }
  ngOnInit() {
    const nameControl = new FormControl('nate');
    console.log('nameControl', nameControl);
  }
  onSubmit(a: any) {
    console.log('a', a);
  }
}
登入後複製

內建校驗器

Angular 提供了幾個內建校驗器,下面是比較常用的校驗器:

  • Validators.required - 表單控制元件值非空
  • Validators.email - 表單控制元件值的格式是 email
  • Validators.minLength() - 表單控制元件值的最小長度
  • Validators.maxLength() - 表單控制元件值的最大長度
  • Validators.pattern() - 表單控制元件的值需匹配 pattern 對應的模式(正規表示式)

自定義驗證器

假設我們的 name 有特殊的驗證需求,比如 name 必須以 123 作為開始

當輸入值(控制元件的值 control.value)不是以 123 作為開始時,驗證器會返回錯誤程式碼 invalidSku

// angular 原始碼中實現 Validators.required 
export class Validators {
  // 接收一個 AbstractControl 物件作為輸入
	static required(control: AbstractControl): ValidationErrors | null;
}
// 當驗證器失敗時,會返回一個 String Map<string,any> 物件,他的鍵是」 錯誤程式碼 「,它的值是 true
export declare type ValidationErrors = {
    [key: string]: any;
};

// 自定義驗證器
function textValidator(
  controls: FormControl // 因為FormControl繼承於 AbstractControl 所以也可以寫成FormControl物件
): {
  [s: string]: boolean;
} {
  if (!controls.value.match(/^123/)) {
    return { textinvalid: true };
  }
}
登入後複製

FormControl 分配驗證器,但是 name 已經有一個驗證器了,如何在同一個欄位上新增多個驗證器

Validators.compose 來實現

Validators.compose 把兩個驗證器包裝在一起,我們可以將其賦值給 FormControl

只有當兩個驗證器都合法時,FormControl 才是合法的

Validators.compose([Validators.required, textValidator])
// 不用compose   [Validators.required, textValidator]
// 保留 compose 是為了向以前歷史版本進行相容,不用 compose 也可實現
登入後複製

動態表單

要實現 Angular 動態表單,主要使用 formArray 方法,formArray 生成的範例是一個陣列,在這個陣列中可以動態的放入 formGroupformControl,這樣便形成了動態表單。

export class ReativeFormsComponent implements OnInit {
  ngOnInit() {
    this.addContact()
  }
  //動態表單
  personMess: FormGroup = new FormGroup({
    //生成動態表單陣列
    contacts: new FormArray([]) 
  })
  //獲取陣列物件
  get contacts(){
    return this.personMess.get('contacts') as FormArray
  }
  //增加一個表單組
  addContact(){
    let myContact = new FormGroup({
      name: new FormControl(),
      phone: new FormControl()
    })
    this.contacts.push(myContact)
  }
   //刪除一個表單組
  deleteContact(i:number){
    this.contacts.removeAt(i)
  }
  //提交表單
  OnSubmit() {
    console.log(this.personMess.value)
  }
}
登入後複製
<form [formGroup]="personMess" (submit)="OnSubmit()">
  <div formArrayName="contacts">
    <!-- 注意:這裡遍歷的時contacts.controls -->
    <div *ngFor="let contact of contacts.controls;let i =index" [formGroupName]="i">
      <input type="text" formControlName="name">
      <input type="text" formControlName="phone">
      <button (click)="deleteContact(i)">刪除資訊</button>
    </div>
  </div>
  <button (click)="addContact()">新增資訊</button><br>
  <input type="submit">
</form>
登入後複製

Angular CDK

CDK 是 Component Dev kit 的簡稱,是 Angular Material 團隊在開發 Library 時發現元件有很多相似的地方,最後進行了抽取,提煉出了公共的邏輯,這部分即是 CDK

官方用了一個很形象的比喻:如果元件庫是火箭飛船,那麼 CDK 就是發動機零件盒
在這裡插入圖片描述

更多程式設計相關知識,請存取:!!

以上就是深入瞭解Angular(新手入門指南)的詳細內容,更多請關注TW511.COM其它相關文章!