Angular開發實踐(八): 使用ng-content進行組件內容投射

来源:https://www.cnblogs.com/laixiangran/archive/2018/04/05/8723166.html
-Advertisement-
Play Games

在Angular中,組件屬於特殊的指令,它的特殊之處在於它有自己的模板(html)和樣式(css)。因此使用組件可以使我們的代碼具有強解耦、可復用、易擴展等特性。通常的組件定義如下: demo.component.ts: demo.component.html: demo.component.scs ...


在Angular中,組件屬於特殊的指令,它的特殊之處在於它有自己的模板(html)和樣式(css)。因此使用組件可以使我們的代碼具有強解耦、可復用、易擴展等特性。通常的組件定義如下:

demo.component.ts:

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

@Component({
    selector: 'demo-component',
    templateUrl: './demo.component.html',
    styleUrls: ['./demo.component.scss']
})
export class DemoComponent implements OnInit {

    constructor() {
    }

    ngOnInit() {
    }
}

demo.component.html:

<div class="demo">
    <h2>
        demo-component - 我是一個簡單的組件
    </h2>
</div>

demo.component.scss:

.demo {
    padding: 10px;
    border: 2px solid red;

    h2 {
        margin: 0;
        color: #262626;
    }
}

此時我們引用該組件,就會呈現該組件解析之後的內容:

<demo-component></demo-component>

假設現在有這樣的需求,這個組件能夠接受外部投射進來的內容,也就是說組件最終呈現的內容不僅僅是本身定義的那些,那該怎麼做呢?這時就要請出本文的主角 ng-content

簡單投射

我們先從最簡單開始,在 demo.component.html 中添加 ,修改後的 demo.component.html 和 demo.component.scss 如下:

demo.component.html:

<div class="demo">
    <h2>
        demo-component - 可嵌入外部內容的組件
    </h2>
    <div class="content">
        <ng-content></ng-content>
    </div>
</div>

demo.component.scss:

.demo {
    padding: 10px;
    border: 2px solid red;

    h2 {
        margin: 0;
        color: #262626;
    }

    .content {
        padding: 10px;
        margin-top: 10px;
        line-height: 20px;
        color: #FFFFFF;
        background-color: #de7d28;
    }
}

為了效果展示特意將 所在的容器背景色定義為橙色。

這時我們在引用該組件時可以從外部投射內容,外部內容將在橙色區域顯示:

<demo-component>
    我是外部嵌入的內容
</demo-component>

針對性投射

如果同時存在幾個 ,那外部內容將如何進行投射呢?

我們先看個示例,為了區別,我再新增一個藍色區域的 ,修改後的 demo.component.html 和 demo.component.scss 如下:

demo.component.html:

<div class="demo">
    <h2>
        demo-component - 可嵌入外部內容的組件
    </h2>
    <div class="content">
        <ng-content></ng-content>
    </div>
    <div class="content blue">
        <ng-content></ng-content>
    </div>
</div>

demo.component.scss:

.demo {
    padding: 10px;
    border: 2px solid red;

    h2 {
        margin: 0;
        color: #262626;
    }

    .content {
        padding: 10px;
        margin-top: 10px;
        line-height: 20px;
        color: #FFFFFF;
        background-color: #de7d28;
        
        &.blue {
            background-color: blue;
        }
    }
}

引用該組件:

<demo-component>
    我是外部嵌入的內容
</demo-component>

此時,我們將看到外部內容投射到了藍色區域:

當然,如果你將橙色區域代碼放在藍色區域代碼的後面,那麼外部內容就會投射到橙色區域:

所以從上面的示例我們可以看出,如果同時存在簡單的 ,那麼外部內容將投射在組件模板最後的那個 中。

那麼知道這個問題,我們可能會想,能不能將外部內容有針對性的投射相應的 中呢?答案顯然是可以的。

為了處理這個問題,

直接看例子,修改後的 demo.component.html 和 demo.component.scss 如下:

demo.component.html:

<div class="demo">
    <h2>
        demo-component - 可嵌入外部內容的組件
    </h2>
    <div class="content">
        <ng-content></ng-content>
    </div>
    <div class="content blue">
        <ng-content select="header"></ng-content>
    </div>
    <div class="content red">
        <ng-content select=".demo2"></ng-content>
    </div>
    <div class="content green">
        <ng-content select="[name=demo3]"></ng-content>
    </div>
</div>

demo.component.scss:

.demo {
    padding: 10px;
    border: 2px solid red;

    h2 {
        margin: 0;
        color: #262626;
    }

    .content {
        padding: 10px;
        margin-top: 10px;
        line-height: 20px;
        color: #FFFFFF;
        background-color: #de7d28;

        &.blue {
            background-color: blue;
        }

        &.red {
            background-color: red;
        }

        &.green {
            background-color: green;
        }
    }
}

從上面代碼可以看到,藍色區域將接收 標簽 header 那部分內容,紅色區域將接收 class為"demo2"的div 的那部分內容,綠色區域將接收 屬性name為"demo3"的div 的那部分內容,橙色區域將接收其餘的外部內容(開始,我是外部嵌入的內容,結束)。

引用該組件:

<demo-component>
    開始,我是外部嵌入的內容,
    <header>
        我是外部嵌入的內容,我在header中
    </header>
    <div class="demo2">
        我是外部嵌入的內容,我所在div的class為"demo2"
    </div>
    <div name="demo3">
        我是外部嵌入的內容demo,我所在div的屬性name為"demo3"
    </div>
    結束
</demo-component>

此時,我們將看到外部內容投射到了指定的 中。

擴展知識

ngProjectAs

現在我們知道通過 ng-content 的 select 屬性可以指定外部內容投射到指定的 中。

而要能正確的根據 select 屬性投射內容,有個限制就是 - 不管是 標簽 headerclass為"demo2"的div還是 屬性name為"demo3"的div,這幾個標簽都是作為 組件標簽 的直接子節點

那如果不是作為直接子節點,會是什麼情況呢?我們簡單修改下引用 demo-component 組件的代碼,將 標簽header 放在一個div中,修改如下:

<demo-component>
    開始,我是外部嵌入的內容,
    <div>
        <header>
            我是外部嵌入的內容,我在header中
        </header>
    </div>
    <div class="demo2">
        我是外部嵌入的內容,我所在div的class為"demo2"
    </div>
    <div name="demo3">
        我是外部嵌入的內容demo,我所在div的屬性name為"demo3"
    </div>
    結束
</demo-component>

此時,我們看到 標簽 header 那部分內容不再投射到藍色區域中了,而是投射到橙色區域中了。原因就是 <ng-content select="header"></ng-content> 無法匹配到之前的 標簽 header,故而將這部分內容投射到了橙色區域的 <ng-content></ng-content> 中了。

為瞭解決這個問題,我們必須使用 ngProjectAs 屬性,它可以應用於任何元素上。具體如下:

<demo-component>
    開始,我是外部嵌入的內容,
    <div ngProjectAs="header">
        <header>
            我是外部嵌入的內容,我在header中
        </header>
    </div>
    <div class="demo2">
        我是外部嵌入的內容,我所在div的class為"demo2"
    </div>
    <div name="demo3">
        我是外部嵌入的內容demo,我所在div的屬性name為"demo3"
    </div>
    結束
</demo-component>

通過設置 ngProjectAs 屬性,讓 標簽header 所在的 div 指向了 select="header",此時 標簽 header 那部分內容有投射到藍色區域了:

<ng-content> 不“產生”內容

做個試驗

做個試驗,先定義一個 demo-child-component 組件:

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

@Component({
    selector: 'demo-child-component',
    template: '<h3>我是demo-child-component組件</h3>'
})
export class DemoChildComponent implements OnInit {

    constructor() {
    }

    ngOnInit() {
        console.log('demo-child-component初始化完成!');
    }
}

demo-component 組件修改為:

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

@Component({
    selector: 'demo-component',
    template: `
        <button (click)="show = !show">
            {{ show ? 'Hide' : 'Show' }}
        </button>
        <div class="content" *ngIf="show">
            <ng-content></ng-content>
        </div>
    `
})
export class DemoComponent implements OnInit {
    show = true;

    constructor() {
    }

    ngOnInit() {
    }
}

然後在 demo-component 中 投射 demo-child-component:

<demo-component>
    <demo-child-component></demo-child-component>
</demo-component>

此時,在控制台我們看到列印出 demo-child-component初始化完成! 這些文字。但是當我們點擊按鈕進行切換操作時,demo-child-component初始化完成! 就不再列印了,這意味著我們的 demo-child-component 組件只被實例化了一次 - 從未被銷毀和重新創建。

為什麼會出現這樣的情況呢?

出現原因

<ng-content> 不會 "產生" 內容,它只是投影現有的內容。你可以認為它等價於 node.appendChild(el) 或 jQuery 中的 $(node).append(el) 方法:使用這些方法,節點不被克隆,它被簡單地移動到它的新位置。因此,投影內容的生命周期將被綁定到它被聲明的地方,而不是顯示在地方。

這也從原理解釋了前面那個問題:如果同時存在幾個 ,那外部內容將如何進行投射呢?

這種行為有兩個原因:期望一致性和性能。什麼 "期望的一致性" 意味著作為開發人員,可以基於應用程式的代碼,猜測其行為。假設我寫了以下代碼:

<demo-component>
    <demo-child-component></demo-child-component>
</demo-component>

很顯然 demo-child-component 組件將被實例化一次,但現在假如我們使用第三方庫的組件:

<third-party-wrapper>
    <demo-child-component></demo-child-component>
</third-party-wrapper>

如果第三方庫能夠控制 demo-child-component 組件的生命周期,我將無法知道它被實例化了多少次。其中唯一方法就是查看第三方庫的代碼,瞭解它們的內部處理邏輯。將組件的生命周期被綁定到我們的應用程式組件而不是包裝器的意義是,開發者可以掌控計數器只被實例化一次,而不用瞭解第三方庫的內部代碼。

性能的原因 更為重要。因為 ng-content 只是移動元素,所以可以在編譯時完成,而不是在運行時,這大大減少了實際應用程式的工作量。

解決方法

為了讓組件能夠控制投射進來的子組件的實例化,我們可以通過兩種方式完成:在我們的內容周圍使用 <ng-template> 元素及 ngTemplateOutlet,或者使用帶有 "*" 語法的結構指令。為簡單起見,我們將在示例中使用 <ng-template> 語法。

demo-component 組件修改為:

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

@Component({
    selector: 'demo-component',
    template: `
        <button (click)="show = !show">
            {{ show ? 'Hide' : 'Show' }}
        </button>
        <div class="content" *ngIf="show">
            <ng-container [ngTemplateOutlet]="template"></ng-container>
        </div>
    `
})
export class DemoComponent implements OnInit {
    @ContentChild(TemplateRef) template: TemplateRef;
    show = true;

    constructor() {
    }

    ngOnInit() {
    }
}

然後我們將 demo-child-component 包含在 ng-template 中:

<demo-component>
    <ng-template>
        <demo-child-component></demo-child-component>
    </ng-template>
</demo-component>

此時,我們在點擊按鈕進行切換操作時,控制台都會列印出 demo-child-component初始化完成! 這些文字。

參考資源

ng-content: The hidden docs


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

-Advertisement-
Play Games
更多相關文章
  • 本文目錄:1.BEGIN...END2.true和false3.if結構4.case結構5.loop、leave和iterate6.repeat迴圈7.while迴圈 MySQL/MariaDB中的符合語句結構有:BEGIN...END,if,case,while,loop,repeat,curso ...
  • 搞開發多年,其實MySql前前後後安裝配置了無數次,但是每次都需要到網上搜教程,折騰半天才搞定,這次索性把整個過程全部記錄下來,以便以後查閱。 下載 到 "MySql官網" ,導航找到 DOWNLOADS Community MySQL Community Server 頁面,也可以直接 "點擊此處 ...
  • 本文目錄:1.游標說明2.使用游標3.游標使用示例 1.游標說明 游標,有些地方也稱為游標。它的作用是在一個結果集中逐條逐條地獲取記錄行並操作它們。 例如: 其中select是游標所操作的結果集,游標每次fetch一行中的name和age欄位,並將每一行的這兩個欄位賦值給變數var1和var2。 有 ...
  • 以前在進行搜索引擎rank-svm排序模型訓練時,直接使用python讀取的HDFS日誌文件、統計計算等預處理操作再進行svm模型,最終產生出訓練模型。現在回想一下,數據預處理這一塊完全可以使用spark進行,而且看起來更“正規一點”和高大上,並藉機接觸一下大數據。pyspark的安裝折騰了一上午, ...
  • 今天特意花白天的時間來寫畢設,終於把老師的第二次課結束了,還剩四次課。 昨天的bug說一下吧,居然是寫錯了一個方法對象,漏寫了個 ' r ' ,結果導致整個頁面都渲染不出來…… 寫代碼果然還是需要更多的耐心和細心才行。 其實昨天,老師還用到了一個parserbody中間件,是一個HTTP請求解析中間 ...
  • 寫作用域插槽之前,先介紹一下Vue中的slot內容分發: 如果<child-component></child-component>標簽之間沒有插入那兩個p標簽的話,頁面會顯示子組件模板中定義的“<p>父組件如果沒有插入內容,我將被顯示</p>”這一則內容,但如果<child-component>< ...
  • 由於某些不可描述的原因,俺的某個小項目要用客戶端桌面應用,後臺那還是 php 了。經廣大的群友指導,發現了 Electron 這個項目。它可以用 html, css, javascript 構建跨平臺的桌面應用程式,基於 nodejs 實現。 然而在安裝 Electron 時,卡死在了 npm in ...
  • 開發環境搭建完成。二、編譯部署1、項目路徑下demo輸入命令npm run build編譯完成後會發現在demo文件夾下多出一個dist文件夾這裡面就是編譯好的文件了。2、網上下載nginx,下載地址http://nginx.org/en/download.html,解壓下載的nginx文件。3、配 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...