###### tags: `Guide` `Component` `Pipe` `Directive` `Input` `Output` `Angular`
# 🌝Angular基礎教學03 #元件與資料傳遞
**主要內容如下**
1. 元件基本介紹
2. 元件間的資料傳遞
3. 管道與指令
**參數傳遞方式整理**
[🌝[T]Passing Parameters 參數傳遞](https://hackmd.io/@Ru/BkcCsfB2H)
## Component
[API參考手冊-Component](https://angular.tw/api/core/Component)
### Component介紹
#### *@Component.* 裝飾器
- 宣告的類別掛上`@Component`裝飾器,就表示此類別為一個元件。Angular的元件類別負責暴露資料,並透過資料繫結機制來處理絕大多數檢視的顯示和使用者互動邏輯。
- `@Component()`裝飾器是擴充`@Directive()`裝飾器,增加了一些與範本(`template`)有關的特性。
#### *@Component.* 資料配置
- `@Component`裝飾器內的配置,決定此元件會與哪個範本(`template`)建立關聯,與其他相關的程式,以下是`Component`基礎配置。
```typescript=
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-first',
templateUrl: './first.component.html',
styleUrls: ['./first.component.css']
})
export class FirstComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
```
:::info
💡**生命週期**
Angaulr在元件渲染範例時生命週期就開始了,生命週期是Angaulr為了應用程式能夠在特定的時機採取特定的行為所誕生的,例如: `ngOnInit`
:::
### Component使用
#### *selector.* CSS選擇器
- 這個 CSS 選擇器用於在範本(HTML)中標記出該指令,並觸發該指令的實例化。
==first.component.ts==
```typescript
@Component({
selector: 'app-first'
})
```
==second.component.html==
```htmlmixed
<app-first></app-first>
```
#### *template.* 範本
- Angular 元件的內聯範本。如果提供了它,就不要再用 templateUrl 提供範本了。
==first.component.ts==
```typescript
@Component({
template: '<p>first works!</p>'
})
```
#### *templateURL.* 範本路由
- Angular 元件範本檔案的 URL。如果提供了它,就不要再用 template 來提供內聯範本了。
==first.component.ts==
```typescript
@Component({
templateUrl: './first.component.html'
})
```
==first.html==
```htmlmix
<p>first works!</p>
```
#### *styles.*
#### *providers*
### Component補充
#### *single-page application.* SPA單頁應用
- Angular專案預設是從index.html建置,可以看到`index.html`的`<body></body>`內就只有`<app-root></app-root>`,Angular的專案配置是透過路由設計,由此一來我們的專案會是樹狀模組,不論哪個元件都會是顯示在`<app-root></app-root>`上,這樣的設計會使我們元件其實都是在同一頁做變換,稱單頁應用。
```htmlmixed=
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>App</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
</head>
<body>
<app-root></app-root>
</body>
</html>
```
## Input 與 Output
[API參考手冊-Input](https://angular.tw/api/core/Input)
[API參考手冊-Output](https://angular.tw/api/core/Output)
### Input & Oupput 介紹
- `@Input()` : 輸入屬性,通常為接收數據資料,也就是就是讓Parent將資料傳送到Child中使用。
- `@Output()` : 輸出屬性,通常提供事件給外部呼叫回傳使用,也就是讓Child將資料傳回Parent中使用。
### Input & Output 實作
[StackBlitz-Input&OutPut](https://stackblitz.com/edit/input-ouput-demo?file=src/app/app.component.ts)
#### *Goal1.* Input
>**Parent傳'Hello',Child接收並顯示**
>1. 建立一個child component並設計`@Input()`功能。
>2. 建立一個parent component使用child component,且使用child的`@Input()`。
*step1.* 建立Child component
==child.component.ts==
```typescript=
import { Component, Input, OnInit } from "@angular/core";
@Component({
selector: "app-child",
templateUrl: "./child.component.html",
styleUrls: ["./child.component.css"]
})
export class ChildComponent implements OnInit {
@Input() strShow: string;
constructor() {}
ngOnInit() {}
}
```
==child.component.html==
```htmlembedded=
<p>{{strShow}}</p>
```
*step2.* 建立Parent component
==parent.component.ts==
```typescript=
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-parent',
templateUrl: './parent.component.html',
styleUrls: ['./parent.component.css']
})
export class ParentComponent implements OnInit {
strInput = 'Hello';
constructor() { }
ngOnInit() {
}
}
```
==parent.component.html==
- 要使用child內的`@Input()`要在HTML內使用,且要用[中括號]括起來後面再帶入參數。
```htmlembedded=
<app-child [strShow]="strInput"></app-child>
```
#### *Goal2.* Output
>**Parent接收點擊按鈕後,由Child送出的'World'**
>1. 建立一個child component並設計`@Output()`功能。
>2. 建立一個parent component使用child component,且使用child的`@Output()`。
*step1.* 建立Child component
==child.component.ts==
- Output一定要搭配使用EventEimmter,這是固定寫法。
- emit(),就是發射訊息的意思。
```typescript=
import { Component, EventEmitter, OnInit, Output } from "@angular/core";
@Component({
selector: "app-child",
templateUrl: "./child.component.html",
styleUrls: ["./child.component.css"]
})
export class ChildComponent implements OnInit {
@Output() strWord = new EventEmitter();
constructor() {}
ngOnInit() {}
onClick() {
this.strWord.emit('World');
}
}
```
==child.component.html==
```htmlembedded=
<button (click)="onClick()">Output</button>
```
*step2.* 建立Parent component
==parent.component.ts==
```typescript=
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-parent',
templateUrl: './parent.component.html',
styleUrls: ['./parent.component.css']
})
export class ParentComponent implements OnInit {
constructor() { }
ngOnInit() {
}
showStrWord(strWord) {
console.log(strWord);
}
}
```
==parent.component.html==
- 要使用child內的`@Output()`要在HTML內使用,且要用(小括號)括起來後面再帶入參數。
- `showStrWord($event)`,這裡面的`$event`是emit的值,那$event也是固定寫法。
- 可以在devTools[F12] console 看到child傳送過來的值,確認是否正確。
```htmlembedded=
<app-child (strWord)="showStrWord($event)"></app-child>
```
## Pipe
[Angular文件-pipes](https://angular.tw/guide/pipes)
[API參考手冊-Pipe](https://angular.tw/api/core/Pipe)
### Pipe介紹
- `Pipe`用來對字串的顯示進行轉換和格式化(ex: 貨幣金額、日期...),就是我們常說的內存外顯。
- Angular內建Pipe
| Pipe | Describe |
| ------------- | -------- |
| DatePipe | 根據本地環境中的規則格式化日期值。 |
| UpperCasePipe | 把文字全部轉換成大寫。 |
| LowerCasePipe | 把文字全部轉換成小寫。 |
| CurrencyPipe | 把數字轉換成貨幣字串,根據本地環境中的規則進行格式化。 |
| DecimalPipe | 把數字轉換成帶小數點的字串,根據本地環境中的規則進行格式化。 |
| PercantPipe | 把數字轉換成百分比字串,根據本地環境中的規則進行格式化。 |
- 我們也可以自訂Pipe,用Angular CLI新增Pipe。
```npm
ng g p [PipeName]
```
### Pipe實作
#### *step1.* 建立Pipe
- 依自己的需求來做一支Pipe。
==*範本*== [StackBlitz-Pipe #phone function: use * hide word](https://stackblitz.com/edit/angular-ivy-tqraje?file=src/app/app.component.ts)
```typescript=
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'hideWordPipe'
})
export class HideWordPipe implements PipeTransform {
/**顯示末幾碼,其餘*遮罩
* @params value 畫面上的值
* @params viewLen 要顯示末幾碼(預設顯示末三碼)
*/
transform(value: string, viewLen = 3): any {
if (!value) {return null; }
const wordLen = value.length;
const hideLen = wordLen - viewLen;
let viewWord = '';
for (let i = hideLen; i > 0; i--) {
viewWord += '*';
}
viewWord += value.slice(hideLen);
return viewWord;
}
}
```
#### *step2.* 應用Pipe
- 要使用Pipe,必須在範本(HTML)中使用`[param] | [PiepName]`來表示,這邊的`PipeName`是裝飾器內的name。
==*Piep.*== `transform(value: string, arg?: any)`
```htmlembedded
<input type="text" [ngModel]="data.SendTo|hideWordPipe">
```
- 若`Pipe`是需要傳值的在範本(HTML)內用`: [arg]`。
==*Piep.*== `transform(value: string, arg1: any)`
```htmlembedded
<input type="text" [ngModel]="data.SendTo|hideWordPipe:3">
```
- 若`Pipe`需要傳多個值,一個值就用一個`: [arg]`表示。
==*Piep.*== `transform(value: string, arg1: any, arg2: any)`
```htmlembedded
<input type="text" [ngModel]="data.SendTo|hideWordPipe:3:3">
```
## Directive
[Angular文件-attribute-directives](https://angular.tw/guide/attribute-directives)
[Angular文件-structural-directives](https://angular.tw/guide/structural-directives)
[API參考手冊-Directive](https://angular.tw/api/core/Directive)
### Directive介紹
- 在Angular中有三種類型的指令
1. 元件: 擁有範本(HTML)的指令。
2. 結構型指令: 透過新增和移除 DOM 元素改變 DOM 佈局的指令。
3. 屬性型指令: 改變元素、元件或其它指令的外觀和行為的指令。
- 我們也可以自訂Directive,用Angular CLI新增Directive。
```npm
ng g d [DirectiveName]
```
### Directive實作
#### *step1.* 建立Directive
- 依自己的需求來做一支Directive。
==*範例*==[StackBlitz-Directive #email function : remove sharp brackets](https://stackblitz.com/edit/angular-ivy-fsoqqo?file=src/app/app.component.ts)
```typescript=
import { Directive, ElementRef, HostListener, Input } from '@angular/core';
import { NgControl } from '@angular/forms';
/**從信箱上複製的Email格式會有尖括號,此功能可以將<>拿掉
* ex: < email@gmail.com > -> email@gmail.com
*/
@Directive({
selector: '[appEmail]',
})
export class EmailDirective {
constructor(
private el: ElementRef,
private ngControl: NgControl
) { }
@HostListener('window:keyup', ['$event']) handleKeyUp() {
//
const isEmail = this.ngControl.name.toLowerCase().includes('email');
if (!isEmail) { return; }
//
const emailValue: string = this.el.nativeElement.value;
const a: number = emailValue.split('').findIndex(x => x === '<');
const b: number = emailValue.split('').findIndex(x => x === '>');
if (a !== -1 && b !== -1) {
this.ngControl.control.setValue(emailValue.substring(a + 1, b));
}
}
}
```
#### *step2.* 應用Directive
- 要使用Directive,必須在範本(HTML)中使用`[DirectiveName]`來表示,這邊的`DirectiveName`是裝飾器內的selector。
```htmlembedded
<input formControlName="EMail" appEmail>
```