這裡的ng
並非電影電視中導演說太爛、要再拍一次的NG(No Good),
而是指Angular的ng。
Angular中的Directive
直接翻譯是指令、指示,
我個人比較喜歡指示、指引的翻譯,代表Angular看到這個特別的詞,要去做對應的事情或動作。
指令比較像在Terminal上做的輸入。
Angular中的Directive分成以下三種:
Component Directive
Attribute Directive
Structure Directive
這幾樣都是在樣板(.html檔案)中會看到的、HTML看不懂的東西,
是專門給Angular看的。
什麼是元件型指示?
指的就是元件啦!
ex: <app-root>
、<app-component>
含有樣板的指示,以標籤(tag)形式呈現
什麼是屬性型指示?
指的就是屬性、樣式啦!
修改套用該屬性的元素外觀、樣式、行為
屬性型指令有以下三種:
ngStyle
ngClass
ngModel
可以使用任意的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>
動態套用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)] = 'property'
就是前一篇提到的雙向繫結。
什麼是結構型指示?
指的就是會影響到程式流的指令啦!
可以新增、刪除DOM元素來動態改變DOM結構
結構型指令有以下三種:ngIf
ngSwitch
ngFor
符合條件時會動態新增DOM、不符條件時動態移除(是移除而非隱藏)
若該元素被移除,若元素裡面有其他的tag或directive 也會一併被移除。
斬草除根。
HTML
<p *ngIf="counter % 2 == 0">偶數時整個DOM會被移除</p>
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="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-url
或self-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>'}
克難的做法:
透過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>
當後端傳送的API資料不一定有該物件時,
想讀取有可能為undifined
、null
物件底下的值,又不希望因為取不到物件的值而讓整篇頁面掛掉。
(取到undifined、null物件都沒事,有問題的是當嘗試取undifined、null物件底下的child)
希望有則顯示、沒則忽略這個此不確定物件時,可以加上?.
安全導覽運算子,
當遇到undifined、null物件的話,會直接return null
,而不會做嘗試取值導致網頁崩潰。
首先來改一下資料格式,
新增一個info
屬性來包住原本的SID
、name
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>