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

这里的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>

<<:  创建App-完结

>>:  【D30】结尾:佳肴上桌

User options 关於使用者体验

今天人还在外面游山玩水阿阿阿阿XDDD 来介绍一个比较无关资安与WEB技术, 纯粹是Burp Sui...

RISC-V: 指令解码器

这次只有针对 RV31I 指令做解码, 原本希望能让这次加入的 INSTRUCTION_DECODE...

Day 5: LeetCode 88. Merge Sorted Array

Tag:随意刷-[50-100] LeetCode Problem Source: 88. Merg...

Day 7 - Rancher 系统管理指南 - RKE Template

本文将於赛後同步刊登於笔者部落格 有兴趣学习更多 Kubernetes/DevOps/Linux 相...

初探网路安全(一):密码大小事,存在服务器的密码安全吗?

在每个要求你注册会员的新网站,都必须要想一组莫名复杂的密码,不但长度要够长、更要包含一堆有的没的字元...