# Angular ## 架構圖 ![](https://i.imgur.com/pXiizdV.png) ### Module 模組 Angular 應用都有一個根模組,通常命名為 AppModule。根模組提供了用來啟動應用的引導機制。 一個應用通常會包含很多特性模組。 * Component 元件模組 * Service 服務模組 EX:Router * Value 數據模組 EX:數學常數 * Fn 函式模組 ### Metadata 元數據 元件的元數據告訴 Angular 從何處獲取創建和呈現元件及其檢視所需的主要構建基塊。特別是,它將範本與元件相關聯,直接與內聯代碼關聯,或通過引用關聯。元件及其範本共同描述檢視。 除了包含或指向範本之外,元數據還配置(例如)如何在 HTML 中引用元件以及它需要哪些服務。 ```typescript @Component({ selector: 'app-hero-list', templateUrl: './hero-list.component.html', providers: [ HeroService ] }) export class HeroListComponent implements OnInit { /* . . . */ } ``` | 配置選項 | 詳 | | -------- | -------- | | selector | 一個 CSS 選擇器,它告訴 Angular 在範本 HTML 中找到相應標記的位置創建並插入此元件的實例。例如,如果應用程式的 HTML 包含 ,則 Angular 會在這些標記之間插入檢視的實例。<app-hero-list></app-hero-list>HeroListComponent | |templateUrl|此元件的 HTML 範本的模組相對位址。或者,您可以內聯提供 HTML 範本作為屬性的值。此範本定義元件的宿主檢視。template| |providers | 元件所需服務的提供程式數位。在示例中,這告訴 Angular 如何提供元件的構造函數用於獲取要顯示的英雄清單的實例。HeroService| ### 數據繫結 ![](https://i.imgur.com/Qk90xkz.png) 數據屬性值與屬性綁定一樣從元件流向輸入框。使用者的更改也會流回元件,將屬性重置為最新值,就像事件綁定一樣 * 文字插植 (Text Interpolation) * 事件繫結 (Event Binding) * 屬性繫結 (Property/Attribute Binding) * 樣式繫結 (Style Binding) * 類別繫結 (Class Binding) ### Directive 指令 當Angular渲染時,它會根據指令給出的指令轉換DOM 除了元件之外,還有另外兩種類型的指令:**結構(星號)**和**屬性**。Angular 定義了這兩種類型的許多指令,您可以使用裝飾器定義自己的指令。 指令的元數據將修飾類與用於將其插入 HTML 的元素相關聯 ### injector 注射 >相依注入 * Services : 對於與特定檢視無關並希望跨元件共享的資料或邏輯,可以建立服務類別。 服務類別的定義通常緊跟在 “@Injectable()” 裝飾器之後。該裝飾器提供的元資料可以讓你的服務作為依賴被注入到客戶元件中。 ![](https://i.imgur.com/GJDBajj.png) Angular 發現某個元件依賴某個服務時,它會首先檢查是否該注入器中已經有了那個服務的任何現有實例。如果所請求的服務尚不存在,注入器就會使用以前註冊的服務提供者來製作一個,並把它加入注入器中,然後把該服務返回給 Angular。 當所有請求的服務已解析並返回時,Angular 可以用這些服務實例為引數,呼叫該元件的建構函式。 ## 簡報 ### 專案架構 * node_modules : 已下載的套件 * e2e : E2E 測試 (End-to-End Testing) * src : * app : * app.component.ts : 應用的根元件定義邏輯,名為 AppComponent * app.component.html : 根元件 AppComponent 關聯的 HTML 範本 * app.component.css : 定義根元件 AppComponent 的 CSS 樣式表 * app.component.spec.ts : 根元件 AppComponent 單元測試 * app.module.ts : AppModule 根模組 * favicon.ico : 網站圖示 * environments : 環境設定檔 * index.html : 起始頁面 * main.ts : Angular bootstrap 的程式進入點 * polyfills.ts : 要符合 IE 或舊版瀏覽器時須做設定 * styles.scss : 整個網頁應用程式共用的scss設定檔 * test.ts : 單元測試進入點 * .editorconfig : Coding style * browserslist : 如果要支援 IE 9–11,根據此檔案的設定來加上 CSS 的前綴 * karma.conf.js : Karma 的設定檔 * angular.json : Angular CLI 執行 啟動、編譯、測試、程式碼功能等等的設定 * package.json : 定義使用的套件 * package-lock.json : 提供 npm 安裝套件資訊 * yarn.lock : 提供 yarn 安裝套件資訊 * tsconfig.app.json : TypeScript 設定檔 * tslint.json : TSLint 設定檔 ### 渲染過程 1. main.ts 2. app.module.ts 3. app.component.ts 4. index.html ### 生命週期 ![](https://i.imgur.com/y3Vsca9.png) 1. construction : 負責注入服務 2. ngOnChanges : 觸發時間點 1. 在ngOnInit前呼叫 2. 觀察外部屬性傳入的變化,單純對記錄參考位置的物件型別做修改,是不會再次觸發。 傳入參數值 SimpleChange 物件 ```typescript= { previousValue : 變化前的數值, CurrentValue : 變化後的數值, firstChange : 是否是首次變更 } ``` 3. ngOnInit : 初始化元件屬性值 4. ngDoCheck : 自訂變更檢測 5. ngAfterContenInit : 帶有@ContentChild 裝飾器屬性,會在這時取得元素實體,且只會執行一次。 6. ngAfterContenCheck : 緊跟ngAfterContenInit的檢測。 7. ngAfterViewInit : 帶有@ViewChild 裝飾器屬性,會在頁面載入時取得元素實體,且只會執行一次。 p.s 可以設定@ViewChild第二個選擇性參數內的static屬性,直接在ngOnInit就取得頁面元素實體 ```typescript= @ViewChild(頁面元素,{static:true}) ``` 8. ngAfterViewCheck : 緊跟ngAfterViewInit的檢測。 ### Module 描述應用的各個部分如何組織在一起 裝飾器 : @NgModule() * Metadata * declarations —— 該應用所擁有的元件。 * imports — 匯入 BrowserModule 以獲取瀏覽器特有的服務,比如 DOM 渲染、無害化處理和位置(location)。 * providers — 各種服務提供者。 * bootstrap — 根元件,Angular 建立它並插入 index.html 宿主頁面。 ### Component 元件控制螢幕上被稱為檢視的一小片區域。 裝飾器 : @Component * Metadata * selector:是一個 CSS 選擇器,它會告訴 Angular,一旦在範本 HTML 中找到了這個選擇器對應的標籤,就建立並插入該元件的一個實例。 比如,如果應用的 HTML 中包含 <app-hero-list></app-hero-list>,Angular 就會在這些標籤中插入一個 HeroListComponent 實例的檢視。 * templateUrl:該元件的 HTML 範本檔案相對於這個元件檔案的地址。 另外,你還可以用 template 屬性的值來提供內聯的 HTML 範本。 * styleUrls : 定義頁面樣式外部檔案路徑清單 * implements 可引用不同的生命週期Function ### Directive 擴增或改變特定頁面元素的功能 裝飾器 : @Directive() * Metadata * selector:選擇器名稱放入[]之間,代表以屬性方式使用。 可以將HTML Tag 或Angular元件名稱放在前面,限制可以使用的範圍 EX : div[選擇器名稱] p.s. 其實元件也是指令的一種,只是加上了頁面及樣式的處理 ``` $ ng generate directive 指令名稱 [參數] ``` * 屬性指令 : 改變DOM元素外觀或行為 * 自訂屬性外觀: @ViewChild : 取得宿主元素 componrnt.ts ```typescript= @Component({ selector: 'my-app', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], }) export class AppComponent implements OnInit { @ViewChild('button1', { static: true }) button: CustomButtonDirective; @ViewChild('button2', { static: true }) button2: CustomButtonDirective; ngOnInit(): void { this.button.changeColor('red'); this.button2.changeColor('green'); } } ``` directive.ts exportAs : 決定指令實體的公開名稱 ```typescript= @Directive({ selector: '[appCustomButton]', exportAs: 'customButton1', }) export class CustomButtonDirective implements OnInit { constructor(private elRef: ElementRef, private renderer: Renderer2) {} ngOnInit(): void { this.renderer.setStyle(this.elRef.nativeElement, 'padding', '10px'); this.renderer.setStyle(this.elRef.nativeElement, 'fontSize', '14pt'); this.renderer.setStyle(this.elRef.nativeElement, 'fontWeight', 'bold'); } changeColor(color: string): void { this.renderer.setStyle(this.elRef.nativeElement, 'color', color); } } ``` html ```htmlmixed= <button type="button" #button1="customButton1" appCustomButton> 套用指令的按鈕 </button> <button type="button" #button2="customButton1" appCustomButton> 套用指令的按鈕 </button> ``` 結果 : ![](https://i.imgur.com/dNmzcgz.png) * 自訂屬性行為: 裝飾器 : @HostListener component.ts ```typescript= @Component({ selector: 'my-app', templateUrl: './app.component.html', styleUrls: [ './app.component.css' ] }) export class AppComponent { onConfirm(): void { alert('確定'); } } ``` directive.ts ```typescript= @Directive({ selector: 'button[appButtonConfirm]' }) export class ButtonConfirmDirective { @Input('appButtonConfirm') message!: string; @Output() confirm = new EventEmitter<void>(); @HostListener('click', ['$event']) clickEvent(event: Event) { event.preventDefault(); event.stopPropagation(); if (confirm(this.message)) { this.confirm.emit(); } } } ``` html ```htmlmixed= <button type="button" 傳入參數=傳入參數值 (自訂監聽器)="執行方法">按鈕</button> <button type="button" appButtonConfirm="是否確認?" (confirm)="onConfirm()">按鈕</button> ``` [參考](https://stackblitz.com/edit/ng-book-attribute-directive-confirm) * <span id="StructuralDirective">結構指令<span> : 新增、移除、替換DOM元素,皆以*開頭 * [*ngFor](#*ngFor) : 使用陣列資料和樣板範本,重複渲染 * [*ngIf](#*ngIf) : 條件為真,才顯示畫面 * [*ngSwitch](#*ngSwitch) : 多個條件,條件為真,顯示需要的畫面 * [*ngComponentOutlet](#*ngComponentOutlet) : 動態元件載入 * [*ngTemplateOutlet](#*ngTemplateOutlet) : 封裝頁面樣本 P.S 不允許一個元素有多個結構指令,解決方式可以使用ng-container 當static 為true,則@ViewChild 在ngOnInit() 無法取得實體 * 自訂結構指令 TemplateRef : 取得`<ng-template>`內容 ViewContainer : 可控制宿主元素的檢視容器 limit.directive.ts ```typescript= @Directive({ selector: '[appLimit]' }) export class LimitDirective { @Input() appLimit!: string; } ``` limit-case.directive.ts ```typescript= @Directive({ selector: '[appLimitCase]' }) export class LimitCaseDirective { private hasView = false; @Input() set appLimitCase(value: string) { const condition = value === this.limit.appLimit; if (condition && !this.hasView) { this.viewContainer.createEmbeddedView(this.templateRef); this.hasView = true; } else if (!condition && this.hasView) { this.viewContainer.clear(); this.hasView = false; } } constructor( private viewContainer : ViewContainerRef, private templateRef: TemplateRef<Object>, @Optional() @Host() private limit: LimitDirective ) { } } ``` html ```htmlmixed= <ng-container [appLimit]="userLimit"> <span>目前的權限:{{ userLimit }}</span> <div class="item" *appLimitCase="'user'">這個區塊只有使用者看得到</div> <div class="item" *appLimitCase="'admin'">這個區塊只有管理員看得到</div> <div class="item">這個區塊全部使用者都看得到</div> </ng-container> ``` [參考](https://stackblitz.com/edit/ng-book-structural-directive) * @HostListener : 擷取宿主元件的使用者操作事件 * @HostBinding : 繫結宿主元素的屬性 ## Pipe(管道) : 在需要轉化的對象後使用管道符號(**|**),再緊接著呼叫要使用的通道 * JsonPipe : 用Json格式檢視物件資料 ```htmlmixed= <pre>{{ 物件資料 | json }}</pre> ``` * CasePipe : 轉化文字大小寫 * Title : 首字大寫 * Lower : 全部小寫 * Upper : 全部大寫 ```htmlmixed= <div>{{ 字串 | titlecase }}</div> <div>{{ 字串 | lowercase }}</div> <div>{{ 字串 | uppercase }}</div> ``` * SlicePipe : 限制顯示長度(可用於字串和陣列) ```htmlmixed= <div>{{字串 | slice: 起始位置:結束位置}}</div> <app-task *ngFor="let 陣列Item of 陣列 | slice: 起始位置:結束位置" [task]="task"></app-task> ``` * KeyValuePipe : 物件陣列化 ```typescript= export class AppComponent { task = new Task({ TaskSn: '001', TaskName: '待辦事項 A', State: 'Finish' }); columnDesc = { TaskSn: '編號', TaskName: '名稱', State: '狀態', }; } ``` ```htmlmixed= <table border="1"> <tr> <th>欄位</th> <th>內容</th> </tr> <tr *ngFor="let t of task | keyvalue"> <td>{{ columnDesc[t.key] }}</td> <td>{{ t.value }}</td> </tr> </table> ``` 結果 ![](https://i.imgur.com/yJk5Eev.png) * DecimalPipe : 轉換數值資料 格式化字串 ```htmlmixed= {整數最小位數}.{小數最小位數}-{小數最大位數} ``` ```htmlmixed= <div>{{需要轉換數值 | number : '{整數最小位數}.{小數最小位數}-{小數最大位數}':'UniCode本地環境識別符號編號'}}</div> ``` p.s UniCode本地環境識別符號編號設定,預設為en-US * PercentPipe : 百分比資料顯示 跟DecimalPipe 格式化字串一樣 ```htmlmixed= <div>{{需要轉換數值 | percent : '{整數最小位數}.{小數最小位數}-{小數最大位數}':'UniCode本地環境識別符號編號'}}</div> ``` * CurrencyPipe : 貨幣資料顯示 格式化字串 ```htmlmixed= <p>B: {{b | 'currency:ISO 4217 貨幣碼':'貨幣指示格式':'{整數最小位數}.{小數最小位數}-{小數最大位數}':'UniCode本地環境識別符號編號'}}</p> ``` * DatePipe : 日期資料顯示 時區 +0000 UTC時間 ```htmlmixed = <p>{{JS Date物件 | date:'預設格式代稱':"時區"}}</p> <p>{{JS Date物件 | date:'格式化字串':"時區"}}</p> EX : <p>{{ dateObj | date:'medium' }}</p> // output is 'Jun 15, 2015, 9:43:11 PM' <p>{{ dateObj | date:'mm:ss' }}</p> // output is '43:11' ``` * 自訂Pipe 裝飾器 : @Pipe pipe.ts ```typescript= @Pipe({ name: 'orderBy', }) export class OrderByPipe implements PipeTransform { transform(list: any[], prop: string): any[] { return list.sort((a, b) => a[prop] > b[prop] ? 1 : a[prop] === b[prop] ? 0 : -1 ); } } ``` html ```htmlmixed= <app-task *ngFor="let task of tasks | orderBy: 'State'" [task]="task"></app-task> ``` 結果 ![](https://i.imgur.com/8aVtFPO.png) ## Service(服務) : 封裝商業邏輯實作 透過依賴注入在元件建立且使用 裝飾器: @Injectable : 定義服務可注入 ### Singleton : 獨體設計模式 應用程式啟動時,會為每個模組建立容器(注入器 injector),會將帶有@Injectable的服務,實體化後註冊到容器裡。 也就是說一個模組裡,所以元件所注入的服務都是同一個 p.s. Angular編譯時,會透過@Injectable 將未使用的依賴對象給<span id="Re-tree-shaking">[**tree-shaking**](#tree-shaking)<span>排除 ### 抽換服務 為了符合[SOLID 五大原則](/xw49lg2XQFejCElXTocSJg)的封閉開放原則 提供了4種設定方式 * useClass : ```typescript= @NgModule({ providers: [{ provite : 模組使用的服務名稱, useClass: 參考的服務對象 }] }) ``` * useExising 不會建立新的實體,會去使用已存在的,如果不存在則會拋出例外 ```typescript= @NgModule({ providers: [{ provite : 模組使用的服務名稱, useExisting: 參考的服務對象 }] }) ``` * useValue 可直接更換物件實體,可使用在測試 ```typescript= @NgModule({ providers: [{ provide : 服務 usVaiue : { 服務方法:() => 回傳值} }] }) ``` * useFactory : 可使用判斷來選擇要使用的服務 ```typescript= @NgModule({ providers: [{ provide : 服務, useFactory:(服務變數: 需要使用的服務)=>{ return 判斷式? 服務A:服務B }, deps: [需要使用服務] }] }) ``` #### 自訂依賴注入令牌(DI Token) 配置物件中 provide屬性就是依賴注入令牌 * 類別類型:元件或服務 * 字串類型: 使用@Inject裝飾器來注入 ```typescript= { porvide:'令牌名稱' useValue: 值 } constructor( @Inject(令牌名稱) public 變數名稱: 值的類型 ) ``` #### InjectionToken 令牌 利用InjectionToken型別來達成元件依賴抽象介面,進一步符合[SOLID 五大原則](/xw49lg2XQFejCElXTocSJg)的介面隔離原則 * interface.ts 服務介面 利用**InjectionToken** 泛行型別變數來定義令牌 ```typescript= export const 常數名稱 = new InjectionToken<介面名稱>(描述); export interface 介面名稱 { 介面內容 } ``` * service.ts ```typescript= @Injectable({ providedIn: 'root', }) export class 服務名稱 implements 服務介面 { constructor() {} 實作介面及擴充 } } ``` * module.ts ```typescript= @NgModule({ ..., providers: [{ provide: 常數名稱, useClass: 服務名稱 }], ... }) ``` * component.ts ```typescript= constructor(@Inject(常數名稱) public 服務名稱: 服務介面) {} ``` #### 多種提供者 如果設定多個,會以最後一個為主 EX : 以BClass為主 ```typescript= { provide: A, useClass: AClass }, { provide: A, useClass: BCalss } ``` 但是以multi屬性,就能使Angular此令牌設定為多個提供者 EX :  * a/a.module.ts ```typescript= { provide: 'i18nFolder', useValue : 'a-module', multi: true } ``` * b/b.module.ts ```typescript= { provide: 'i18nFolder', useValue : 'b-module', multi: true } ``` * i18n.service.ts ```typescript= @Injectable({ providedIn: 'root', }) export class I18nService { i18nFolder: string[]; constructor(@Inject('i18nFolder') folders: string[]) { this.i18nFolder = folders.map((folder) => `assets/${folder}`); } } ``` * component.html ```htmlmixed= <h3>多個 i18n 資料夾設定</h3> <pre>{{ i18nService.i18nFolder | json }}</pre> ``` * 結果 ![](https://i.imgur.com/cBswwfk.png) ## Router: 路由 網址結構 ``` 通訊協定://(網域orIP):(埠號Port)/路由路徑/:路由變數/查詢字串 https://ng-book-router-param-snapshot.stackblitz.io/task/form/002 ``` 利用路由機制來實作業面的切換 創建專案時,選擇需要路由機制,則會自動建立AppRouterModule模組, 所有路由設定皆在這裡設定 app-routing.module.ts ```typescript= const routes: Routes = [ { path: 路由位置, component: 使用哪個元件 }, { path: '', component: MainPageComponent },//預設頁面 { path: 'tasks', pathMatch: 'full', redirectTo: 'task/list' },//轉址路由 { path: 'task/list', component: TaskPageComponent }, { path: 'task/form', component: TaskFormPageComponent }, { path: '**', component: NotFoundPageComponent }//萬用路由 ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule], }) export class AppRoutingModule {} ``` RouterModule * forRoot() : 應用程式根路由,一個專案只會有一個定義,通常用在AppModule * forChild() : 使用在其他特性群組 利用RouterOutlet 指令來決定顯示位置 ```htmlmixed= <router-outlet></router-outlet> ``` ### 萬用路由定義(Wildcard route) 當找不到路由時,將會直接導向此路由,如不設定時,找不到路由會報錯。 且因Angular 會先選擇符合的路由,所以此設定必須是最後一個。 ```typescript= { path: '**', component: 指定的Component } ``` ### 轉址路由定義 利用**redirectTo**屬性指定在特別路徑下轉址到其他路徑中。 ```typescript= { path: 'tasks', pathMatch: 'full', redirectTo: 'task/list' } ``` ### routerLink 指令 Angular使用routerLink指令來進行頁面切換。 ```htmlmixed= <a routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }" > 首頁 </a> <a [routerLink]="['task', 'list']" routerLinkActive="active">工作事項</a> <a [routerLink]="['404']" routerLinkActive="active">Not Found ``` * routerLinkActive :讓導覽頁顯示當下路由位置 * routerLinkActiveOptions : 由於/和/task/lst 路徑是比較成功,所以必須使用此指令 ```htmlmixed= <a routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }" > 首頁 </a> ``` ### Router服務 可以透過Router服務的navigate() 方法,來直接在程式碼切換 ```typescript= this.router.navigate([路由路徑1, 路由路徑2....,路由變數]); ``` 或是navigateByUrl()方法,直接輸入路由路徑字串 ```typescript= this.router.navigateByUrl('tasks'); ``` ### 路由參數傳遞 * 路由變數 :  * 設定 :  利用冒號來指定一個路由變數 ```typescript= { path: '路由/:sn', component: 指定Component } ``` * 取得 : 在ngOnInit利用ActivatedRoute的snapshot屬性 ```typescript= ngOnInit(): void { const taskSn = this.route.snapshot.paramMap.get('sn'); } ``` 由於ngOnInit只會在元件載入時觸發,所以當表單可以切換至其他頁面時,就無法取得更新後的路由變數,可以利用paramMap屬性來監控變化 ```typescript= ngOnInit(): void { this.route.paramMap .pipe( map((paramMap) => paramMap.get('sn')), switchMap((taskSn) => this.taskService.get(taskSn)), tap((task) => (this.task = task)) ) .subscribe({ next: (task) => this.formControl.setValue(task.TaskName), }); } onNext(): void { const sn = this.taskService.getNextSn(this.task.TaskSn); this.router.navigate([`../${sn}`], { relativeTo: this.route });//使用相對路徑 } ``` * 查詢字串 :  * 設定 :  * routeLink : * 繫結到queryParams ```htmlmixed= <a [routerLink]="路由" routerLinkActive="active" [queryParams]="{ 參數: 值,... }">工作事項</a> ``` * Router服務 : navigate的queryParams屬性 ```typescript= this.router.navigate([], { relativeTo: this.route, queryParamsHandling: 'merge', queryParams: { pageIdx: this.pageIndex + moveIndex, pageSize: this.pageSize, }, }); ``` * 取得 :  * ActivatedRoute的snapshot屬性 ```typescript= const pageIndex = +this.route.snapshot.queryParamMap.get('pageIdx') || 1; const pageSize = +this.route.snapshot.queryParamMap.get('pageSize') || 5; ``` * 訂閱ActivatedRoute的queryParamMap屬性 ```typescript= this.tasks$ = this.route.queryParamMap.pipe( map((queryParamMap) => ({ index: +queryParamMap.get('pageIdx') ?? 0, size: +queryParamMap.get('pageSize') || 5, })), tap(({ index, size }) => { this.pageIndex = index; this.pageSize = size; }), switchMap(({ index, size }) => this.taskService.getList(index, size)) ); ``` * 頁面切換時,一併帶查詢字串傳遞 routeLink 指令或Router服務的navigate()方法,均設定queryParamHandling * preserve : 保留當前的查詢字串 * merge : 會把當下及切換指定的查詢字串結合成新的 * 路由資料 :  * 設定 : 使用Resolver服務,將預先載入資料的邏輯放在resolve * data : 如有字串不想在網址出現時,可使用此屬性 resolver.ts ```typescript= resolve(route: ActivatedRouteSnapshot): Observable<Task> { const sn = route.paramMap.get('sn'); return this.taskService.get(sn); } ``` app-routing.module.ts :  把ResolveService加到relsove物件屬性,就能在頁面載入前取得資料 ```typescript= { path: 'task/form/:sn', component: TaskFormPageComponent, data: { message: '單純的文字資料' }, resolve: { task: TaskResolver, } } ``` * 取得 :  * 訂閱data屬性 : component.ts ```typescript= this.route.data .pipe( map(({ task, message }: Data) => ({ task, message })), tap(({ message }) => console.log(message)), filter(({ task }) => !!task), tap(({ task }) => (this.task = task)) ) .subscribe(({ task }) => this.formControl.setValue(task.TaskName)); ``` * snapshot屬性 : ```typescript= const task = this.route.snapshot.data.task; const message = this.route.snapshot.data.message; ``` ### 子路由 依照路徑結構將路由定義給結構化 假設 ```typescript= const routes: Routes = [ { path: 'task/list', component: TaskPageComponent }, { path: 'task/form', component: TaskFormPageComponent } ]; ``` 則可以改寫成 ```typescript= const routes: Routes = [ { path:'task', component: TaskComponent children:[ { path: 'list', component: TaskPageComponent }, { path: 'form', component: TaskFormPageComponent } ] } ]; ``` ### 延遲載入 為了提升使用者體驗,不要在一開始過多加載檔案 先把需要延遲的路由打包至另一個Router模組,在主Router設定 ```typescript= { path: 'task', loadChildren: () => import('./task-feature/task-feature.module').then( (m) => m.TaskFeatureModule ), } ``` ### 預先全部載入 preloadingStrategy: PreloadAllModules ```typescript= imports: [ RouterModule.forRoot( routes, { preloadingStrategy: PreloadAllModules } )] ``` ### 路由守門員(Router Guard) 檢查是否有權限進出頁面 * CanActivate介面 canActivate() : 父路由檢查 * CanActivateChild介面 canActivateChild() : 子路由檢查 * CanDeactivate介面 canDeactivate() : 檢查是否可以離開頁面 ## <span id="*ngFor">*ngFor</span> ```htmlmixed= <元件/html-Tag *ngFor="let 宣告變數 of 需要遍歷的陣列" [元件變數] = "宣告變數" let i = index><元件/html-Tag> ``` * index: number:可迭代物件中當前條目的索引。 * count: number:可迭代物件的長度。 * first: boolean:如果當前條目是可迭代物件中的第一個條目則為 true。 * last: boolean:如果當前條目是可迭代物件中的最後一個條目則為 true。 * even: boolean:如果當前條目在可迭代物件中的索引號為偶數則為 true。 * odd: boolean:如果當前條目在可迭代物件中的索引號為奇數則為 true。 ### trackBy : 由於資料來源被更改時,DOM會全部重新渲染,為了效能會使用 EX : 只渲染task.TaskSn 有改變的DOM元素 ts ```typescript= trackByItem(index:number,task : Task):string{ return task.TaskSn } ``` html ```htmlmixed= <元件 *ngFor="let 宣告變數 of 需要遍歷的陣列 ; trackBy: trackByItems" [元件變數] = "宣告變數" ><元件> ``` [返回](#StructuralDirective) ## <span id="*ngIf">*ngIf</span> ```htmlmixed= <div *ngIf="條件式"></div> ``` ### if-else ```htmlmixed= <div *ngIf="條件式 ;else template名稱"></div> <ng-template #template名稱></ng-template> ``` 或是將資料顯示也放在template ```htmlmixed= <div *ngIf="條件式 ;then 為真顯示的template名稱 else template名稱"></div> ``` [返回](#StructuralDirective) ## <span id="*ngSwitch">*ngSwitch</span> ```htmlmixed= <div [ngSwitch]="判斷屬性"> <span *ngSwitchCase="值1">值1</span> <span *ngSwitchCase="值2">值1</span> <span *ngSwitchDefault>預設</span> </div> ``` [返回](#StructuralDirective) ## *ngComponentOutlet html ```htmlmixed= <ng-container *ngComponentOutlet="顯示元件變數"></ng-container> ``` ### Injector : 注入其他服務 html ```htmlmixed= <ng-container *ngComponentOutlet="顯示元件變數; injector: myInjector"></ng-container> ``` ts ```typescript= export class AppComponent { myInjector: Injector; constructor(injector: Injector) { this.myInjector = ReflectiveInjector.resolveAndCreate( [所需其他服務], injector ); } } ``` ### content : 當元件有ng-content 元件html ```htmlmixed= <ng-content></ng-content> <p>test</p> <ng-content></ng-content> ``` html ```htmlmixed= <ng-container *ngComponentOutlet="顯示元件變數; content: myContent"></ng-container> ``` ts ```typescript= export class AppComponent { myContent = [ [document.createTextNode('Ahoj')], [document.createTextNode('second content')] ]; constructor() {} } ``` 顯示 ``` Ahoj test second content ``` ### ngModuleFactory : 載入的 Component 是來自其他的 Module html ```htmlmixed= <ng-container *ngComponentOutlet="顯示元件變數; ngModuleFactory: myModule"></ng-container> ``` ts ```typescript= export class AppComponent { myModule: NgModuleFactory<any>; constructor(compiler: Compiler) { this.myModule = compiler.compileModuleSync(OtherModule); } } ``` p.s. OtherModule 不需要事先 Import 到 AppModule 裡 ### 動態載入範例 html ```htmlmixed= <ng-container *ngComponentOutlet="component"></ng-container> ``` ts ```typescript= export class AppComponent implements OnInit { component!: Type<any>; ngOnInit():void{ this.onSwitch('a'); } onSwitch(type:string){ switch(type) { case 'a': this.component = AComponent; break; case 'b': this.component = BComponent; break; case 'c': this.component = CComponent; break; } } } ``` [返回](#StructuralDirective) ## <span id="*ngTemplateOutlet">*ngTemplateOutlet</span> ```htmlmixed= <div *ngTemplateOutlet = "sample"></div> <ng-template #sample> <p>test</p> </ng-template> ``` ### context 使用物件傳入參數 ``` context:{$implicit:參數值,參數名稱:參數值} ``` $implicit : 預設資料,ng-template可直接使用`let-XXX` 直接接收 EX : ```htmlmixed= <div *ngTemplateOutlet="myNameIs; context:{$implicit:'Andy',test:fontSize}"></div> <div *ngTemplateOutlet="myNameIs; context:{$implicit:'Candy'}"></div> <ng-template #myNameIs let-MyName let-testText="test"> <p>我是 {{MyName}}{{testText}}</p> </ng-template> ``` [返回](#StructuralDirective) ## <span id="tree-shaking">tree-shaking<span>(搖樹優化) 原理依賴於ES6的模組特性 將不需使用到的程式碼給刪除掉 [返回](#Re-tree-shaking)
{"metaMigratedAt":"2023-06-17T00:17:15.419Z","metaMigratedFrom":"Content","title":"Angular","breaks":true,"contributors":"[{\"id\":\"f838e6d0-4ef9-442c-b94a-66fd8d29f581\",\"add\":30514,\"del\":4685}]"}
    164 views