Angular開發實踐(四):組件之間的交互

来源:https://www.cnblogs.com/laixiangran/archive/2018/03/24/8639257.html
-Advertisement-
Play Games

在Angular應用開發中,組件可以說是隨處可見的。本篇文章將介紹幾種常見的組件通訊場景,也就是讓兩個或多個組件之間交互的方法。根據數據的傳遞方向,分為父組件向子組件傳遞、子組件向父組件傳遞及通過服務傳遞三種交互方法。 ...


在Angular應用開發中,組件可以說是隨處可見的。本篇文章將介紹幾種常見的組件通訊場景,也就是讓兩個或多個組件之間交互的方法。

根據數據的傳遞方向,分為父組件向子組件傳遞子組件向父組件傳遞通過服務傳遞三種交互方法。

父組件向子組件傳遞

子組件通過@Input裝飾器定義輸入屬性,然後父組件在引用子組件的時候通過這些輸入屬性向子組件傳遞數據,子組件可通過setterngOnChanges()來截聽輸入屬性值的變化。

先定義兩個組件,分別為子組件DemoChildComponent父組件DemoParentComponent.

子組件:

@Component({
  selector: 'demo-child',
  template: `
    <p>{{paramOne}}</p>
    <p>{{paramTwo}}</p>
  `
})
export class DemoChildComponent {
    @Input() paramOne: any; // 輸入屬性1
    @Input() paramTwo: any; // 輸入屬性2
}

子組件通過@Input()定義輸入屬性paramOneparamTwo(屬性值可以為任意數據類型)

父組件:

@Component({
  selector: 'demo-parent',
  template: `
    <demo-child [paramOne]='paramOneVal' [paramTwo]='paramTwoVal'></demo-child>
  `
})
export class DemoParentComponent {
    paramOneVal: any = '傳遞給paramOne的數據';
    paramTwoVal: any = '傳遞給paramTwo的數據';
}

父組件在其模板中通過選擇器demo-child引用子組件DemoChildComponent,並通過子組件的兩個輸入屬性paramOneparamTwo向子組件傳遞數據,最後在子組件的模板中就顯示傳遞給paramOne的數據傳遞給paramTwo的數據這兩行文本。

通過 setter 截聽輸入屬性值的變化

在實際應用中,我們往往需要在某個輸入屬性值發生變化的時候做相應的操作,那麼此時我們需要用到輸入屬性的 setter 來截聽輸入屬性值的變化。

我們將子組件DemoChildComponent進行如下改造:

@Component({
  selector: 'demo-child',
  template: `
    <p>{{paramOneVal}}</p>
    <p>{{paramTwo}}</p>
  `
})
export class DemoChildComponent {
    private paramOneVal: any;
    
    @Input() 
    set paramOne (val: any) { // 輸入屬性1
        this.paramOneVal = val;
        // dosomething
    };
    get paramOne () {
        return this.paramOneVal;
    };
    
    @Input() paramTwo: any; // 輸入屬性2
}

在上面的代碼中,我們可以看到通過paramOne屬性的 setter 將攔截到的值val賦值給內部私有屬性paramOneVal,達到父組件傳遞數據給子組件的效果。當然,最重要的是,在 setter 裡面你可以做更多的其它操作,程式的靈活性就更強了。

通過ngOnChanges()來截聽輸入屬性值的變化

通過 setter 截聽輸入屬性值的變化的方法只能對單個屬性值變化進行監視,如果需要監視多個、互動式輸入屬性的時候,這種方法就顯得力不從心了。而通過使用 OnChanges 生命周期鉤子介面的 ngOnChanges() 方法(當組件通過@Input裝飾器顯式指定的那些變數的值變化時調用)就可以實現同時監視多個輸入屬性值的變化。

子組件DemoChildComponent新增ngOnChanges

@Component({
  selector: 'demo-child',
  template: `
    <p>{{paramOneVal}}</p>
    <p>{{paramTwo}}</p>
  `
})
export class DemoChildComponent implements OnChanges {
    private paramOneVal: any;
    
    @Input() 
    set paramOne (val: any) { // 輸入屬性1
        this.paramOneVal = val;
        // dosomething
    };
    get paramOne () {
        return this.paramOneVal;
    };
    
    @Input() paramTwo: any; // 輸入屬性2
    
    ngOnChanges(changes: {[propKey: string]: SimpleChange}) {
        for (let propName in changes) { // 遍歷changes
            let changedProp = changes[propName]; // propName是輸入屬性的變數名稱
            let to = JSON.stringify(changedProp.currentValue); // 獲取輸入屬性當前值
            if (changedProp.isFirstChange()) { // 判斷輸入屬性是否首次變化
                console.log(`Initial value of ${propName} set to ${to}`);
            } else {
                let from = JSON.stringify(changedProp.previousValue); // 獲取輸入屬性先前值
                console.log(`${propName} changed from ${from} to ${to}`);
            }
        }
    }
}

新增的ngOnChanges方法接收的參數changes是以輸入屬性名稱為鍵、值為SimpleChange的對象,SimpleChange對象含有當前輸入屬性是否第一次變化、先前值、當前值等屬性。因此在ngOnChanges方法中通過遍歷changes對象可監視多個輸入屬性值併進行相應的操作。

獲取父組件實例

前面介紹的都是子組件通過@Input裝飾器定義輸入屬性,這樣父組件可通過輸入屬性將數據傳遞給子組件。

當然,我們可以想到一種更主動的方法,那就是獲取到父組件實例,然後調用父組件的某個屬性或方法來獲取需要的數據。考慮到每個組件的實例都會添加到註入器的容器里,因此可通過依賴註入來找到父組件的示例

子組件獲取父組件實例相比於父組件獲取子組件實例(直接通過模板變數@ViewChild@ViewChildren獲取)要麻煩一些。

要在子組件中獲取父組件的實例,有兩種情況:

  • 已知父組件的類型

    這種情況可以直接通過在構造函數中註入DemoParentComponent來獲取已知類型的父組件引用,代碼示例如下:

    @Component({
      selector: 'demo-child',
      template: `
        <p>{{paramOne}}</p>
        <p>{{paramTwo}}</p>
      `
    })
    export class DemoChildComponent {
        paramOne: any;
        paramTwo: any;
    
        constructor(public demoParent: DemoParentComponent) {
    
            // 通過父組件實例demoParent獲取數據
            this.paramOne = demoParent.paramOneVal;
            this.paramTwo = demoParent.paramTwoVal;
        }
    }
  • 未知父組件的類型

    一個組件可能是多個組件的子組件,有時候無法直接知道父組件的類型,在Angular中,可通過類—介面(Class-Interface)的方式來查找,即讓父組件通過提供一個與類—介面標識同名的別名來協助查找。

    首先創建DemoParent抽象類,它只聲明瞭paramOneValparamTwoVal屬性,沒有實現(賦值),示例代碼如下:

    export abstract class DemoParent {
        paramOneVal: any;
        paramTwoVal: any;
    }

    然後在父組件DemoParentComponentproviders元數據中定義一個別名 Provider,用 useExisting 來註入父組件DemoParentComponent的實例,代碼示例如下:

    @Component({
      selector: 'demo-parent',
      template: `
        <demo-child [paramOne]='paramOneVal' [paramTwo]='paramTwoVal'></demo-child>
      `,
      providers: [{provider: DemoParent, useExisting: DemoParentComponent}]
    })
    export class DemoParentComponent implements DemoParent {
        paramOneVal: any = '傳遞給paramOne的數據';
        paramTwoVal: any = '傳遞給paramTwo的數據';
    }

    然後在子組件中就可通過DemoParent這個標識找到父組件的示例了,示例代碼如下:

    @Component({
      selector: 'demo-child',
      template: `
        <p>{{paramOne}}</p>
        <p>{{paramTwo}}</p>
      `
    })
    export class DemoChildComponent {
        paramOne: any;
        paramTwo: any;
    
        constructor(public demoParent: DemoParent) {
    
            // 通過父組件實例demoParent獲取數據
            this.paramOne = demoParent.paramOneVal;
            this.paramTwo = demoParent.paramTwoVal;
        }
    }

子組件向父組件傳遞

依然先定義兩個組件,分別為子組件DemoChildComponent父組件DemoParentComponent.

子組件:

@Component({
  selector: 'demo-child',
  template: `
    <p>子組件DemoChildComponent</p>
  `
})
export class DemoChildComponent implements OnInit {
    readyInfo: string = '子組件DemoChildComponent初始化完成!';
    @Output() ready: EventEmitter = new EventEmitter<any>(); // 輸出屬性
    
    ngOnInit() {
        this.ready.emit(this.readyInfo);
    }
}

父組件:

@Component({
  selector: 'demo-parent',
  template: `
    <demo-child (ready)="onReady($event)" #demoChild></demo-child>
    <p>
        <!-- 通過本地變數獲取readyInfo屬性,顯示:子組件DemoChildComponent初始化完成! -->
        readyInfo: {{demoChild.readyInfo}}
    </p>
    <p>
        <!-- 通過組件類獲取子組件示例,然後獲取readyInfo屬性,顯示:子組件DemoChildComponent初始化完成! -->
        readyInfo: {{demoChildComponent.readyInfo}}
    </p>
  `
})
export class DemoParentComponent implements AfterViewInit {
    // @ViewChild('demoChild') demoChildComponent: DemoChildComponent; // 通過模板別名獲取
    @ViewChild(DemoChildComponent) demoChildComponent: DemoChildComponent; // 通過組件類型獲取
    
    ngAfterViewInit() {
        console.log(this.demoChildComponent.readyInfo); // 列印結果:子組件DemoChildComponent初始化完成!
    }

    onReady(evt: any) {
        console.log(evt); // 列印結果:子組件DemoChildComponent初始化完成!
    }
}

父組件監聽子組件的事件

子組件暴露一個 EventEmitter 屬性,當事件發生時,子組件利用該屬性 emits(向上彈射)事件。父組件綁定到這個事件屬性,併在事件發生時作出回應。

在上面定義好的子組件和父組件,我們可以看到:

子組件通過@Output()定義輸出屬性ready,然後在ngOnInit中利用ready屬性的 emits(向上彈射)事件。

父組件在其模板中通過選擇器demo-child引用子組件DemoChildComponent,並綁定了一個事件處理器(onReady()),用來響應子組件的事件($event)並列印出數據(onReady($event)中的$event是固定寫法,框架(Angular)把事件參數(用 $event 表示)傳給事件處理方法)。

父組件與子組件通過本地變數(模板變數)互動

父組件不能使用數據綁定來讀取子組件的屬性或調用子組件的方法。但可以在父組件模板里,新建一個本地變數來代表子組件,然後利用這個變數來讀取子組件的屬性和調用子組件的方法。

在上面定義好的子組件和父組件,我們可以看到:

父組件在模板demo-child標簽上定義了一個demoChild本地變數,然後在模板中獲取子組件的屬性:

<p>
    <!-- 獲取子組件的屬性readyInfo,顯示:子組件DemoChildComponent初始化完成! -->
    readyInfo: {{demoChild.readyInfo}}
</p>

父組件調用@ViewChild()

本地變數方法是個簡單便利的方法。但是它也有局限性,因為父組件-子組件的連接必須全部在父組件的模板中進行。父組件本身的代碼對子組件沒有訪問權。

如果父組件的類需要讀取子組件的屬性值或調用子組件的方法,就不能使用本地變數方法。

當父組件類需要這種訪問時,可以把子組件作為 ViewChild,註入到父組件裡面。

在上面定義好的子組件和父組件,我們可以看到:

父組件在組件類中通過@ViewChild()獲取到子組件的實例,然後就可以在模板或者組件類中通過該實例獲取子組件的屬性:

<p>
    <!-- 通過組件類獲取子組件示例,然後獲取readyInfo屬性,顯示:子組件DemoChildComponent初始化完成! -->
    readyInfo: {{demoChildComponent.readyInfo}}
</p>
ngAfterViewInit() {
    console.log(this.demoChildComponent.readyInfo); // 列印結果:子組件DemoChildComponent初始化完成!
}

通過服務傳遞

Angular的服務可以在模塊註入或者組件註入(均通過providers註入)。

在模塊中註入的服務在整個Angular應用都可以訪問(除惰性載入的模塊)。

在組件中註入的服務就只能該組件和其子組件進行訪問,這個組件子樹之外的組件將無法訪問該服務或者與它們通訊。

下麵的示例就以在組件中註入的服務來進行父子組件之間的數據傳遞:

通訊的服務:

@Injectable()
export class CallService {
    info: string = '我是CallService的info';
}

父組件:

@Component({
  selector: 'demo-parent',
  template: `
    <demo-child></demo-child>
    <button (click)="changeInfo()">父組件改變info</button>
    <p>
        <!-- 顯示:我是CallService的info -->
        {{callService.info}}
    </p>
  `,
  providers: [CallService]
})
export class DemoParentComponent {
    constructor(public callService: CallService) {
      console.log(callService.info); // 列印結果:我是CallService的info
    }
    
    changeInfo() {
        this.callService.info = '我是被父組件改變的CallService的info';
    }
}

子組件:

@Component({
  selector: 'demo-child',
  template: `
    <button (click)="changeInfo()">子組件改變info</button>
  `
})
export class DemoChildComponent {
    constructor(public callService: CallService) {
        console.log(callService.info); // 列印結果:我是CallService的info
    }
    
    changeInfo() {
        this.callService.info = '我是被子組件改變的CallService的info';
    }
}

上面的代碼中,我們定義了一個CallService服務,在其內定義了info屬性,後面將分別在父子組件通過修改這個屬性的值達到父子組件互相傳遞數據的目的。

然後通過DemoParentComponent的providers元數據數組提供CallService服務的實例,並通過構造函數分別註入到父子組件中。

此時,通過父組件改變info按鈕子組件改變info按鈕在父組件或子組件中改變CallService服務的info屬性值,然後在頁面可看到改變之後對應的info屬性值。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 現象 在android開發中,經常會需要替換res\drawable中的圖片,打開res\layout下的文件預覽佈局頁面發現圖片已經被替換,但在模擬器或者真實機器上運行時發現該圖片並沒有被替換,還是使用的是原來的資源圖片。 原因 在開發過程中,由於使用模擬器測試了程式,在首次運行後會將res文件夾 ...
  • 最近在讀javascript高級程式設計,讀第十三章的時候有感。 開發中經常考慮的事情就是相容性,樣式相容,腳本相容等~~ 經常考慮的對象常為: DOM 與 IE (這裡的dom對象我理解為ie9,Firefox,chrome,safari,opera以上。IE則為ie8及以下) 首先介紹 事件流: ...
  • 上一章,咱們學瞭如何用webpack來打包css,壓縮js等。這一篇文章咱們來學習一下如何用webpack來處理圖片。廢話不多說,咱們開始吧。 首先,咱們隨便找一張你喜歡的圖片放到src/images目錄下,然後把圖片設置為背景圖片,代碼是這個樣子。 src/index.html: src/css/ ...
  • 上一篇文章留下了一些問題,如果你沒看過上一篇文章,可以在我的博客里查找,或者直接從這篇文章開始也是沒問題的。 這是上一篇文章中使用但是沒有詳細講解的代碼片段。現在我們來一一做一個瞭解。 entry:配置入口文件,也就是你想要打包的文件路徑。可以是單一的,也可以是多入口文件。下麵會詳細的講解。‘ ou ...
  • 1,HTML語言不區分大小寫 頁頭標簽: <title> </titile> 標題標簽: <hn> </hn> 可以使用屬性 align 例如align="right" hn中的n取值越大,顯示的字越小。 格式排版標簽: <br> 換行 <hr> 網頁水平線標簽 屬性:<hr align='"cen ...
  • 對於小白,對命令行工具使用得很少,而在學習vuejs框架時,命令行工具必不可少,因此,我對一些不懂安裝vuejs環境的小白寫如下教程: 1.vuejs是前端框架,環境藉助於nodo.js,因此,我們先要安裝node.js的環境。 2.百度{node.js},下載最新版本的nodejs環境,安裝完畢打 ...
  • canvas 畫圖,能夠選中所畫的圖片並且能夠隨意移動圖片 ...
  • 概念: W3C標準是一系列標準的集合,本質是結構標準語言,我們使用的html,css都要遵循這個標準。萬維網聯盟W3C創建於1994年,是Web技術領域最具權威和影響力的國際中立性技術標準機構。它有效促進了web技術相互之間的相容。 為什麼要遵守W3C標準呢?對於我們開發者來說,我們是介於瀏覽器製造 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...