您好,登錄后才能下訂單哦!
這篇文章主要介紹了angular10組件的示例分析,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
html模板
typescript,定義行為
css組,可以引入多個css文件,因此可以在一個組件里面定義多個css文件
//從angular主模塊中引入Component(組件裝飾器或組件注解) import { Component } from '@angular/core'; //裝飾器中以json的形式聲明元數據 @Component({ //它指定了一個叫 <app-root> 的元素。 該元素是 index.html 文件里的一個占位符 //為什么這個組件跟入口index建立了聯系呢?因為入口main.ts中綁定了主模塊為appModule selector: 'app-root', //在模板中找對應的標簽,找到后創建并插入該組件實例 templateUrl: './app.component.html', // html模板 styleUrls: ['./app.component.css'], // css樣式,可以引入多個css文件 // 這個屬性(內聯模板)和templateUrl(外聯模板)二選一,template后面可以直接跟html字符串 // 注意在模板語法(反引號)中是使用插值表達式,不能使用${}插入值 template: `<h2>{{title}}</h2>` }) //組件控制器,寫邏輯代碼的地方 export class AppComponent { title = 'myAngular'; //構造函數可以用來進行屬性的聲明和初始化語句 //在angular里面有個特別重要的點要記住:只能通過構造函數注入依賴 constructor() {} }
安裝 Angular CLI (npm install -g @angular/cli)
建立一個 Angular 項目 (ng new )
如果不滿足這兩個條件,請參考 搭建環境。
使用 Angular CLI 創建一個組件
ng generate component <project-name> // 創建一個組件 ng g c <project-name> // 縮寫
常用的創建組件的一些其他選項
ng g c <project-name> --skip-tests // 創建一個組件,且不用安裝測試文件 ng g c <project-name> --inline-style // 縮寫-s,內聯樣式 ng g c <project-name> --inline-template // 縮寫-t,內聯模板 ng g c <project-name> --module= <module-name> // 指定創建的組件在哪個模塊引用,在多個模塊的項目中會用到
除了通過angular cli自動生成組件外,也可以手動創建組件(不推薦),這里不介紹。
在工作中經常能夠用到的生命周期就兩個(ngOninit和ngOnDestroy),其他的生命周期很少用到;但是如果能夠掌握組件的生命周期,對angular會掌握的得更加深刻。
生命周期含義
當 Angular 實例化組件類并渲染組件視圖及其子視圖時,組件實例的生命周期就開始了。生命周期一直伴隨著變更檢測,Angular 會檢查數據綁定屬性何時發生變化,并按需更新視圖和組件實例。當 Angular 銷毀組件實例并從 DOM 中移除它渲染的模板時,生命周期就結束了。當 Angular 在執行過程中創建、更新和銷毀實例時,指令就有了類似的生命周期。
應用:
你的應用可以使用生命周期鉤子方法來觸發組件或指令生命周期中的關鍵事件,以初始化新實例,需要時啟動變更檢測,在變更檢測過程中響應更新,并在刪除實例之前進行清理。
如何實現生命周期事件
每個組件或指令都可以實現一個或多個生命周期鉤子,這些生命周期鉤子可以在適當的時候對組件或指令實例進行操作。
每個接口都有唯一的一個鉤子方法,它們的名字是由接口名再加上 ng 前綴構成的。比如,OnInit 接口的鉤子方法叫做 ngOnInit()。如果你在組件或指令類中實現了這個方法,Angular 就會在首次檢查完組件或指令的輸入屬性后,緊接著調用它
import { Component } from '@angular/core'; @Component({ selector: 'app-root', styleUrls: ['./app.component.css'], template: `<h2>{{title}}</h2>` }) // 實現OnInit生命周期,可以實現多個生命周期 export class AppComponent implements OnInit{ title = 'myAngular'; constructor() {} ngOnInit(){ console.log("Angular在首次檢查完組件的輸入屬性后,然后調用它,只調用一次") } }
生命周期概覽
鉤子 | 時機 | 用途 | 注意 |
---|---|---|---|
ngOnChanges() | 當被綁定的輸入屬性的值發生變化時調用,首次調用一定會發生在 ngOnInit() 之前 | 當 Angular 設置或重新設置數據綁定的輸入屬性時響應。 該方法接受當前和上一屬性值的 SimpleChanges 對象 | 這發生的非常頻繁,所以你在這里執行的任何操作都會顯著影響性能。 |
ngOnInit() | 在第一輪 ngOnChanges() 完成之后調用,只調用一次。 | 在 Angular 第一次顯示數據綁定和設置指令/組件的輸入屬性之后,初始化指令/組件。組件獲取初始數據的好地方 | 很重要,只調用一次 |
ngDoCheck() | 緊跟在每次執行變更檢測時的 ngOnChanges() 和 首次執行變更檢測時的 ngOnInit() 后調用。 | 檢測,并在發生 Angular 無法或不愿意自己檢測的變化時作出反應。 | 它和ngOnChanges一樣發生的很頻繁 |
ngAfterContentInit() | 第一次 ngDoCheck() 之后調用,只調用一次。 | 當 Angular 把外部內容投影進組件視圖或指令所在的視圖之后調用。 | 只調用一次 |
ngAfterContentChecked() | ngAfterContentInit() 和每次 ngDoCheck() 之后調用 | 每當 Angular 檢查完被投影到組件或指令中的內容之后調用。 | |
ngAfterViewInit() | 第一次 ngAfterContentChecked() 之后調用,只調用一次。 | 初始化完組件視圖及其子視圖之后調用。 | 只調用一次 |
ngAfterViewChecked() | ngAfterViewInit() 和每次 ngAfterContentChecked() 之后調用。 | 每次做完組件視圖和子視圖的變更檢測之后調用。 | |
ngOnDestroy() | 在 Angular 銷毀指令/組件之前調用。 | 當 Angular 每次銷毀指令/組件之前調用并清掃。 在這兒反訂閱可觀察對象和分離事件處理器,以防內存泄漏。 取消訂閱可觀察對象、 清除定時器、 反注冊該指令在全局或應用服務中注冊過的所有回調。 | 很重要 |
重點生命周期詳解
初始化組件和指令 ngOnInit
在構造函數外部執行復雜的初始化。組件的構造應該既便宜又安全。比如,你不應該在組件構造函數中獲取數據。當在測試中創建組件時或者決定顯示它之前,你不應該擔心新組件會嘗試聯系遠程服務器。ngOnInit() 是組件獲取初始數據的好地方。
在 Angular 設置好輸入屬性之后設置組件。構造函數應該只把初始局部變量設置為簡單的值。請記住,只有在構造完成之后才會設置指令的數據綁定輸入屬性。如果要根據這些屬性對指令進行初始化,請在運行 ngOnInit() 時設置它們
在實例銷毀時進行清理 ngOnDestroy
這里是釋放資源的地方,這些資源不會自動被垃圾回收。如果你不這樣做,就存在內存泄漏的風險。
取消訂閱可觀察對象和 DOM 事件。
停止 interval 計時器。
反注冊該指令在全局或應用服務中注冊過的所有回調。
ngOnDestroy() 方法也可以用來通知應用程序的其它部分,該組件即將消失
頁面埋點
可以在組件中通過ngOnInit和ngOnDestroy方法統計頁面的時長,
更好的做法可以通過指令實現ngOnInit和ngOnDestroy生命周期,用來統計頁面時長
也可以通過路由守衛來記錄頁面出入棧所需要的時間
@Directive({selector: '[appSpy]'}) export class SpyDirective implements OnInit, OnDestroy { constructor(private logger: LoggerService) { } ngOnInit() { this.logIt(`onInit`); } ngOnDestroy() { this.logIt(`onDestroy`); } private logIt(msg: string) { this.logger.log(`Spy #${nextId++} ${msg}`); } }
使用變更檢測鉤子 ngOnchanges
一旦檢測到該組件或指令的輸入屬性發生了變化,Angular 就會調用它的 ngOnChanges() 方法。
因為這個方法會頻繁執行,所以要注意判斷的計算量,會影響性能。
// 可以監聽輸入屬性的變化 ngOnChanges(changes: SimpleChanges) { for (const propName in changes) { const chng = changes[propName]; const cur = JSON.stringify(chng.currentValue); const prev = JSON.stringify(chng.previousValue); this.changeLog.push(`${propName}: currentValue = ${cur}, previousValue = ${prev}`); } }
一、父子組件的通信(基礎)
父傳子
1、父組件通過屬性綁定向子組件傳值
import { Component } from '@angular/core'; @Component({ selector: 'app-parent', template: ` <app-child [msg]="msg"></app-child> ` }) export class ParentComponent { msg = '父組件傳的值'; }
2、子組件通過@Input接收數據
import { Component, Input } from '@angular/core'; @Component({ selector: 'app-child', template: ` <p>{{msg}}</p> ` }) export class ChildComponent { @Input() msg: String; }
子傳父
1、子組件通過自定義事件,向父組件發送數據
import { Component, Input, EventEmitter, Output} from '@angular/core'; , @Component({ selector: 'app-child', template: ` <p>{{msg}}</p> <button (click)="vote()" >發送</button> ` }) export class ChildComponent { @Input() msg: String; @Output() voted = new EventEmitter<boolean>(); vote() { this.voted.emit("子組件傳的值"); } }
2、父組件通過監聽自定義事件,接收子組件的傳值
import { Component } from '@angular/core'; @Component({ selector: 'app-parent', template: ` <app-child [msg]="msg" (voted)="voted($event)"></app-child> ` }) export class ParentComponent { msg = '父組件傳的值'; voted(val){ //監聽自定義事件的傳值 console.log(val) } }
子組件怎么監聽輸入屬性值的變化?(2種方法)
1、可以使用一個輸入屬性@Input()的 setter,以攔截父組件中值的變化。
import { Component, Input } from '@angular/core'; @Component({ selector: 'app-child', template: '<h4>"{{name}}"</h4>' }) export class ChildComponent { @Input() get name(): string { return this._name; } set name(name: string) { this._name = (name && name.trim()) || '<no name set>'; } private _name = ''; }
2、通過ngOnChange()來截聽輸入屬性值的變化
當需要監視多個、交互式輸入屬性的時候,本方法比用屬性的 setter 更合適。
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; @Component({ selector: 'app-version-child', template: ` <h4>Version {{major}}.{{minor}}</h4> <h5>Change log:</h5> <ul> <li *ngFor="let change of changeLog">{{change}}</li> </ul> ` }) export class VersionChildComponent implements OnChanges { @Input() major: number; @Input() minor: number; changeLog: string[] = []; ngOnChanges(changes: SimpleChanges) { //ngOnchange適合監聽子組件多個輸入屬性的變化 const log: string[] = []; for (const propName in changes) { const changedProp = changes[propName]; const to = JSON.stringify(changedProp.currentValue); if (changedProp.isFirstChange()) { log.push(`Initial value of ${propName} set to ${to}`); } else { const from = JSON.stringify(changedProp.previousValue); log.push(`${propName} changed from ${from} to ${to}`); } } this.changeLog.push(log.join(', ')); } }
父組件怎么讀取子組件的屬性和調用子組件的方法?(2種方法)
1、通過本地變量代表子組件
父組件不能使用數據綁定來讀取子組件的屬性或調用子組件的方法。但可以在父組件模板里,新建一個本地變量來代表子組件,然后利用這個變量來讀取子組件的屬性和調用子組件的方法,如下例所示。
思考:父組件可以通過這種方式讀取子組件的私有屬性和私有方法嗎?
父組件
import { Component } from '@angular/core'; import { CountdownTimerComponent } from './countdown-timer.component'; @Component({ selector: 'app-countdown-parent-lv', template: ` <h4>Countdown to Liftoff (via local variable)</h4> <button (click)="timer.start()">Start</button> //調用子組件方法 <button (click)="timer.stop()">Stop</button> <div class="seconds">{{timer.seconds}}</div> //讀取子組件屬性 <app-countdown-timer #timer></app-countdown-timer> `, styleUrls: ['../assets/demo.css'] }) export class CountdownLocalVarParentComponent { }
子組件
import { Component, OnDestroy } from '@angular/core'; @Component({ selector: 'app-countdown-timer', template: '<p>{{message}}</p>' }) export class CountdownTimerComponent implements OnDestroy { intervalId = 0; message = ''; seconds = 11; ngOnDestroy() { this.clearTimer(); } start() { this.countDown(); } stop() { this.clearTimer(); this.message = `Holding at T-${this.seconds} seconds`; } private clearTimer() { clearInterval(this.intervalId); } private countDown() { this.clearTimer(); this.intervalId = window.setInterval(() => { this.seconds -= 1; if (this.seconds === 0) { this.message = 'Blast off!'; } else { if (this.seconds < 0) { this.seconds = 10; } // reset this.message = `T-${this.seconds} seconds and counting`; } }, 1000); } }
2、父組件調用@viewChild() (基礎,推薦使用)
這個本地變量方法是個簡單便利的方法。但是它也有局限性(只能在模板html中使用),因為父組件-子組件的連接必須全部在父組件的模板中進行。父組件本身的ts代碼對子組件沒有訪問權。
當父組件類需要訪問子組件時,可以把子組件作為 ViewChild,注入到父組件里面。
父組件類中訪問子組件的屬性和方法:
import { AfterViewInit, ViewChild } from '@angular/core'; import { Component } from '@angular/core'; import { CountdownTimerComponent } from './countdown-timer.component'; //引入子組件 @Component({ selector: 'app-countdown-parent-vc', template: ` <h4>Countdown to Liftoff (via ViewChild)</h4> <button (click)="start()">Start</button> <button (click)="stop()">Stop</button> <div class="seconds">{{ seconds() }}</div> <app-countdown-timer></app-countdown-timer> `, styleUrls: ['../assets/demo.css'] }) export class CountdownViewChildParentComponent implements AfterViewInit { //通過 @ViewChild 屬性裝飾器,將子組件 CountdownTimerComponent 注入到私有屬性 timerComponent 里面。 @ViewChild(CountdownTimerComponent) private timerComponent: CountdownTimerComponent; seconds() { return 0; } // angular創建了組件的子視圖后會調用它,注意獲取子組件的屬性,要在子組件視圖加載之后 ngAfterViewInit() { // 訪問子組件屬性 setTimeout(() => this.seconds = () => this.timerComponent.seconds, 0); } start() { this.timerComponent.start(); } // 訪問子組件的方法 stop() { this.timerComponent.stop(); } }
注意:(使用場景很多,必須掌握)
ngAfterViewInit() 生命周期鉤子是非常重要的一步。被注入的計時器組件只有在 Angular 顯示了父組件視圖之后才能訪問,所以它先把秒數顯示為 0.
然后 Angular 會調用 ngAfterViewInit 生命周期鉤子,但這時候再更新父組件視圖的倒計時就已經太晚了。Angular 的單向數據流規則會阻止在同一個周期內更新父組件視圖。應用在顯示秒數之前會被迫再等一輪。
使用 setTimeout() 來等下一輪,然后改寫 seconds() 方法,這樣它接下來就會從注入的這個計時器組件里獲取秒數的值。
二、組件通過服務來通信(發布訂閱者模式,基礎,必須掌握)
父組件和它的子組件共享同一個服務,利用該服務在組件家族內部實現雙向通信。
不僅局限于父子組件,只要組件與組件共享同一個服務,就可以實現數據通信。
<!--parent.component.html--> <p style="width: 1000px;margin: auto"> <p class="card" style="width: 500px;float: left"> <p class="card-header"> 父組件</p> <p class="card-body"> <h6 class="card-title">父組件</h6> <p class="form-group"> <label for="serviceoutput">父組件服務輸入:</label> <input type="text" class="form-control" id="serviceoutput" placeholder="服務輸入" [(ngModel)]="serviceInput" > </p> <button class="btn btn-primary" (click)="clickService()">Service方式</button> </p> </p> <app-child></app-child> </p>
<!--child.component.html--> <p class="card" style="width: 500px;"> <p class="card-header">子組件</p> <p class="card-body"> <h6 class="card-title">子組件</h6> <p class="form-group"> <label for="serviceoutput">子組件服務輸入:</label> <input type="text" class="form-control" id="serviceoutput" placeholder="服務輸入" [(ngModel)]="serviceInput" > </p> <button class="btn btn-primary" (click)="clickService()">Service方式</button> </p> </p>
//服務重點 //meditor.service.ts import {Injectable} from '@angular/core'; import {Subject} from 'rxjs/Subject'; import {Observable} from 'rxjs/Observable'; @Injectable() export class MeditorService { private subject = new Subject<MeditorMsg>(); constructor() {} // 獲取訂閱者 public getObservable(): Observable<MeditorMsg> { return this.subject.asObservable(); } // 推送信息 public push(msg: MeditorMsg) { this.subject.next(msg); } } // 中間者信息 export interface MeditorMsg { id: string; body: any; }
subscription: Subscription = null; //初始化一個訂閱對象 //子組件構造函數,用于監聽數據推送 constructor(private meditor: MeditorService) { this.subscription = meditor.getObservable().subscribe( msg => { console.log(msg); if (msg.id === 'parent') { //id為parent,獲取父組件數據 this.serviceInput = msg.body; } } ); } // 子組件將數據推送到中間著,給訂閱者 clickService() { this.meditor.push({id: 'parent', body: this.serviceInput}); } //父組件構造函數,用于監聽數據推送 constructor(private meditor: MeditorService) { this.subscription = meditor.getObservable().subscribe( msg => { console.log(msg); if (msg.id === 'child') { //id為child,獲取子組件數據 this.serviceInput = msg.body; } } ); } // 父組件將數據推送到中間著,給訂閱者 clickService() { this.meditor.push({id: 'parent', body: this.serviceInput}); } // 注意:訂閱一個對象,就是在生命周期結束前,要取消訂閱。 ngOnDestroy() { this.subscription.unsubscribe(); }
思考: 這種發布訂閱者模式適合全局狀態管理嗎?
三、可以通過本地緩存來實現通信(Cookie,LocalStorage、SessionStorage)
<!-- catch_namae_type.ts --> // 目的是好維護 // 項目當中用到的頁面緩存,需要在這里進行聲明;key-value保持一致 // 聲明規則,不同類型的緩存使用前綴 session_/ local_ / cookie_ // 動態設置緩存不用在這里聲明,但是需要在key后面加'_noSetType_'標識 export const CatchNameType = { session_userInfo: 'session_userInfo', // 用戶信息 session_toekn: 'session_token', // token local_loginInfo: 'local_loginInfo', // 本地緩存用戶名密碼 }; <!--catch.ts--> import { Injectable } from '@angular/core'; // 定義這個類,主要是看全局定義了哪些本地緩存 import { CatchNameType } from './catch_namae_type'; // -------------------------------------------------------緩存工具類(三類方法) // Cookie (方法有:set/get/remove) // SStorage(sessionStorage) (方法有:set/get/remove/clear) // LStorage(localStorage) (方法有:set/get/remove/clear) @Injectable({ providedIn: 'root', }) export class Catch { // cookie public static Cookie = { /** * cookie 存貯 * @param key 屬性 * @param value 值 * @param String expire 過期時間,單位天 */ set(key: string, value: any, expire: any): void { if (Catch.is_set_catch_name_type(key)) { const d = new Date(); d.setDate(d.getDate() + expire); document.cookie = `${key}=${value};expires=${d.toDateString()}`; } }, get(key: string): string { const cookieStr = unescape(document.cookie); const arr = cookieStr.split('; '); let cookieValue = ''; // tslint:disable-next-line: prefer-for-of for (let i = 0; i < arr.length; i++) { const temp = arr[i].split('='); if (temp[0] === key) { cookieValue = temp[1]; break; } } return cookieValue; }, remove(key: string): void { document.cookie = `${encodeURIComponent(key)}=;expires=${new Date()}`; }, }; // sessionStorage public static SStorage = { set(key: string, value: any): void { if (Catch.is_set_catch_name_type(key)) { sessionStorage.setItem(key, JSON.stringify(value)); } }, get(key: string): any { const jsonString = sessionStorage.getItem(key) === 'undefined' ? undefined : sessionStorage.getItem(key); return jsonString ? JSON.parse(jsonString) : null; }, remove(key: string): void { sessionStorage.removeItem(key); }, clear(): void { sessionStorage.clear(); }, }; // localStorage public static LStorage = { set(key: string, value: any): void { if (Catch.is_set_catch_name_type(key)) { localStorage.setItem(key, JSON.stringify(value)); } }, get(key: string): any { const jsonString = localStorage.getItem(key) === 'undefined' ? undefined : localStorage.getItem(key); return jsonString ? JSON.parse(jsonString) : null; }, remove(key: string): void { localStorage.removeItem(key); }, clear(): void { localStorage.clear(); }, }; // 設置緩存的時候是否在catch_name_type里面聲明 static is_set_catch_name_type(key: string): boolean { let allow = false; // 對動態設置緩存不進行檢查 if (key.indexOf('_noSetType_') !== -1) { allow = true; console.log('動態設置緩存', key); return allow; } // 對命名規則進行檢查 const nameRule = key.indexOf('session_') !== -1 || key.indexOf('local_') !== -1 || key.indexOf('cookie_') !== -1; if (!nameRule) { allow = false; console.log('命名規則錯誤', key); return allow; } // 靜態設置的緩存需要配置類型 Object.values(CatchNameType).forEach((item) => { if (item === key) { allow = true; } }); if (!allow) { console.log('緩存操作失敗,請檢查配置緩存類型'); } return allow; } }
四、頁面路由傳參也可以實現單向通信
這部分內容,我會在路由章節整理。
組件通信總結
所以組件通信大概有如下幾種:
1、父子組件通信(1、@Input() @output 2、本地變量#val 3、@viewChild())
2、通過服務
3、頁面緩存
4、頁面級組件傳參(兩個頁面等同于兩個組件)
組件的模板不會永遠是固定的。應用可能會需要在運行期間按需加載一些新的組件。 通過下面的例子可以了解動態組件的基本使用
1、創建組件,被引入的組件
@Component({ template: `<span>hello world</span>` }) export class DynamicComponent { }
2、創建容器組件,用于加載動態組件
@Component({ selector: 'app-container', template: ` <div #dynamicContainer></div> <button (click)="createComponent()">Create</button> ` }) export class AppContainerComponent { // 聲明容器 @ViewChild("dynamicContainer", { read: ViewContainerRef }) container: ViewContainerRef; }
在AppContainerComponent組件中,通過@ViewChild裝飾器來獲取視圖中的模板元素,如果沒有指定第二個查詢參數,則默認返回 組件實例或相應的DOM元素,但這個示例中,我們需要獲取ViewContainerRef實例也就是視圖容器。可以在視圖容器中創建、插入、刪除組件等。
3、動態創建組件
在創建組件之前,需要注入ComponentFactoryResolver服務對象,該服務是動態加載組件的核心,可以將一個組件實例呈現到另一個 組件視圖上。
使用ComponentFactoryResolve將已聲明但未實例化的組件解析成為可以動態加載的component
//依賴組件類型獲取對應的factory,從名字上可以看出該factory是用來初始化組件的 const componentFactory = this.ComponentFactoryResolver(DynamicComponent); //調用視圖容器的createComponent方法并將組件添加到容器上。該方法內部會調用factory的create方法來初始化組件。 const modalContainerRef = this.container.createComponent(componentFactory);
4、為組件屬性賦值
通過如下的方式為組件屬性進行賦值
modalContainerRef.instance.property = ***;
5、銷毀組件
在使用組件后,記得要銷毀組件。
modalContainerRef.destroy();
6、組件注冊
為了能夠動態創建組件需要將組件在模塊的entryComponents中聲明。因為在entryComponents中聲明的組件Angular都會創建一個 ComponentFactory并將其存儲在ComponentFactoryResolver中,這是動態加載必需的步驟。
感謝你能夠認真閱讀完這篇文章,希望小編分享的“angular10組件的示例分析”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,更多相關知識等著你來學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。