[Angular] Day7. Content projection

本章节将要介绍如何使用 ng-content 将一个 Component 的内容投影到另一个 Component 中,创建灵活且可被重复使用的 Component,来满足程序设计中 DRY (Don't repeat yourself) 的观念。

在 Angular 中 Content 是一种呈现的模式,可以在其中插入或投影另一个 Component 的内容,简单来说,当我们在写 HTML 时常常会有这样的结构:

<div>
	<p> Hello World!</p>
</div>

在 Angular 的概念中可以把它想像成,有一个 <p> tag 被投影到 <div> 之中,虽然不是原理可能不这样子,但是类似的模式。

在 Angular 中有几种常见的 ng-content 例子:

  • Signle-slot content projection: 投影来自单一 Source 的 Component 内容
  • Multi-slot content projection: 一个 Component 接收来自多个 Source 的 Component 内容

https://ithelp.ithome.com.tw/upload/images/20210821/20124767Bs01NpTA05.jpg


Single-slot content projection

在 Angular 的 ng-content 中最基本的就是 single-slot content projection,他是指将一个 Component 的内容投影在另一个 Component 之中,我们做一个简单的小例子来解释这个行为。

  1. 首先我们先使用 Angular CLI 建立一个 Component

    ng generate component zippy-basic
    ng g c zippy-basic
    
  2. 在 zippy-basic.component.html 中添加 ng-content 到你希望投影内容出现的位置

    <!-- zippy basic.component.html -->
    <h2>Single-slot content projection</h2>
    <div>
        ng-content content: 
        <ng-content></ng-content>
    </div>
    
  3. 接着我们在 app.component.html 中使用 zippy-basic 的 selector

    <!-- app.component.html -->
    <app-zippy-basic></app-zippy-basic>
    

https://ithelp.ithome.com.tw/upload/images/20210801/20124767DnHr1yIlhY.png

在画面中我们看到,我们使用 ng-content 的位置什麽都没有,是出了什麽 bug 吗?其实不是/images/emoticon/emoticon15.gif,是因为我们还没决定该把什麽内容投影在 的位置上,我们来更改一下 app.component.html 的内容

<!-- app.component.html -->
<app-zippy-basic>
    <p>From app.component.html projection content to zippy-basic component</p>
</app-zippy-basic>

https://ithelp.ithome.com.tw/upload/images/20210801/20124767EQg91a8KNp.png

当更改玩 app.component.html 的内容後,可以在画面中看到,Angular 把我们夹在 <app-zippy-basic> 的内容放到 <ng-content> 的位置了,
https://ithelp.ithome.com.tw/upload/images/20210801/20124767bUsgQrsaND.png

当然可能有人会问了,那...我直接把 <p> 放在 zippy-basic.component.html 不就好了吗?/images/emoticon/emoticon19.gif

<!-- zippy basic.component.html -->
<h2>Single-slot content projection</h2>
<div>
    ng-content content: 
    <p>From app.component.html projection content to zippy-basic component</p>
</div>

没有错,但是其实 ng-content 通常不是让你放 html tag 的,我们来举个例子,想像一下一个场景,当你在开发专案时,有没有遇到在一个地方需要放置三种不同类型的 button ? 如果是这样的话你是不是会这麽做

<div>
	<!-- style1 button -->
	<label>This is style 1 button</label>
	<p>Hello world</p>
	<button>button</button>
<div>
<div>
	<!-- style2 button -->
	<label> This is style 2 button </label>
	<p>Hello world</p>
	<button>button</button>
<div>
<div>
	<!-- style3 button -->
	<label> This is style 3 button </label>
	<p>Hello world</p>
	<button>button</button>
<div>

有没有发现当使用上面这种 HTML 结构虽然可以达到我们要的目的,但是 <label><p><button> 一直在重复出现但内容却是一样的,就违背了 DRY 的原则了,这时候我们就可以使用 ng-content 先将模板做好,再把不同 style 的 投影进去就好,来举个例子吧:

  1. 首先我们先建立一个模板 Component

    ng generate component content-template
    ng g c content-template
    
  2. 接着我们在 content-template.component.html 中把我们的模板写好

    <!-- content-template.component.html -->
    
    <h1>Content projection number {{styleCount}}</h1>
    <label>This is style {{styleCount}} button</label>
    <p>Hello world</p>
    <ng-content></ng-content>
    

    我们在原本放置不同 style 的地方使用 变成将别的内容投影到这个位置上,对了还记得昨天提到的 @Input( ) 吗? 这边我们来复习一下,使用 ng-content 一样可以由父层传递数据到子层喔

    import { Component, Input } from '@angular/core';
    
    @Component({
      selector: 'app-content-template',
      templateUrl: './content-template.component.html',
    })
    export class ContentTemplateComponent {
      @Input() styleCount: string = '';
      constructor() { }
    }
    
  3. 接着我们来更改一下 app.component.html 的内容,让不同 style 的 button 投影到 content-template 吧

    <!-- app.component.html -->
    <app-content-template [styleCount]="'1'">
        <button class="style1">Click Me</button>
    </app-content-template>
    
    <app-content-template [styleCount]="'2'">
        <button class="style2">Click Me</button>
    </app-content-template>
    
    <app-content-template [styleCount]="'3'">
        <button class="style3">Click Me</button>
    </app-content-template>
    

https://ithelp.ithome.com.tw/upload/images/20210801/20124767W7aNxciNY5.png

这样就完成了我们的目的,简单的几行就可以达到相同的目的,透过先建立模板再将不同样式或不同的 Component 投影进这个模板 Component 之中,就可以让这个模板 Component 在各个地方都被使用并且达到减少重复程序码的目的。


Multi-slot content projection

第二种其实与第一种非常相似,只不过变成了一个 Component 中投影了多个不同的内容,不过值得注意的是,由於是多个不同的内容投影在一个 Component 之中,所以就会有顺序位置问题,於是 Angular 提供了 select 属性让你加在 ng-content 上,让你可以将某一个内容放在指定的位置上,一样举个例子吧。

  1. 首先一样创建一个新的 Component

    ng generate component zippy-basic
    ng g c zippy-basic
    
  2. 接着我们在 zippy-basic.component.html 中添加 ng-content 到想要的位置

    <!-- zippy-basic.component.html -->
    
    <h2>Multi-slot content projection</h2>
    Default:
    <ng-content></ng-content>
    Question:
    <ng-content></ng-content>
    
  3. 接着我们把想要的位置添加一个 select 属性,让投影的位置固定在我们想要的地方

    <!-- zippy-basic.component.html -->
    
    <h2>Multi-slot content projection</h2>
    Default:
    <ng-content></ng-content>
    Question:
    <ng-content select="[question]"></ng-content>
    
  4. 最後我们在 app.component.html 中将我们像要投影的内容放进去,记得!指定位置的内容需要加上 select 的内容喔

    <!-- app.component.html -->
    
    <app-zippy-basic>
      <p question>Is content projection cool?</p>
      <p>Let's learn about content projection!</p>
    </app-zippy-basic>
    

https://ithelp.ithome.com.tw/upload/images/20210801/20124767IFrm7OvyYX.png

可以看到我们在 app.component.html 中 的顺序与 UI 呈现的顺序是颠倒的,就因为我们指定了有 question 这个 select 放置的地方。

与 Single-slot content projection 一样,Multi-slot content projection 的目的不在於放入原生 HTML 的 Tag,目的与上一个一样,都是可以先建立一个模板之後将不同的内容投影到这个模板中,只不过多了可以透过 select 让你选择放置的位置与让你可以放多个投影,这边就不在做一次练习了,有兴趣的话可以自己挑战一下利用 Multi-slot content projection 完成与上面的例子一样的内容。


结论

在本章中介绍了什麽是 content projection 以及该如何使用它,在官方文档中其实除了 Single-slot content projection 与 Multi-slot content projection 之外其实还有第三个常见的投影 Conditional content projection,但是因为他牵扯的概念跟本篇章使用 ng-content 比较不一样,他是透过使用 ng-template 来做到这个功能,所以就先不将它纳入本章的范围,这个方法会在之後介绍到。

下一篇将会分享 template 是什麽,他在 Angular 中是 HTML 的角色,而可以在 template 中使用许多语法来达到灵活建立画面的功能,那我们就下一篇再见吧。


Referece


<<:  【Day7】Vocoder Model 以及 WaveNet 介绍

>>:  Subtotal函数经典用法,以一敌十!

Day 03 - 行前说明 — 在 MVC & MVVM 的 UI 元件

相信网路上其实已经有不少文章在谈架构了,我的资历也尚浅,今天虽然会介绍架构,但是主要会侧重的点会是...

Android工程师说:只有「不当Android工程师」才是「好工程师」

开发Android需要经历些什麽样的心酸?...不是说iOS开发不会有心酸事,一想到「天线门」、「T...

Day 6 (CSS)

1.grid的justify-content水平对齐、align-content垂直对齐 =>...

DAY10-JAVA的类别(4)-建构元

建构元 在JAVA中,建构元所扮演的主要角色,是帮助新建立的物件设定初值。 修饰子 类别名称(型态1...

day28-认识一些不太熟的伪类选择器(中)

安安,今天继续讲伪类选择器欧!看书才发现...omg原来还有这麽多不知道的伪类选择器!!!原本以为一...