# Angular - 結構型指令:使用 ngFor 及 ngIf
`ngFor`與`ngIf` 是屬於結構型指令的一種。
>結構型指令是透過新增和刪除 DOM 元素來更改 DOM 佈局的指令,使用簡寫語法時,Angular 在**一個元素上只允許有一個結構型指令**。
而 `ngFor` 以及`ngIf`很特別的是,他是可以直接撰寫在 html 當中的,這實在是很酷的一件事!
## ngFor
`ngFor`,類似於原生 JS 的 for 循環,主要用於遍歷某個集合,並且將其值渲染到 Template 上。
首先在閱讀 Angular 文檔時,你會看見寫法是 `*ngFor`,前方有加了一個星號,而這個星號其實是一個語法糖,在 Angular 看見這個符號後,會自動將其轉換為 `<ng-template>`。
### `<ng-template>`
`<ng-template>` 是一個模板元素,通常在使用結構型指令時才會產生作用。
>Angular's `<ng-template>` element defines a template that is not rendered by default. - Angular 官方。
### ngFor 的基本遍歷寫法
我們會使用 `<ul *ngFor = "let item of items"></ul>`,由這段寫法,我們可以遍歷 items 集合,來獲取每一個 item 的值。
### 取得更多的值
Angular 提供了一系列的值,讓我們可以在使用 `ngFor` 遍歷集合時取得。
以下值可透過 Angular 官方文檔查詢到。
1. index: number:可迭代物件中當前條目的索引。
2. count: number:可迭代物件的長度。
3. first: boolean:如果當前條目是可迭代物件中的第一個條目則為 true。
4. last: boolean:如果當前條目是可迭代物件中的最後一個條目則為 true。
5. even: boolean:如果當前條目在可迭代物件中的索引號為偶數則為 true。
6. odd: boolean:如果當前條目在可迭代物件中的索引號為奇數則為 true。
要注意的是:**first, last, even, odd 這些值都會得到布林值**,而不是內容。
## 使用資料綁定結合 ngFor,來創建一個 todoList
### 創建 HTML Template
```HTML=
<div>
<h1>TODO LIST</h1>
<div class="task__input">
<!-- 這一行我們有使用到 範本參考變數、雙向綁定 -->
<input #tTaskInput type="text" [(ngModel)]="taskTitle" />
<!-- 使用事件綁定來加入 task -->
<button (click)="handleAddTask()">add task</button>
<!-- 獲取範本參考變數的值 -->
<p>{{ tTaskInput.value }}</p>
</div>
<!-- 使用 *ngFor 來遍歷 tasks 陣列,並取得 task 項目內容以及編號還有是否為第一個的布林值 -->
<div *ngFor="let task of tasks; let i = index; let first = first">
<div class="task__item">
<span>{{ i + 1 }}</span>
<input type="checkbox" />
<!-- 使用內嵌綁定來取得 task 物件的 taskTitle -->
<span>{{ task.taskTitle }}</span>
<!-- 判斷是否為第一個 -->
<span>{{ first }}</span>
<!-- 使用事件綁定來移除任務 -->
<button (click)="handleRemoveTask(i)">remove</button>
</div>
</div>
</div>
```
### 在組件中撰寫邏輯
```javascript=
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
taskTitle: string = '';
/* 因為 Angular 是基於 TypeScript 所撰寫的,
所以在這裡先將 tasks 陣列中每一個 task 的物件型別給訂定,
後續也可以使用 interface 來定義物件型別*/
tasks: Array<{
taskTitle: string;
isCheck: boolean;
}> = [];
// 當按下新增按鈕時,會增加一個任務(tasks陣列會 push 一個任務物件進入)
handleAddTask() {
// 如果輸入值為空,則 return
if (!this.taskTitle.trim()) return;
this.tasks.push({ taskTitle: this.taskTitle, isCheck: false });
this.taskTitle = '';
}
// 當按下刪除按鈕時,先詢問是否刪除,是,則刪除任務
handleRemoveTask(index: any) {
const isRemove = window.confirm('remove task?');
if (!isRemove) return;
this.tasks.splice(index, 1);
}
}
```

## ngIf
`ngIf`,類似於原生 JS 的 If 判斷式,使用布林值來做判斷,根據 true,False,來判斷是否渲染到 Template 上。
注意:當 `ngIf` 返回 false 值時,該元素將從 DOM 中刪除,並且在刪除 HTML 元素時,作用域會被破壞,當元素重新放回檢視時,會建立一個新的作用域。(這部分可能會與 ngShow 來做比較)
### ngIf 與邏輯運算子
在 Angular 中,使用`ngIf`也可以加入 `&&`, `||`,`!` 這類的邏輯運算子來進行判斷。
```html=
<div *ngFor="let task of tasks; let i = index; let first = first">
<div class="task__item">
<span>{{ i + 1 }}</span>
<input type="checkbox" (change)="handleChecked(i)" />
<span>{{ task.taskTitle }}</span>
<!-- 使用 ngIf 來判斷是否任務已經打勾,若已經打勾則隱藏刪除按鈕 -->
<button *ngIf="!task.isCheck" (click)="handleRemoveTask(i)">
remove
</button>
</div>
</div>
```

### ngIf 與 else
原生 JS 當中,具有 `if...else` 語句,當然,`ngIf` 也有,使用方式如下:
```html=
<div *ngFor="let task of tasks; let i = index; let first = first">
<div class="task__item">
<span>{{ i + 1 }}</span>
<input type="checkbox" (change)="handleChecked(i)" />
<span>{{ task.taskTitle }}</span>
<!-- 如果任務已經完成,則刪除 remove 按鈕,並且顯示 #tDone 的標籤元素 -->
<button *ngIf="!task.isCheck; else tDone" (click)="handleRemoveTask(i)">
remove
</button>
<!-- ng-template 用於結構型指令,一般時候其內容並不會被顯示 -->
<ng-template #tDone>DONE</ng-template>
</div>
</div>
```
上方程式碼使用了 `*ngIf="!task.isCheck; else tDone"` 來判斷任務是否已經完成,若完成,則刪除 button,改為顯示 ` <ng-template #tDone>DONE</ng-template>`

## 參考資料
- [Angular - 範本參考變數(Template reference variables)](https://dotblogs.com.tw/H20/2018/05/08/143826)
- [Angular官方-ngFor](https://angular.tw/api/common/NgFor)
- [新新新手閱讀 Angular 文件 - ngFor(1) - Day19](https://ithelp.ithome.com.tw/articles/10267675)
- [How can I use "*ngIf else"?](https://stackoverflow.com/questions/43006550/how-can-i-use-ngif-else)
###### tags: `Angular`