Angular 從入坑到挖坑 – 組件食用指南

  • 2020 年 2 月 25 日
  • 筆記

一、Overview

angular 入坑記錄的筆記第二篇,介紹組件中的相關概念,以及如何在 angular 中通過使用組件來完成系統功能的實現

對應官方文檔地址:

配套程式碼地址:angular-practice/src/components-guide

二、Contents

  1. Angular 從入坑到棄坑 – Angular 使用入門
  2. Angular 從入坑到挖坑 – 組件食用指南

三、Knowledge Graph

四、Step by Step

4.1、組件與模板

4.1.1、組件的基礎概念

組件包含了一組特定的功能,每個組件的功能都單一且獨立,可以進行重複使用;組件可以通過 angular cli 進行創建,生成的組件位於工作空間的 src/app/ 路徑下面

## 創建一個 product-list 組件  ng g component product-list

當需要將組件放置在某個指定的目錄下時,可以直接在 ng g 命令中添加路徑

## 將 hero 組件生成到 components 路徑下  ng g component components/hero

angular 應用就是通過一個個的組件所構成的組件樹,一個組件包含了如下的四個部分

  • product-list.component.ts:組件類,用來處理數據和功能,為視圖呈現提供支援
  • product-list.component.html:組件對應的頁面 HTML 模板,用來呈現組件的功能
  • product-list.component.scss:只針對當前組件的樣式
  • product-list.component.spec.ts:當前組件的單元測試文件(非必須)

當通過命令行創建一個新的組件之後,會自動將新創建的組件註冊到應用的根模組(app.module.ts)中

在組件類中,通過使用 @Component 裝飾器 1 用來將類聲明為組件類,並為這個組件類配置一些元數據 2,以決定該組件在運行期間該如何處理、實例化和使用

裝飾器中存在三個基礎的配置參數,用來完成組件與視圖之間的關聯

  • selector:選擇器,當我們在頁面上添加了這個選擇器指定的標籤(<app-product-list></app-product-list>)後,就會在當前使用位置上創建並插入這個組件的一個實例
  • templateUrl:該組件所對應的 HTML 模板文件地址
  • styleUrls:該組件視圖所特有的 css 樣式文件地址

當需要使用這個組件時,直接在頁面上添加選擇器對應的標籤就可以了

4.1.2、模板綁定語法

在 angular 應用中,組件扮演著控制器或是視圖模型的作用,在創建組件時會關聯一個 html 文件,這個 html 文件則是一個基礎的 angular 模板文件

在這個模板文件中,可以通過 angular 內置的模板語法與 html 元素進行結合,從而告訴 angular 如何根據我們的應用邏輯和數據來渲染頁面

4.1.2.1、插值表達式

插值表達式可以將組件中的屬性值或者是模板上的數據通過模板表達式運算符進行計算,最終將值渲染到視圖頁面上

import { Component, OnInit } from '@angular/core';    @Component({    selector: 'app-product-list',    templateUrl: './product-list.component.html',    styleUrls: ['./product-list.component.scss']  })    export class ProductListComponent implements OnInit {      public title = '我是 title 屬性值';      constructor() { }      ngOnInit(): void {    }  }
<p>title:{{title}}</p>    <p>1+2+3+4+5={{1+2+3+4+5}}</p>

模板表達式的變數來源

  • 模板本身的變數
  • 指令的上下文變數
  • 組件的成員資訊(屬性 or 方法)

在使用模板表達式時,如果變數名在多個來源中都存在的話,則模板變數是最優先的,其次是指令的上下文變數,最後是組件的成員

在使用模板表達式時應該遵循如下的原則

  • 簡單:正常情況下,應該將業務邏輯或是數據運算放到組件中,模板表達式只作為屬性或方法的調用
  • 快速執行:模板表達式得出的數據應該快速結束,否則就會對於用戶體驗造成影響
  • 沒有可見的副作用:模板表達式只作為數據的展示,不應該改變任何的數據;應該構建出冪等的表達式,除非依賴的值發生變化,否則多次調用時,應該返回相同的數據資訊
4.1.2.2、模板綁定語法

通過數據綁定機制,將數據源與視圖進行綁定,從而實現源數據與用戶呈現的一致性

  • 從數據源到視圖:插值、組件中的屬性、dom 元素的 property 3、css 樣式、css 類
  • 從視圖到數據源:事件
  • 視圖與數據源之間的雙向綁定:數據對象

分類

語法

單向從數據源到視圖

1、插值表達式:{{expression}}2、使用 [] 進行綁定:<a [href]='expression'></a>3、使用 bind 進行綁定:<a bind-href='expression'></a>

單向從視圖到數據源

1、使用 () 進行綁定:<a (click)='statement'></a>2、使用 on 進行綁定:<a on-click='statement'></a>

雙向視圖到數據源;數據源到視圖

1、使用 [()] 進行綁定:<input type="text" [(ngModel)]="product.Name">2、使用 bindon 進行綁定:<input type="text" bindon-ngModel="product.Name">

import { Component, OnInit } from '@angular/core';    @Component({    selector: 'app-product-list',    templateUrl: './product-list.component.html',    styleUrls: ['./product-list.component.scss']  })    export class ProductListComponent implements OnInit {      public title = '我是 title 屬性值';      public styleProperty = '<b>我是包含 html 標籤的屬性</b>';      public fontColor = 'red';      public url = 'https://yuiter.com';      public name: string;      constructor() { }      ngOnInit(): void {    }      getUser() {      alert('111111111');    }  }
<h3>2.1、從數據源到視圖</h3>    <p>    <a href='{{url}}'>使用插值表達式進行綁定</a>  </p>  <p>    <a [href]='url' [style.color]='fontColor'>使用 [] 進行綁定</a>  </p>  <p>    <a bind-href='url'>使用 bind 進行綁定</a>  </p>  <p>    <span [innerHtml]="styleProperty"></span>  </p>    <h3>2.2、從視圖到數據源</h3>    <p>    <button (click)="getUser()">使用 () 進行綁定</button>  </p>  <p>    <button on-click="getUser()">使用 on 進行綁定</button>  </p>    <h3>2.3、數據雙向綁定 --- 需要在 AppModule 中添加對於 FormsModule 的引用</h3>    <p>    <input type="text" id="userName" [(ngModel)]="name">  </p>  <p>    <input type="text" bindon-ngModel="name">  </p>
4.1.3、數據綁定
  • 單向數據綁定
<p>{{title}}</p>
  • 雙向數據綁定
<input type="text" id="userName" [(ngModel)]="name">    <!--    當沒有 NgModel 時,雙向數據綁定等同於下面的寫法   -->  <input type="text" id="userName" [value]="name" (input)="name=$event.target.value">
4.1.4、屬性、樣式綁定
  • dom 元素的 property 綁定
<img [src]="productImageUrl">    <img bind-src="productImageUrl">
  • html 標籤的 attribute 綁定 attribute 綁定的語法類似於 property 綁定,由前綴 attr、點( . )和 attribute 名稱組成 attribute 綁定的主要用例之一是設置 ARIA attribute(給殘障人士提供便利)
<button [attr.aria-label]="actionName">{{actionName}} with Aria</button>
  • style 內聯樣式綁定
// 1、[style.width]="width" :string | undefined | null  public width = "100px";    //2、[style.width.px]="width":number | undefined | null  public width = "20";    // 3、[style]="styleExpr":string  public styleExpr = "width: 100px; color:red";    // 4、[style]="styleExpr":{key:value}  public styleExpr = {width: '100px', height: '100px'};    // 5、[style]="styleExpr":array  public styleExpr = ["width", "100px"];
  • class 屬性綁定
// 1、[class.foo]="hasFoo":bool | undefined | null  public hasFoo = true;    // 2、[class]="classExpr":string  public classExpr = "my-class1 my-class2";    // 3、[class]="classExpr":{key:value}  public classExpr= {my-class1:  true, my-class2: true};    // 4、[class]="classExpr":array  public classExpr= ["my-class1", "my-class2"];
4.1.5、事件綁定

在事件綁定中,可以通過 $event 參數獲取到 dom 事件對象的屬性從而獲取到模板資訊

<input type="text" (keyup)="getMsg($event)">  <p>輸入的值:{{msg}}</p>
import { Component, OnInit } from '@angular/core';    @Component({    selector: 'app-product-list',    templateUrl: './product-list.component.html',    styleUrls: ['./product-list.component.scss']  })    export class ProductListComponent implements OnInit {      public msg: string;      constructor() { }      ngOnInit(): void {    }      getMsg(event: KeyboardEvent) {      console.log(event);      this.msg = (event.target as HTMLInputElement).value;    }  }

通過使用 $event 作為方法的參數會將許多用不到的模板資訊傳遞到組件中,導致我們在僅僅是為了獲取數據的前提下,卻需要對於頁面元素十分了解,違背了模板(用戶所能看到的)與組件(應用如何去處理用戶數據)之間的關注點分類的原則。因此,這裡應該使用模板引用變數的方式獲取數據資訊。

模板引用變數是對模板中 DOM 元素的引用,提供了從模組中直接訪問元素的能力。

<input type="text" #refMsgInput (keyup)="getRefMsg(refMsgInput.value)">  <p>通過模板引入變數的方式獲取到輸入的值:{{refMsg}}</p>
import { Component, OnInit } from '@angular/core';    @Component({    selector: 'app-product-list',    templateUrl: './product-list.component.html',    styleUrls: ['./product-list.component.scss']  })    export class ProductListComponent implements OnInit {      public refMsg: string;      constructor() { }      ngOnInit(): void {    }      getRefMes(msg: string) {      this.refMsg = msg;    }  }

模板引用變數的作用域是整個模板,因此要確保一個模板中的引用變數名稱是唯一的,同時,在聲明引用變數時,也可以使用 ref- 代替 #

<input type="text" ref-refMsgInput (keyup)="getRefMsg(refMsgInput.value)">  <p>通過模板引入變數的方式獲取到輸入的值:{{refMsg}}</p>

4.2、指令

4.2.1、屬性型指令

屬性型指令被應用在視圖 dom 元素上,用來改變 dom 元素的外觀或行為

  • NgClass:用來設置元素的多個 css 類屬性,如果只設置一個 css 類,應該使用模板綁定語法中 class 類綁定
<p [ngClass]="inlineStyle">NgClass 屬性指令</p>
import { Component, OnInit } from '@angular/core';    @Component({    selector: 'app-product-list',    templateUrl: './product-list.component.html',    styleUrls: ['./product-list.component.scss']  })    export class ProductListComponent implements OnInit {      public inlineStyle: {};      constructor() { }      ngOnInit(): void {      this.setInlineStyle();    }      setInlineStyle() {      this.inlineStyle = {        'text-red': true,        'bg-blue': false,      };    }  }

這裡的 text-red、bg-blue 都是 css 類名,如果想要在指定的元素上添加該類,則 css 類名對應的值為 true,反之則為 false

  • NgStyle:用來設置元素的多個內聯樣式,如果只設置一個內聯樣式,應該使用模板綁定語法中的樣式綁定
<p [ngStyle]="currentStyles">NgStyle 屬性指令</p>
import { Component, OnInit } from '@angular/core';    @Component({    selector: 'app-product-list',    templateUrl: './product-list.component.html',    styleUrls: ['./product-list.component.scss']  })    export class ProductListComponent implements OnInit {      public currentStyles: {};      constructor() { }      ngOnInit(): void {      this.setCurrentStyles();    }      setCurrentStyles() {      this.currentStyles = {        'font-style': 'italic',        'font-weight': 'bold',        'font-size': '24px'      };    }  }

通過在組件的屬性中設置多個內聯樣式對象的形式,完成對於頁面元素樣式的批量設置

  • NgModel:雙向數據綁定
   <input type="text" id="userName" [(ngModel)]="name">   
4.2.2、結構型指令

結構型指令用來操作 dom 樹,通過進行一些的邏輯判斷,從而完成對於頁面布局的修改

  • NgIf:根據表達式的值(true or false)來創建或者銷毀 dom 元素
<p *ngIf="expr">NgIf 結構型指令</p>

當 expr 屬性為 true 時,這個元素則會顯示在頁面上,當屬性值為 false 時,則不顯示該元素 ngIf 指令並不是通過使用 css 樣式來隱藏元素的,當值為 false 時,則這些元素會從 dom 中被銷毀,並且所有監聽該 dom 元素的事件會被取消,當重新顯示該元素時,會重新執行初始化的過程 與銷毀元素不同,對於隱藏的元素來說,所有的元素監聽事件還會執行監聽的,再次顯示時不用重新進行初始化過程

  • NgFor:通過定義單條數據的顯示格式,angular 以此為模板,循環渲染出所有的數據
   <p *ngFor="let item of products; let i = index">{{i+1}} - {{item.name}} --- {{item.price}}</p>
import { Component, OnInit } from '@angular/core';    @Component({    selector: 'app-product-list',    templateUrl: './product-list.component.html',    styleUrls: ['./product-list.component.scss']  })    export class ProductListComponent implements OnInit {      public products = [{      'name': 'lalala',      'price': '$200'    }, {      'name': 'hehehe',      'price': '$400'    }, {      'name': 'wuwuwu',      'price': '$120'    }, {      'name': 'xixi',      'price': '$570'    }];      constructor() { }      ngOnInit(): void {    }  }  

NgFor 指令上下文中的 index 屬性在每次迭代中,會獲取到條數據的索引值 當渲染的數據發生改變時 4,會導致 dom 元素的重新渲染,此時可以採用 trackBy 的方式,通過在組件中添加一個方法,指定循環需要跟蹤的屬性值,此時當渲染的數據發生改變時,只會重新渲染變更了指定的屬性值的數據

<p>不使用 trackBy 跟蹤屬性</p>  <div>    <p *ngFor="let item of products; let i = index;">      {{i+1}} - {{item.name}} --- {{item.price}}    </p>  </div>  <p>使用 trackBy 跟蹤屬性</p>  <div>    <p *ngFor="let item of products; let i = index; trackBy: trackByIndex">      {{i+1}} - {{item.name}} --- {{item.price}}    </p>  </div>  <button (click)="addProduct()">新增</button>
import { Component, OnInit } from '@angular/core';    @Component({    selector: 'app-product-list',    templateUrl: './product-list.component.html',    styleUrls: ['./product-list.component.scss']  })    export class ProductListComponent implements OnInit {      public products = [{      'name': 'lalala',      'price': '$200'    }, {      'name': 'hehehe',      'price': '$400'    }, {      'name': 'wuwuwu',      'price': '$120'    }, {      'name': 'xixi',      'price': '$570'    }];      constructor() { }      ngOnInit(): void {    }      trackByIndex(index: number, item: any): string {      return item.price;    }      addProduct() {      this.products = [{        'name': 'lalala',        'price': '$200'      }, {        'name': 'hehehe',        'price': '$400'      }, {        'name': 'wuwuwu',        'price': '$120'      }, {        'name': 'xixi',        'price': '$570'      }, {        'name': 'lululu',        'price': '$' + (Math.random() * 100).toFixed()      }];    }  }     
  • NgSwitch:根據條件切換,從候選的幾個元素中選擇匹配的,放到 dom 元素中
<p>    請選擇配置    <select [(ngModel)]="config">      <option value="">請選擇</option>      <option value="r7-3700x">AMD Ryzen 7 3700X</option>      <option value="i5-9400f">Intel i5 9400F</option>      <option value="i5-9600kf">Intel i5 9600KF</option>    </select>  </p>  <p> 配置描述 </p>  <div [ngSwitch]="config">    <p *ngSwitchCase="'r7-3700x'">      一個能打得都木的~~~    </p>    <p *ngSwitchCase="'i5-9400f'">      擠牙膏的。。。    </p>    <p *ngSwitchCase="'i5-9600kf'">      別看了,我不是開封菜。。。    </p>    <p *ngSwitchDefault>      你選一個啊~~~    </p>  </div>
import { Component, OnInit } from '@angular/core';    @Component({    selector: 'app-product-list',    templateUrl: './product-list.component.html',    styleUrls: ['./product-list.component.scss']  })    export class ProductListComponent implements OnInit {      public config = '';      constructor() { }      ngOnInit(): void {    }  }     

NgSwitch 本身是一個屬性型指令,它不會直接操作 dom 元素,而是通過它所控制的兩個結構型指令(NgSwitchCase、ngSwitchDefault)來操作 dom 元素

4.3、管道

在使用模板表達式綁定數據時,可以使用管道對於表達式的結果進行轉換

管道是一種簡單的函數,它們接受輸入值並返迴轉換後的值。通過在模板表達式中使用管道運算符(|)則可以完成相應的結果轉換

4.3.1、模板表達式中的特殊運算符

angular 模板表達式是 javascript 的子集,相對於常見的 javascript 運算符,添加了三個特殊的運算符

  • 管道運算符 管道是一種特殊的函數,可以把運算符(|)左邊的數據轉換成期望呈現給視圖的數據格式,例如,將時間進行格式化、將數據轉換成 json 字元串的形式等等 可以針對一個數據使用多個管道進行串聯,並且管道運算符的優先順序比三元運算符( ?: )高 <h3>5.1、管道運算符</h3> <div> <p>產品資訊 json 字元串</p> {{products | json}} </div>
  • 安全導航運算符 在視圖中使用的屬性值為 null or undefined 時,javascript 和 angular 會引發空指針異常並中斷視圖的渲染過程, 從而視圖會渲染失敗,而使用了安全導航運算符(?)後,視圖依然會渲染,只是顯示的值為空白 <h3>5.2、安全導航運算符</h3> <p>第五個專案的名稱為:{{products[5].name}}</p>

<p>第五個專案的名稱為:{{products[5]?.name}}</p>

  • 非空斷言運算符 在 tsconfig.json 中啟用 strictNullChecks 屬性,typescript 將會強制開啟嚴格的空值檢查,在這種模式下,所有定義了類型的屬性是不允許賦值為 null 的,當將屬性賦值為 null,則會編譯報錯

非空斷言運算符用來告訴編譯器對特定的屬性不做嚴格的空值校驗,當屬性值為 null or undefined 時,不拋錯誤。在下面的程式碼中,在判斷 obj 存在後,就不再針對 obj.name 進行校驗

import { Component, OnInit } from '@angular/core';    interface Person {      name: string;      age: number;    }      @Component({      selector: 'app-product-list',      templateUrl: './product-list.component.html',      styleUrls: ['./product-list.component.scss']    })      export class ProductListComponent implements OnInit {        public obj: Person;        constructor() {      }        ngOnInit(): void {      }      }
<p *ngIf="obj">    <span>{{obj!.name}}</span>  </p>

非空斷言運算符不會防止出現 null 或 undefined,只是不提示

4.3.2、常用的管道函數
  • 純管道 只有在它檢測到輸入值發生了純變更時才會執行,但是會忽略對象內部的變更 純變更是指對原始類型值(String、Number、Boolean、Symbol)的更改, 或者對對象引用(Date、Array、Function、Object)的更改
  • 非純管道 每個組件的變更周期都會執行

管道

作用

JsonPipe

將一個值轉換成 json 字元串

DatePipe

根據區域設置規則格式化日期值

UpperCasePipe

把文本轉換成全大寫形式

LowerCasePipe

把文本轉換成全小寫形式

<h3>6.1、json 管道</h3>  <p>{{products | json}}</p>    <h3>6.2、date 管道</h3>  <p>現在時間:{{date | date:'yyyy-MM-dd HH:mm:ss'}}</p>    <h3>6.3、upper 管道</h3>  <p>轉換成全大寫:{{url | uppercase}}</p>    <h3>6.4、lower 管道</h3>  <p>轉換成全小寫:{{url | lowercase}}</p>

4.4、組件之間的通訊

4.4.1、輸入屬性與輸出屬性

輸入屬性(@Input)和輸出屬性(@Output)用來在父子組件或指令中進行共享數據。@Input 用來獲取數據,@Output 用來向外發送數據

4.4.2、子組件獲取父組件資訊
  • 在父組件中,添加對於子組件的引用,並將需要傳遞的數據 or 方法綁定到子組件上 傳遞數據直接將父組件中的屬性值賦值給綁定在子組件上的屬性就可以了 傳遞方法時,綁定在子組件上的屬性是父組件方法的名稱,此處不能加 () ,否則就會直接執行該父組件的方法 在傳遞數據給子組件時,也可以通過 this 來指代父組件,從而將整個父組件作為數據綁定子組件上
<h2>父組件內容:</h2>    <p>    <label for="title">標題:</label>    <input id="title" type="text" [(ngModel)]="title">  </p>    <hr>    <h2>子組件內容:</h2>    <!--    將父組件的數據綁定到子組件上   -->  <app-child-component [parentTitle]="title" [parentGetMsg]='getMsg'></app-child-component>   
  • 在子組件中引入 Inupt,同時使用 @Input 裝飾器來接收父組件傳遞的數據
// 引入 Input 介面  import { Component, OnInit, Input } from '@angular/core';    @Component({    selector: 'app-child-component',    templateUrl: './child-component.component.html',    styleUrls: ['./child-component.component.scss']  })  export class ChildComponentComponent implements OnInit {      // 獲取父組件的數據    @Input() parentGetMsg: any;      // 使用 setter 對父組件的數據進行深加工    private _title: string;    @Input()    set parentTitle(title: string) {      this._title = (title && title.trim()) || '父組件的 title 屬性值為空';    }    get parentTitle(): string {      return this._title;    }      constructor() { }      ngOnInit(): void {    }      runParentFunc() {      this.parentGetMsg();    }  }
<p>父組件的 title 屬性值:{{parentTitle}}</p>  <p>    <button (click)="runParentFunc()">調用父組件的方法</button>  </p>  

對於使用 @Input 裝飾器獲取到的父組件數據,可以通過輸入屬性中的 setter 方法中進行重新賦值

4.4.3、父組件獲取子組件資訊
  • 使用 @ViewChild 裝飾器獲取 在子組件上定義一個模板引用變數
<h2>父組件內容:</h2>    <h3>1、使用 @ViewChild 裝飾器獲取子組件數據</h3>    <p>    <button (click)="getChildMsg()">獲取子組件的 msg 數據</button>  </p>    <p>    <button (click)="runChildFunc()">調用子組件的方法</button>  </p>    <hr>    <h2>子組件內容:</h2>    <!--    在子組件上定義一個模板引用變數   -->  <app-child-component #childComponent></app-child-component>

在父組件中添加對於 ViewChild 的引用,然後使用 @ViewChild 裝飾器來接收子組件的 dom 資訊,從而獲取到子組件的數據或方法

// 引入 ViewChild  import { Component, OnInit, ViewChild } from '@angular/core';    @Component({    selector: 'app-parent-component',    templateUrl: './parent-component.component.html',    styleUrls: ['./parent-component.component.scss']  })  export class ParentComponentComponent implements OnInit {      // 通過 @ViewChild 裝飾器來接收字組件的 dom 資訊    @ViewChild('childComponent') child: any;      constructor() {    }      ngOnInit(): void {    }      getMsg() {      alert('我是父組件的 getMsg 方法');    }      getChildMsg() {      alert(this.child.msg);    }  }
  • 使用 @Output 裝飾器配合 EventEmitter 實現 在子組件中引入 Output 和 EventEmitter,通過 @Output 裝飾器定義一個事件觸發器,然後就可以通過這個事件觸發器的 emit 方法進行事件廣播
// 引入 Output、EventEmitter  import { Component, OnInit, Output, EventEmitter } from '@angular/core';    @Component({    selector: 'app-child-component',    templateUrl: './child-component.component.html',    styleUrls: ['./child-component.component.scss']  })  export class ChildComponentComponent implements OnInit {      public msg = 'child title';      // 定義一個事件觸發器    @Output() childEmitter = new EventEmitter<string>();      constructor() { }      ngOnInit(): void {    }      runParentFunc() {      this.parentGetMsg();    }      sendMsg() {      this.childEmitter.emit(this.msg);    }  }

當子組件進行事件廣播時,就可以通過在子組件上使用事件綁定的方式綁定到一個父組件事件,通過 $event 獲取到子組件傳遞的數據值

<h2>父組件內容:</h2>    <h3>2、使用 @Output 裝飾器配合 EventEmitter 獲取子組件數據</h3>    <p>{{childMsg}}</p>    <hr>    <h2>子組件內容:</h2>    <!--    將子組件的事件廣播綁定到父組件事件上   -->  <app-child-component (childEmitter)='childEmitMsg($event)'></app-child-component>

import { Component, OnInit } from '@angular/core';    @Component({    selector: 'app-parent-component',    templateUrl: './parent-component.component.html',    styleUrls: ['./parent-component.component.scss']  })  export class ParentComponentComponent implements OnInit {      public childMsg: string;      constructor() {    }      ngOnInit(): void {    }      childEmitMsg(event) {      this.childMsg = event;    }  }   
4.4.4、非父子組件之間的通訊

不管組件之間是否具有關聯關係,都可以通過共享一個服務的方式來進行數據交互,也可以將需要進行共享的數據存儲到一些存儲介質中,通過直接讀取這個存儲介質中的數據進行通訊

  • 創建一個服務,並添加到模組中
## 在 services/storage 路徑下創建一個 storage 服務  ng g service services/storage/storage  
import { BrowserModule } from '@angular/platform-browser';  import { NgModule } from '@angular/core';  import { AppRoutingModule } from './app-routing.module';  import { AppComponent } from './app.component';  import { ProductListComponent } from './product-list/product-list.component';  import { FormsModule } from '@angular/forms';  import { ParentComponentComponent } from './parent-component/parent-component.component';  import { ChildComponentComponent } from './child-component/child-component.component';    // 引入自定義的服務  import { StorageService } from './services/storage/storage.service';    @NgModule({    declarations: [      AppComponent,      ProductListComponent,      ParentComponentComponent,      ChildComponentComponent    ],    imports: [      BrowserModule,      AppRoutingModule,      FormsModule    ],    // 配置自定義的服務    providers: [StorageService],    bootstrap: [AppComponent]  })    export class AppModule { }   
  • 在組件中使用服務 在需要使用的組件中引入服務,然後在組件的構造函數中通過依賴注入的方式注入這個服務,就可以在組件中完成對於這個服務的使用 在父組件中對數據進行賦值,然後調用服務的方法改變數據資訊
import { Component, OnInit } from '@angular/core';    // 引入服務  import { StorageService } from '../services/storage/storage.service';    @Component({    selector: 'app-parent-component',    templateUrl: './parent-component.component.html',    styleUrls: ['./parent-component.component.scss']  })  export class ParentComponentComponent implements OnInit {      public msg = 'this is a service default value writen in parent component';      constructor(private storage: StorageService) {      this.storage.setMsg(this.msg);    }      ngOnInit(): void {    }      submit() {      this.storage.setMsg(this.msg);    }  }
<h2>父組件內容:</h2>    <h3>3、通過服務在屬性中共享數據</h3>    <p>    修改服務中的數據值    <input type="text" [(ngModel)]="msg">    <button (click)="submit()">提交</button>  </p>    <p>服務中的數據:{{msg}}</p>    <hr>    <h2>子組件內容:</h2>    <app-child-component></app-child-component>  

在子組件中引入服務,從而同步獲取到父組件修改後的服務中的數據資訊

import { Component, OnInit } from '@angular/core';    // 引入服務  import { StorageService } from '../services/storage/storage.service';    @Component({    selector: 'app-child-component',    templateUrl: './child-component.component.html',    styleUrls: ['./child-component.component.scss']  })  export class ChildComponentComponent implements OnInit {      public storageMsg: string;      constructor(private storage: StorageService) {    }      ngOnInit(): void {    }      getServiceMsg() {      this.storageMsg = this.storage.getMsg();    }  }

<button (click)="getServiceMsg()">獲取服務中的數據值</button>  <p>    服務中 msg 屬性值:{{storageMsg}}  </p>   

五、組件的生命周期鉤子函數

當 angular 在創建、更新、銷毀組件時都會觸發組件的生命周期鉤子函數,通過在組件中實現這些生命周期函數,從而介入到這些關鍵時刻

鉤子函數

觸發時機

ngOnChanges

被綁定的輸入屬性值發生變化時觸發,會調用多次;如果沒有使用到父子組件傳值,則不會觸發

ngOnInit

初始化組件時會調用一次,一般是用來在構造函數之後執行組件複雜的初始化邏輯

ngDoCheck

只要數據發生改變就會被調用

ngAfterContentInit

組件內容渲染完成後調用一次

ngAfterContentChecked

只要組件的內容發生改變就會被調用

ngAfterViewInit

視圖載入完成後觸發一次,一般用來對視圖的 dom 元素進行操作

ngAfterViewChecked

視圖發生變化時調用,在組件的生命周期中會調用多次

ngOnDestroy

只在銷毀組件時調用一次,一般用來在組件銷毀前執行某些操作

在組件載入過程中,會按照上面列出的鉤子函數順序,在組件的構造函數執行之後依次執行,在頁面載入過程中會涉及綁定數據的操作,因此會再次出發 ngDoCheck、ngAfterContentChecked、ngAfterViewChecked 這三個生命周期鉤子函數。後續只要頁面數據有發生改變,都會觸發這幾個事件


  1. 裝飾器是一種特殊類型的聲明,它能夠被附加到類聲明,方法, 訪問符,屬性或參數上,就像是 C# 中的特性
  2. 元數據是用來描述數據的數據項,例如這裡的 selector 是為了描述 Component 這個數據資訊資源中抽取出來用於說明其特徵的一個結構化的數據
  3. property 是 dom 元素默認的基本屬性,在 dom 初始化時會被全部創建,而 attribute 是 html 標籤上定義的屬性和值 =》DOM 中 Property 和 Attribute 的區別
  4. 這裡的數據改變指的是會將原來的數據對象重新銷毀然後重建的過程,因此像 push、unshift 這樣的方法即使不添加 trackBy 也不會重新渲染整個 DOM,只會重新渲染改變的數據