###### 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> ```