前端(vue)入門到精通課程,老師線上輔導:聯絡老師
Apipost = Postman + Swagger + Mock + Jmeter 超好用的API偵錯工具:
Angular 是谷歌開發的一款開源的 web 前端框架,基於 TypeScript 。【相關教學推薦:《》】
和 react 與 vue 相比, Angular 更適合中大型企業級專案。
ng new 新建專案
ng serve 啟動專案
ng build 打包專案
ng generate 建立模組/元件/服務
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 // 開發環境
登入後複製
在 app.module.ts 中定義 AppModule,這個根模組會告訴 Angular 如何組裝應用。
@NgModule 接受一個後設資料物件,告訴 Angular 如何編譯和啟動應用
設計意圖
後設資料
常用的有:核心模組、通用模組、表單模組、網路模組等
當專案比較小的時候可以不用自定義模組
但是當專案非常龐大的時候,把所有的元件都掛載到根模組裡面就不太合適了
所以可以使用自定義模組來組織專案,並且通過自定義模組可以實現路由的懶載入
匯入其他模組時,需要知道使用該模組的目的
需要在每個需要的模組中進行匯入的
CommonModule
: 提供繫結、*ngIf 和 *ngFor 等基礎指令,基本上每個模組都需要匯入它FormsModule / ReactiveFormsModule
: 表單模組需要在每個需要的模組匯入只在根模組匯入一次的
HttpClientModule / BrowerAnimationsModule NoopAnimationsModule
NgModule
才能被其他元件或應用使用@NgModule
後設資料的 declarations
欄位中參照@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)
如何進行檢測:檢測兩個狀態值(當前狀態和新狀態)
何時觸發髒值檢測:瀏覽器事件(click
、mouseover
、keyup
等)、setTimeout()
或setInterval()
、HTTP請求
Angular 有兩種變更檢測策略:Default
和 OnPush
可以通過在@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
的指令的元素才能投影穿透過來
指令可以理解為沒有模版的元件,它需要一個宿主元素(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 使用建構函式新建一個元件或指令後,就會按下面規定的順序在特定時刻呼叫生命週期勾點
constructor :建構函式永遠首先被呼叫,一般用於變數初始化以及類範例化
ngOnChanges :被繫結的輸入屬性變化時被呼叫,首次呼叫一定在 ngOnInit 之前。輸入屬性發生變化是觸發,但元件內部改變輸入屬性是不會觸發的。注意:如果元件沒有輸入,或者使用它時沒有提供任何輸入,那麼框架就不會呼叫 ngOnChanges
ngOnInit :元件初始化時被呼叫,在第一輪 ngOnChanges 完成之後呼叫,只呼叫一次。使用 ngOnInit 可以在建構函式之後馬上執行復雜的初始化邏輯,同時在 Angular 設定完輸入屬性之後,可以很安全的對該元件進行構建
ngDoCheck :髒值檢測時呼叫,在變更檢測週期中 ngOnChanges 和 ngOnInit 之後
ngAfterContentInit :內容投影ng-content完成時呼叫,只在第一次 ngDoCheck 之後呼叫
ngAfterContentChecked: 每次完成被投影元件內容的變更檢測之後呼叫(多次)
ngAfterViewInit :元件檢視及子檢視初始化完成時呼叫,只在第一次 ngAfterContentChecked 呼叫一次
ngAfterViewChecked: 檢測元件檢視及子檢視變化之後呼叫(多次)
ngOnDestroy 當元件銷燬時呼叫,可以反訂閱可觀察物件和分離事件處理器,以防記憶體漏失
路由(導航)本質上是切換檢視的一種機制,路由的導航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'}
];
登入後複製
元件不應該直接獲取或儲存資料,應該聚焦於展示資料,而把資料存取的職責委託給某個服務
獲取資料和檢視展示應該相分離,獲取資料的方法應該放在服務中
類似 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)
}
}
登入後複製
在 Angular 中,要把一個類定義為服務,就要用 @Injectable()
裝飾器來提供後設資料,以便讓 Angular 把它作為依賴注入到元件中。
同樣,也要使用 @Injectable ()
裝飾器來表明一個元件或其它類(比如另一個服務、管道或 NgModule
)擁有一個依賴。
@Injectable ()
裝飾器把這個服務類標記為依賴注入系統的參與者之一,它是每個 Angular 服務定義中的基本要素。
在未設定好 Angular 的依賴注入器時,Angular 實際上無法將它注入到任何位置。
@Injectable ()
裝飾器具有一個名叫 providedIn
的後設資料選項,providedIn
設定為 'root'
,即根元件中,那麼該服務就可以在整個應用程式中使用了。
providedIn
提供這些值:‘root'
、'platform'
、'any'
、null
對於要用到的任何服務,必須至少註冊一個提供者。
服務可以在自己的後設資料中把自己註冊為提供者,可以讓自己隨處可用,也可以為特定的模組或元件註冊提供者。
要註冊提供者,就要在服務的 @Injectable ()
裝飾器中提供它的後設資料,或者在 @NgModule ()
或 @Component ()
的後設資料中。
在元件中提供服務時,還可以使用 viewProdivers
,viewProviders
對子元件樹不可見
可以使用不同層級的提供者來設定注入器,也表示該服務的作用範圍
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
管道是編寫可以在 HTML 元件中宣告的顯示值轉換的方法
管道將資料作為輸入並將其轉換為所需的輸出
管道其實就是過濾器,用來轉換資料然後顯示給使用者
管道將整數、字串、陣列和日期作為輸入,用 |
分隔,然後根據需要轉換格式,並在瀏覽器中顯示出來
在插值表示式中,可以定義管道並根據情況使用
在 Angular
應用程式中可以使用許多型別的管道
String
-> String
Number
-> String
Object
-> String
Tools
使用方法
<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
裝飾器定義 Pipe
的 metadata
資訊,如 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 陣列中宣告後使用
登入後複製
ngAfterViewInit(){
var boxDom:any=document.getElementById('box');
boxDom.style.color='red';
}
登入後複製
ElementRef
是對檢視中某個原生元素的包裝類
因為 DOM 元素不是 Angular 中的類,所以需要一個包裝類以便在 Angular 中使用和標識其型別
ElementRef
的背後是一個可渲染的具體元素。在瀏覽器中,它通常是一個 DOM 元素
class ElementRef<T> {
constructor(nativeElement: T)
nativeElement: T //背後的原生元素,如果不支援直接存取原生元素,則為 null(比如:在 Web Worker 環境下執行此應用的時候)。
}
登入後複製
當需要直接存取 DOM 時,請把本 API 作為最後選擇 。優先使用 Angular 提供的模板和資料繫結機制
如果依賴直接存取 DOM 的方式,就可能在應用和渲染層之間產生緊耦合。這將導致無法分開兩者,也就無法將應用釋出到 Web Worker 中
使用模板和資料繫結機制,使用 @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
是 Angular 提供的操作 element
的抽象類,使用該類提供的方法,能夠實現在不直接接觸 DOM 的情況下操作頁面上的元素。
Renderer2
的常用方法:
addClass
/removeClass
在 directive
的宿主元素新增或刪除 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
不推薦。儘量採用@viewChild
和renderer2
組合,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');
}
}
登入後複製
需匯入 HttpClientModule
,只在根模組中匯入,並且整個應用只需匯入一次,不用在其他模組匯入
在建構函式中注入HttpClient
,get/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
模板驅動表單在往應用中新增簡單的表單時非常有用,但是不像響應式表單那麼容易擴充套件
如果有非常基本的表單需求和簡單到能用模板管理的邏輯,就使用模板驅動表單
響應式表單和模板驅動表單共用了一些底層構造塊:
FormControl
範例用於追蹤單個表單控制元件的值和驗證狀態
FormGroup
用於追蹤一個表單控制元件組的值和狀態
FormArray
用於追蹤表單控制元件陣列的值和狀態,有長度屬性,通常用來代表一個可以增長的欄位集合
ControlValueAccessor
用於在 Angular 的FormControl
範例和原生 DOM 元素之間建立一個橋樑
FormControl
和 FormGroup
是 angular 中兩個最基本的表單物件
FormControl
代表單一的輸入欄位,它是 Angular 表單中最小單員,它封裝了這些欄位的值和狀態,比如是否有效、是否髒(被修改過)或是否有錯誤等
FormGroup
可以為一組 FormControl
提供總包介面(wrapper interface),來管理多個 FormControl
當我們試圖從 FormGroup
中獲取 value 時,會收到一個 「鍵值對」 結構的物件
它能讓我們從表單中一次性獲取全部的值而無需逐一遍歷 FormControl
,使用起來相當順手
FormGroup
和 FormControl
都繼承自同一個祖先 AbstractControltractControl
(這是 FormControl
,FormGroup
和 FormArray
的基礎類別)
首先載入 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
提供了兩個重要的功能:
ngForm
的 FormGroup
物件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
構建 FormControl
和 FormGroup
很方便,但是無法提供客製化化選項,因此引入響應式表單
響應式表單提供了一種模型驅動的方式來處理表單輸入,其中的值會隨時間而變化
使用響應式表單時,通過編寫 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
-->
登入後複製
記住以下兩點:
- 如果想隱式建立新的 FormGroup 和 FormControl,使用:ngForm、ngModel
- 如果要繫結一個現有的 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
- 表單控制元件值的格式是 emailValidators.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
生成的範例是一個陣列,在這個陣列中可以動態的放入 formGroup
和 formControl
,這樣便形成了動態表單。
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>
登入後複製
CDK 是 Component Dev kit
的簡稱,是 Angular Material 團隊在開發 Library 時發現元件有很多相似的地方,最後進行了抽取,提煉出了公共的邏輯,這部分即是 CDK
官方用了一個很形象的比喻:如果元件庫是火箭飛船,那麼 CDK 就是發動機零件盒
更多程式設計相關知識,請存取:!!
以上就是深入瞭解Angular(新手入門指南)的詳細內容,更多請關注TW511.COM其它相關文章!