iT邦幫忙

2021 iThome 鐵人賽

DAY 29
0
Modern Web

後轉前要多久系列 第 29

【後轉前要多久】# Day29 Angular - 各種ng指示(ngClass、ngIf、ngFor...)

  • 分享至 

  • xImage
  •  

這裡的ng並非電影電視中導演說太爛、要再拍一次的NG(No Good),
而是指Angular的ng。

指示 Directive

Angular中的Directive 直接翻譯是指令、指示,
我個人比較喜歡指示指引的翻譯,代表Angular看到這個特別的詞,要去做對應的事情或動作。
指令比較像在Terminal上做的輸入。

Angular中的Directive分成以下三種:

  • 元件型 Component Directive
  • 屬性型 Attribute Directive
  • 結構型 Structure Directive

這幾樣都是在樣板(.html檔案)中會看到的、HTML看不懂的東西,
是專門給Angular看的。


元件型指示 Component Directive

什麼是元件型指示?
指的就是元件啦!

ex: <app-root><app-component>

元件型指令

含有樣板的指示,以標籤(tag)形式呈現


屬性型 Attribute Directive

什麼是屬性型指示?
指的就是屬性、樣式啦!

修改套用該屬性的元素外觀、樣式、行為

屬性型指令有以下三種:

  • ngStyle
  • ngClass
  • ngModel

ngStyle

可以使用任意的CSS,搭配ngStyle動態的來修改Style

HTML

<div class="container">
    <h1 [ngStyle]="{'font-size': 26 + counter + 'px'}">{{counter}}</h1>
    <input type="button" value="計數器+按了會變大" (click)="count()">
</div>

TS

...
export class AppComponent {
    counter = 0;
    count(){
        this.counter++;
    }
}

計數器+按了會變大

也可以將Style變成一個方法(method),並移進TS中,透過點擊事件觸發

HTML

<div class="container">
    <h1 [ngStyle]="getStyle()">{{counter}}</h1>
    <input type="button" value="計數器+按了會變大" (click)="count()">
</div>

TS

export class AppComponent {
    counter = 0;
    count(){
        this.counter++;
    }
    
    getStyle(){
        return {('font-size': 26 + this.counter) + 'px'};
    }
}

動態樣式套用
甚至有更簡便不需要ngStyle的寫法

HTML

<div class="container">
    <h1 [style.font-size]="(26+counter)+'px'">{{counter}}</h1>
    <input type="button" value="計數器+按了會變大" (click)="count()">
</div>

如果要用[style]寫死固定數值的話,
要額外加上一個雙或單引號,使他變成一個JS物件
算是比較特別的用法。

HTML

<div class="container">
    <h1 [style.color]="'green'">標題</h1>
</div>

ngClass

動態套用class
藉由帶入布林值,讓符合條件值時動態新增class、不符合時則移除

[class.classname]="true"

HTML

<div class="container">
    <h1 [style.font-size]="(26+counter)+'px'" [style.color]='"green"'>{{counter}}</h1>
    <input type="button" value="計數器+按了會變大" (click)="count()">
    <p [ngClass]="{highlight: counter % 2 == 0}">偶數時會有螢光背景</p>
</div>

CSS

.highlight{
    background: yellow;
}

偶數時會有螢光背景

也可以改成簡短一點

HTML

<p [class.highlight]="counter % 2 == 0">偶數時會有螢光背景</p>

ngModel

[(ngModel)] = 'property'

就是前一篇提到的雙向繫結。


結構型 Structure Directive

什麼是結構型指示?
指的就是會影響到程式流的指令啦!

可以新增、刪除DOM元素來動態改變DOM結構

結構型指令有以下三種:
ngIf
ngSwitch
ngFor


ngIf

符合條件時會動態新增DOM、不符條件時動態移除(是移除而非隱藏)
若該元素被移除,若元素裡面有其他的tag或directive 也會一併被移除。
斬草除根。

HTML

<p *ngIf="counter % 2 == 0">偶數時整個DOM會被移除</p>

ngSwitch

HTML

<div class="container">
    <h1  [style.font-size]="(26+counter)+'px'" [style.color]='"green"'>{{counter}}</h1>
    <input type="button" value="計數器+按了會變大" (click)="count()">

    <div [ngSwitch]="counter % 3">
        <div *ngSwitchCase="1"><p>3N+1</p></div>
        <div *ngSwitchCase="2"><p>3N+2</p></div>
        <div *ngSwitchDefault><p>Default 三的倍數</p></div>
    </div>
</div>

switch在條件多時非常好用,
但好用歸好用,為了要用switch而一下子多出了兩層div,會造成網頁架構跑版、跟原本預期不同
這時可將<div> 改為 <ng-container>,就不會產生任何的HTML標籤。


ngFor

*ngFor="let item of list"

只能帶入list、set進行迭代,

我們先來產生一些學生姓名與分數的資料

TS

export class AppComponent {
    data = [
        {SID: 'S001', name: '王大明', score: 80, 'image-url': 'https://picsum.photos/id/10/200/300', 'self-intro': '<div>大家好,我是王大明。</div>'},
        {SID: 'S002', name: '林一二', score: 99, 'image-url': 'https://picsum.photos/id/20/200/300', 'self-intro': '<div>大家好,我是林一二<br>請各位多多指教。</div>'},
        {SID: 'S003', name: '黃阿道', score: 54, 'image-url': 'https://picsum.photos/id/30/200/300', 'self-intro': '<div>大家好,我是黃阿道<br>我成績不太好<br>請大家多包涵。</div>'},
    ];
    
    
    set = new Set([1, 1, 2, 3, 4, 5, 5, 5]);
}

透過取得TS裡的變數資料,自動產生重複性的結構

取得Set資料
HTML

<div class="container" *ngFor="let item of set;">
    <p>{{item}}</p>
</div>

取得List資料
HTML

<div class="container d-flex">
    <div class="student border border-dark m-5" id="student0" *ngFor="let item of data">
        <p>學號: {{item.SID}}</p>
        <p>姓名: {{item.name}}</p>
        <img [src]="item['image-url']" alt="大頭照">
        <p>分數: {{item.score}}</p>
        <p class="self-intro" [innerHTML]="item['self-intro']"></p>
    </div>
</div>

要取用image-urlself-intro資料時,沒辦法直接透過.來取得item底下的物件,
只能透過item['image-url']帶入Key來取值。

另外,
因為TS變數資料中的self-intro是HTML結構,
為了正常顯示出文字而用了屬性繫結在innerHTML屬性上。

如果要讓id也跟著做變化,除了透過帶入學號 id="{{item.SID}}" 以外,
也可帶入for迴圈中的index值 let item of data; let i=index
這邊可導出的變數index只能在ngFor裡面可使用,所以需要使用區域變數let來宣告值,讓i可在for迴圈中使用。

NgForOf provides exported values that can be aliased to local variables

HTML

<div class="student border border-dark m-5" id="student{{i}}" *ngFor="let item of data; let i=index">

更多可用的變數

另外在資料中多加一個人物叫hacker,填入一些惡意程式碼
Angular會過濾掉JS程式碼而不執行、預防XSS攻擊
TS

{SID: 'S999', name: 'Hacker', score: 1000000, 'image-url': 'https://picsum.photos/id/444/200/300', 'self-intro': '塞JS腳本<br><script>alert("hi");</script>'}

ngFor與pipe搭配使用

克難的做法:
透過pipe管線與ngFor,不寫任何TS邏輯,在HTML中使用for迭代印出物件
資料共八筆,每一排會呈現兩筆,共印了四排,但因為迭代的關係總共跑了八次迴圈

(通常會在TS裡面先做處理、不會這樣子寫)

HTML

<div class="container" *ngFor="let _ of list; let len = count; let count = index">
    <div>>>>第{{count+1}}次迴圈</div>
    <div *ngFor="let item of list | slice:count*2:count*2+2">
        <span>{{item}}</span>
    </div>
    <br>
</div>

TS

export class AppComponent {
    list = [
        '1',
        '22',
        '333',
        '4444',
        '55555',
        '666666',
        '7777777',
        '88888888',
    ];
}

如果精準的控制for迴圈次數,可搭配constructor

HTML

<div class="container">
    <div *ngFor="let _ of [].constructor(10); let i = index">
        {{i}}
    </div>
</div>

安全導覽運算 Safe Navigation Operator

當後端傳送的API資料不一定有該物件時,
想讀取有可能為undifinednull物件底下的值,又不希望因為取不到物件的值而讓整篇頁面掛掉。
(取到undifined、null物件都沒事,有問題的是當嘗試取undifined、null物件底下的child)

希望有則顯示、沒則忽略這個此不確定物件時,可以加上?.安全導覽運算子,
當遇到undifined、null物件的話,會直接return null,而不會做嘗試取值導致網頁崩潰。

首先來改一下資料格式,
新增一個info屬性來包住原本的SIDname

TS

export class AppComponent {
    data = [
        {info: {SID: 'S001', name: '王大明'}, score: 80, 'image-url': 'https://picsum.photos/id/10/200/300', 'self-intro': '<div>大家好,我是王大明。</div>'},
        {info: {SID: 'S002', name: '林一二'}, score: 99, 'image-url': 'https://picsum.photos/id/20/200/300', 'self-intro': '<div>大家好,我是林一二<br>請各位多多指教。</div>'},
        {info: {SID: 'S003', name: '黃阿道'}, score: 54, 'image-url': 'https://picsum.photos/id/30/200/300', 'self-intro': '<div>大家好,我是黃阿道<br>我成績不太好<br>請大家多包涵。</div>'},
    ];
}

這邊也相應修改一下取值方式

HTML

<div class="container d-flex">
    <div class="student border border-dark m-5" id="student0" *ngFor="let item of data">
        <p>學號: {{item.info.SID}}</p>
        <p>姓名: {{item.info.name}}</p>
        <img [src]="item['image-url']" alt="大頭照">
        <p>分數: {{item.score}}</p>
        <p class="self-intro" [innerHTML]="item['self-intro']"></p>
    </div>
</div>

到這邊都沒有問題。

但今天如果來了一個駭客,插入了一筆沒有帶info物件的資料
從那筆資料開始後面都無法正常顯示了

TS

export class AppComponent {
    data = [
        {info: {SID: 'S001', name: '王大明'}, score: 80, 'image-url': 'https://picsum.photos/id/10/200/300', 'self-intro': '<div>大家好,我是王大明。</div>'},
        {score: 1000000, 'image-url': 'https://picsum.photos/id/444/200/300', 'self-intro': '塞JS腳本<br><script>alert("hi");</script>'},
        {info: {SID: 'S002', name: '林一二'}, score: 99, 'image-url': 'https://picsum.photos/id/20/200/300', 'self-intro': '<div>大家好,我是林一二<br>請各位多多指教。</div>'},
        {info: {SID: 'S003', name: '黃阿道'}, score: 54, 'image-url': 'https://picsum.photos/id/30/200/300', 'self-intro': '<div>大家好,我是黃阿道<br>我成績不太好<br>請大家多包涵。</div>'},
    ];
}

此時只要在這info物件上加上?
就能忽略過沒有帶info物件的那格資料欄位,
讓後面的資料也能夠繼續顯示

HTML

<p>學號: {{item.info?.SID}}</p>
<p>姓名: {{item.info?.name}}</p>

上一篇
【後轉前要多久】# Day28 Angular - 四種資料繫結 Binding
下一篇
【後轉前要多久】# Day30 Angular - Pipe 管道元件
系列文
後轉前要多久30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言