---
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() 由下到上