---
# System prepended metadata

title: Angular
tags: [' Web', 學習筆記]

---

---
tags: 學習筆記, Web
---
Angular
=======
## TypeScript
Angular以TypeScript為開發語言，TypeScript是一個為了改善JavaScript諸多問題而誕生的語言。
### 語法
### const & let
```typescript=
const cannotChange = 9; // 只要改變這個常數，程式會出錯
let canChange = [{'a': '1'}, {'b': '2'}]; //在不改變宣告的資料型別的情況下，可以改變這個參數
```
### 資料型別
```typescript=
//Number 
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;

//Stirng
let person: string = "Mike"; //可以用 ""
let age: number = 37;
let sentence: string = `Oh, ${person} is ${age} years old.`; //也可以用 `${}`
//上面等於 "Oh, " + person + " is " + age + " years old."

//Array
let list: number[] = [1, 2, 3];

//Tuple
let x: [string, number]; // Array中包含不同型別的變數用 Tuple
x = ["hello", 10]; // OK
x = [10, "hello"]; // Error
x[3] = true // Error 往後的變數只能是一開始設定的 string 或 number
```
### 型別註釋
明確參數型別
```typescript=
function greeter(person: string) {
    return "Hello, " + person;
}
```
### Interface
使物件有更明確的結構型態。
```typescript=
interface Person {
    firstName: string;
    lastName: string;
}

function greeter(person: Person) {
    return "Hello, " + person.firstName + " " + person.lastName;
}
```
### 類別
```typescript=
class Student {
    fullName: string;
    constructor(public firstName, public middleInitial, public lastName) {
        this.fullName = `${firstName} ${middleInitial} ${lastName}`;
    }
}

interface Person {
    firstName: string;
    lastName: string;
}

function greeter(person : Person) {
    return `Hello, ${person.firstName} ${person.lastName}`;
}

let user = new Student("Jane", "M.", "User");

console.log(greeter(user));
```
同樣存在static, extends, implements
### 迭代
```typescript=
let list = [4, 5, 6];

for (let i in list) {
   console.log(i); // "0", "1", "2",
}

for (let i of list) {
   console.log(i); // "4", "5", "6"
}
```
等價於
```typescript=
let list = [4, 5, 6];
for (let _i = 0; _i < list.length; _i++) {
    var num = list[_i];
    console.log(num);
}
```
## Angular 架構
### Component
Component 是一個用 TS 寫出來的 Class，這個 Class 之後會被編譯成 JS。而這些 Component 最後可以被包裝變成獨立的 Module 讓其他 Mudule 使用。
一個 Module 可以包裝很多 Component 給其他 Module 使用。


```typescript=
//app.component.ts
import { Component } from '@angular/core';

@Component({
  moduleId: module.id,
  selector: 'my-app',
  templateUrl: 'app.component.html',
  styleUrls: ['app.component.css']
})
export class AppComponent implements OnInit { 
    //Do Something
}
```
```typescript=
//app.module.ts
import { NgModule }      from '@angular/core';
import { AppComponent } from './app.component'; //引入 Component
import { BrowserModule } from '@angular/platform-browser';
@NgModule({
  imports:      [ BrowserModule ],
  providers:    [ Logger ],
  declarations: [ AppComponent ],
  exports:      [ AppComponent ],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }
```
Component 用稱作 decorator function的 @Component來表示為 Metadata 物件。 Metadata 用來綁定 HTML template 、 CSS 和 Component。

至於 selector 則是告訴 Angular 這個 Component 要放在 index.html 裡的自訂標籤 <my-app>。
```html=
//index.html 
<body>
...
<my-app>Loading AppComponent content here ...</my-app>
...
```
### Directive
* Components directives： 用來控制 HTML Template。是我們最常用到的 Directive。
* 結構性 directives：例如 ngFor (用來改變 HTML DOM，可以是增加或刪掉)
  ```html=
  <div *ngFor="let hero of heroes">{{hero.fullName}}</div>
  ```
* 屬性 directives： 例如 ngStyle (用來改變 Elements 的 Style)
  ```html=
  <p [style.background]="'lime'">I am green with envy!</p>
  ```
@Directive適合直接用在已經存在的 DOM element。可以重複使用，一個 DOM element 可以有很 Directive。
@Component適合建立一個可以重複使用的 DOM element，通常用來建立 UI工具，此外一個 DOM elelment 只能有一個 Component，而且必須搭配@View或是templateUrl。
### Data Binging
目標=來源
* One Way Binding
  可以是任何 HTML property。像是innerText style。
  * 從數據來源綁定看到的目標用的是：
    ```html=
    <h1 [innerText]="employee.name"></h1> 
    <h1>{{employee.name}}</h1> 
    <span [style.backgroundColor]="employee.favouriteColor"></span>
    <span bind-style.backgroundColor="employee.favouriteColor"></span>
    ```
  * 從看到的目標綁定數據來源用的是：
    ```html=
    <span (style.backgroundColor)="employee.favouriteColor"></span>
    <span on-style.backgroundColor="employee.favouriteColor"></span>
    ```
* Two Way Binding
  可以由使用者控制，像是選單、輸入，也可以由程式控制。
  ```html=
  <input [(ngModel)]="employee.name"/> 
  <input bindon-ngModel="employee.name"/> 
  ```
* Event Binding
  綁定事件，像是click、focus或blur
  ```html=
  <button (click)="sendForm()">Send</h1>  
  ```
  ```typescript=
    deleteRequest = new EventEmitter<Hero>();

    delete() {
      this.deleteRequest.emit(this.hero);
    }
    ```
    ```html=
    <div>
        ...
        <button (click)="delete()">Delete</button>
        ...
    </div>
    <hero-detail (deleteRequest)="deleteHero($event)" [hero]="currentHero"></hero-detail>
    ```
### Dependency injection
一個 Injector (注入)用 providers 建立 dependencies(依賴)。 Providers 會知道如何去建立 dependencies。
TS 中的型別註釋(Type annotations)可以被用來要求 dependencies 。此外每個 Component 都會有自己的 injector，組成一個架構譜。
用一個一個 provider 如 @NgModule、 @Component或@Directive來建立 Injector
在 Component 的 constructor 注入服務。
> * 一個 Injector (注入)用 providers 建立 dependencies(依賴)。 Providers 會知道如何去建立 dependencies。
> * TS 中的型別註釋(Type annotations)可以被用來要求 dependencies 。此外每個 Component 都會有自己的 injector，組成一個架構譜。
> * 用一個一個 provider 如 @NgModule、 @Component或@Directive來建立 Injector
> * 在 Component 的 constructor 注入服務。

```typescript=
export class Car {
  public description = 'DI';
  constructor(public engine: Engine, public tires: Tires) { }
}

let car = new Car(new Engine(), new Tires());

let bigCylinders = 12;
let car = new Car(new V8Engine(bigCylinders), new Tires());
```
一個 Class 從外部接收依賴而非內部創建，這就是依賴注入(DI)。
> 依賴反轉原則
> 抽象不要依賴細節，細節要依賴抽象。
> 高階模組不應該依賴低階模組，低階模組應該由高階模組決定其依賴。

但每次要使用一個物件Car，需要自己製造零件Engine、Tires，還要自己組裝零件實在是很費工夫的事情，若是可以直接使用一個物件Car，但所以東西都製造也組裝好了，那該有多好！這時候我們有個injector已經幫你註冊組裝好，就如同品牌產品，直接領取一點也不費工夫，這便是注入依賴框架在做的事情囉！
```typescript=
//直接開走，不需要自己裝輪胎，不需要自己裝引擎
let car = injector.get(Car);
```

```typescript=
import { Component }          from 'angular2/core';
import { HeroListComponent }  from './hero-list.component';
import { HeroService }        from './hero.service';
@Component({
  selector: 'my-heroes',
  template: `
  <h2>Heroes</h2>
  <hero-list></hero-list>
  `,
  providers:[HeroService],
  directives:[HeroListComponent]
})
export class HeroesComponent { }
```

```typescript=
import { Component }   from 'angular2/core';
import { Hero }        from './hero';
import { HeroService } from './hero.service';
@Component({
  selector: 'hero-list',
  template: `
  <div *ngFor="#hero of heroes">
    {{hero.id}} - {{hero.name}}
  </div>
  `,
})
export class HeroListComponent {
  heroes: Hero[];
  constructor(heroService: HeroService) {
    this.heroes = heroService.getHeroes();
  }
}
```
會以隱形式 DI 完成。
```typescript=
import { Injectable } from '@angular/core';
@Injectable()
export class Logger {
  logs: string[] = []; // capture logs for testing
  log(message: string) {
    this.logs.push(message);
    console.log(message);
  }
}
```
@Component、@Directive、@Pipe，都是 Injectable 的子型。
因為都沒有裝飾器，所以需要加上 Injectable 來告訴 ts，讓他產生 metadata，才能正常編譯。
### Angular 表單
* Component 和 Template 構建表單
* 雙向綁定 `[(ngModel)]` 讀寫表單表。
* ngControl追蹤表單狀態和有效性。
* 根據ngControl改變 CSS。
* 顯示驗證錯誤消息並啟用/禁止表單。
* 使用本地模板變量共享控制間的信息。

### PIPES
每一個應用程式都是一些非常簡單的任務開始：獲取數據、轉換數據，並把它們顯示給用戶。
有時候想要把所有小寫字母，在顯示的時候變成都是大寫。
```typescript=
// app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  // uppercase 為 Angular 內建
  template: '<p>My name is <strong>{{ name | capitalize  }}</strong>.</p>',
})
export class AppComponent {
  name = 'john doe';
}
```
```typescript=
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({name: 'capitalize'})
export class CapitalizePipe implements PipeTransform {
  transform(value: string, args: string[]): any {
    if (!value) return value;

    return value.replace(/\w\S*/g, function(txt) {
        return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
    });
  }
}
```
在 App Module 中引入我們寫好的 Pipe 這樣之後要用 Pipe 就不用每個 Component 都一一宣告囉！
```typescript=
// app.module.ts
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent }   from './app.component';
import { CapitalizePipe } from './capitalize.pipe'; // 導入我們的 pipe

@NgModule({
  imports:      [ BrowserModule ],
  // 引入 capitalize pipe 
  declarations: [ AppComponent, CapitalizePipe ], 
  bootstrap:    [ AppComponent ]
})

export class AppModule { }
```
# Lazy Loading
* 指定 app.routing.ts 中要 lazy load 的路由 (routes)
* 建立 Component 路由文件
* 調整要被 lazy load 的 module 的內容

```typescript=
//app.routes.ts

import {Routes, RouterModule} from "@angular/router";
import {ModuleWithProviders} from "@angular/core";

const appRoutes: Routes  = [
    // 假設有首頁
    { path: "", redirectTo: "home", pathMatch: "full" },
    
    // 要處理的 Product APP 頁面
    { path: "product", loadChildren: "app/product/product.module#ProductModule"  }
];

export const routing: ModuleWithProviders  = RouterModule.forRoot(appRoutes);;;
```
```typescript=
//product/product.routing.ts

import {RouterModule} from "@angular/router";
import {ModuleWithProviders} from "@angular/core";
import {ProductComponent} from "./product.component";
// 小零件 ( 各種用到的東西 )
import {ProductsListComponent} from "./products-list.component";
import {ProductDetailsComponent} from "./product-details.component";

const routes = [
    {
        path: "", //設定根目錄為這一層
        component: ProductComponent,
        // Product 包含的小組件們
        children: [
            {path: "", component: ProductsListComponent},
            {path: ":id", component: ProductDetailsComponent}
        ]
    }
];

export const routing: ModuleWithProviders = RouterModule.forChild(routes);//product/product.routing.ts

import {RouterModule} from "@angular/router";
import {ModuleWithProviders} from "@angular/core";
import {ProductComponent} from "./product.component";
// 小零件 ( 各種用到的東西 )
import {ProductsListComponent} from "./products-list.component";
import {ProductDetailsComponent} from "./product-details.component";

const routes = [
    {
        path: "", //設定根目錄為這一層
        component: ProductComponent,
        // Product 包含的小組件們
        children: [
            {path: "", component: ProductsListComponent},
            {path: ":id", component: ProductDetailsComponent}
        ]
    }
];

export const routing: ModuleWithProviders = RouterModule.forChild(routes);
```
```typescript=
//product/product.component.ts
import {Component} from "@angular/core";

@Component({
    template: `
        <my-nav></my-nav>
        <h1>Products</h1>
        <router-outlet></router-outlet>
    `
})
export class ProductComponent {}
```
```typescript=
//product/product.module.ts

import {NgModule} from "@angular/core";
import {ProductsService} from "./products.service";
import {ProductsListComponent} from "./products-list.component";
import {ProductDetailsComponent} from "./product-details.component";
import {routing} from "./product.routing";
import {ProductComponent} from "./product.component";

@NgModule({
    declarations: [ProductComponent, ProductsListComponent, ProductDetailsComponent],
    imports: [routing],
    providers: [ProductsService],
})
export class ProductModule {}
```
模板載入組件
```typescript=
//app.component.ts
import {Component} from '@angular/core'

import {FastComponent} from 'src/components/fast'
import {SlowComponent} from 'src/components/slow'

@Component({
  selector: 'template-load',
  template: `
      <h1>方式一：template 動態載入</h1>
      <fast></fast>
      <slow></slow>
  `,
  directives: [ FastComponent, SlowComponent ]
})
export class TemplateLoadComponent {
  constructor() {

  }
}

//src/components/slow.ts
import { Component } from '@angular/core';

@Component({
  selector: 'slow',
  template: `
    <p>Slow Content</p>
  `
})

export class SlowComponent { }

//src/components/fast.ts
import { Component } from '@angular/core';

@Component({
  selector: 'fast',
  template: `
    <p>Fast Content</p>
  `
})

export class FastComponent { }
```
### Router
```typescript=
@Component({ ... })
@Routes([
  {path: '/crisis-center', component: CrisisListComponent},
  {path: '/heroes',        component: HeroListComponent},
  {path: '/hero/:id',      component: HeroDetailComponent}  // 參數傳址 /hero/58
])
export class AppComponent  implements OnInit {
  constructor(private router: Router) {}

  ngOnInit() {
    // 導向 crisis-center 頁面
    this.router.navigate(['/crisis-center']);
  }
}
```
傳至router-outlet
```htmlm=
//app.component.html

<h1>Component Router</h1>
<nav>
    <a [routerLink]="['/crisis-center']">Crisis Center</a>
    <a [routerLink]="['/heroes']">Heroes</a>
    
    <!-- 也可以不是陣列
    <a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a>
    <a routerLink="/heroes" routerLinkActive="active">Heroes</a>
    -->
</nav>
<router-outlet></router-outlet>
```

### Animation
```typescript=
import {
  Component, OnChanges, Input,
  trigger, state, animate, transition, style
} from '@angular/core';

@Component({
  selector : 'my-fader',
  animations: [
    trigger('visibilityChanged', [
      state('true' , style({ opacity: 1, transform: 'scale(1.0)' })),
      state('false', style({ opacity: 0, transform: 'scale(0.0)'  })),
      transition('1 => 0', animate('300ms')),
      transition('0 => 1', animate('900ms'))
    ])
  ],
  template: `
  <div [@visibilityChanged]="isVisible" >
    <ng-content></ng-content>
    <p>Can you see me? I should fade in or out...</p>
  </div>
  `
})
export class FaderComponent implements OnChanges {
  @Input() isVisible : boolean = true;
}
```
divdiv -> visibiliityChanged(isVisiblesible)
### API異地資料傳接
#### RxJS
```typescript=
import { Injectable }     from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable }     from 'rxjs/Observable';
@Injectable()
export class HttpClientService {
  constructor (private http: Http) {}
  get(url: string, optionHeader?: {[header: string]: string}): Observable<Response> {
    let headers = new Headers();
    // set option header
    for(let header in optionHeader) {
      let value = optionHeader[header];
      headers.append(header, value);
    }
    return this.http.get(url, {headers: headers,})
                    .map(this.extractData)
                    .catch(this.handleError);
  }
  private extractData(res: Response) {
    let body = res.json();
    return body.data || { };
  }
  private handleError (error: Response | any) {
    // In a real world app, we might use a remote logging infrastructure
    let errMsg: string;
    if (error instanceof Response) {
      const body = error.json() || '';
      const err = body.error || JSON.stringify(body);
      errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
    } else {
      errMsg = error.message ? error.message : error.toString();
    }
    console.error(errMsg);
    return Observable.throw(errMsg);
  }
}
```
```typescript=
getData() {
  this.HttpClientService.get(url:string)
                        .subscribe(
                          data => this.data = data,
                          error =>  this.errorMessage = <any>error
                        );
}
```
#### AJAX
```typescript=
declare var $:any; // typescript看不懂$ 所以建立個$給他

$.ajax({
  url: url,
  type: 'POST',
  data:  {'data':JSON.stringify(json)},
  success: function(data: any) {
      console.log(data);
  },
  error: function(XMLHttpRequest: any, textStatus: any, errorThrown: any) {
      console.log(errorThrown);
  }
});
```
### <ng-content>
<ng-content> 嵌入式設計
```typescript=
// card.component.ts

import { Component, Input, Output } from '@angular/core';
@Component({
  selector: 'card',
  templateUrl: 'card.component.html',
})
export class CardComponent {
    @Input() header: string = 'this is header';   
    @Input() footer: string = 'this is footer';
}
```
```html=
<!-- card.component.html -->

<div class="card">
    <div class="card-header">
        {{ header }}
    </div>

    <!-- 單一嵌入點 -->
    <ng-content></ng-content>

    <div class="card-footer">
        {{ footer }}
    </div>
</div>
```
```html=
<!-- app.component.html -->

<h1>Single slot transclusion</h1>
<card header="my header" footer="my footer">
  <!-- 放入要嵌入的動態內容 -->
  <div class="card-block">
    <h4 class="card-title">You can put any content here</h4>
    <p class="card-text">For example this line of text and</p>
    <a href="#" class="btn btn-primary">This button</a>
  </div>
  <!-- 結束 -->
<card>
```
### NgModule
一個 Module ( 模組 ) 是一個把彼此互相關聯的 components、directives、pipes 和 services 整合的機制。然後這個模組可以再和其他模組結合，最後就形成我們的網頁應用程式。網頁就像拼圖一般，由許多小碎片的組件和一塊一塊的模組所構成。
一個模組又由點像是類別 ( Class )，一樣也有 public 和 private 的概念。應用程式只能取用公開的部分，私有的部分則看不見。
* declarations：模塊內部Components/Directives/Pipes的列表，聲明一下這個模塊內部成員
* providers：指定應用程式的根級別需要使用的service。（Angular2中沒有模塊級別的service，所有在NgModule中聲明的Provider都是註冊在根級別的Dependency Injector中）
* imports：導入其他module，其它module暴露的出的Components、Directives、Pipes等可以在本module的組件中被使用。比如導入CommonModule後就可以使用NgIf、NgFor等指令。
* exports：用來控制將哪些內部成員暴露給外部使用。導入一個module並不意味著會自動導入這個module內部導入的module所暴露出的公共成員。除非導入的這個module把它內部導入的module寫到exports中。
* bootstrap：通常是app啟動的根組件，一般只有一個component。bootstrap中的組件會自動被放入到entryComponents中。
* entryCompoenents: 不會再模板中被引用到的組件。這個屬性一般情況下只有ng自己使用，一般是bootstrap組件或者路由組件，ng會自動把bootstrap、路由組件放入其中。 除非不通過路由動態將component加入到dom中，否則不會用到這個屬性。
透過@NgModule定義其為模組。
```typescript=
@NgModule({
  imports: [ ... ],
  declarations: [ ... ],
  bootstrap: [ ... ]
})
```

### CSS Shadow DOM
```typescript=
@Component({
  templateUrl: 'card.html',
  styles: [`
    .card {
      height: 70px;
      width: 100px;
    }
  `],
  encapsulation: ViewEncapsulation.Native
  // encapsulation: ViewEncapsulation.None
  // encapsulation: ViewEncapsulation.Emulated is default 
})
```
* None: style 直接被包裝到 `<head>` 的 `<style>` 裡面
* Emulated: style 被包裝到 `<head>` 的 `<style>` 裡面，但可以辨識要對應的 Component
* Native: 如同預期一般為網頁組件。
Angular預設以Emulated達到封裝style的效果，替class rename或是加上prefix來模擬Shadow DOM。
```html=
<my-app _nghost-ric-0="">
    <div _ngcontent-ric-0="" class="card card-block">
      <h4 _ngcontent-ric-0="">Title</h4>
    </div>
</my-app>
```
```typescript=
@Component({
  styleUrls: ['css/style.css'],
  templateUrl: 'card.html',
})
```
* None: style 直接被包裝到 `<head>` 的 `<style>` 裡面。他會被產生於執行完「寫在 Component 裡面」的方式之後。
* Emulated: style 被包裝到 `<head>` 的 `<style>` 裡面，但可以辨識要對應的 Component。反而不是引入 link 喔！
* Native: 如同預期一般為網頁組件。
### Component Inheritance
* Metadata (decorator): 定義在衍伸的類別的 metadata 像是 @Input、@Output 等，會覆蓋先前所有在繼承鏈裡面的 metadata，不然則採用基本的類別 (父類別) 的 metadata。
* Constructor: 如果衍伸類別沒有 constructure，會採用基本類別的。意思就是說所以注入到 parent constructure 的服務會被 child constructure 繼承。
* Lifecycle hooks: 生命週期鉤子像是 ngOnInit、ngOnChanges會被呼叫，就算他們在衍伸類別沒有被定義。
```typescript=
// simple-pagination.component.ts

import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'simple-pagination',
  template: `
    <button (click)="previousPage()" [disabled]="!hasPrevious()">Previous</button> 
    <button (click)="nextPage()" [disabled]="!hasNext()">Next</button>

    <p>page {{ page }} of {{ pageCount }}</p>
  `
})
export class SimplePaginationComponent {
  @Input() 
  pageCount: number;

  @Input()
  page: number;

  @Output()
  pageChanged = new EventEmitter<number>();

  nextPage() {
    this.page ++;
    this.pageChanged.emit(this.page);
  }

  previousPage() {
    this.page --;
    this.pageChanged.emit(this.page);
  }

  hasPrevious(): boolean { return +this.page > 1; }

  hasNext(): boolean { return +this.page < +this.pageCount; }

}
```

```typescript=
// my-pagination.component.ts

import { Component } from '@angular/core';
import { SimplePaginationComponent } from './simple-pagination.component';

@Component({
  selector: 'my-pagination',
  template: `
    <a (click)="previousPage()" [class.disabled]="!hasPrevious()" 
      href="javascript:void(0)">
      ««
    </a> 
    <span>{{ page }} / {{ pageCount }}</span>
    <a (click)="nextPage()" [class.disabled]="!hasNext()"
      href="javascript:void(0)" >
      »»
    </a>
  `
})
export class MyPaginationComponent extends SimplePaginationComponent {
}
```
不同 Component 之間傳遞
inputs、@input() 由上到下
outputs、@output() 由下到上