Posted on Leave a comment

[功能介紹-3] Hooks的生命週期

Lifecycle Hooks


一個組件有一個由Angular管理的生命週期。
Angular創建、產生元件,當元件的數據綁定屬性改變時做檢查並確認,並在元件從DOM中刪除它之前destroys掉該元件。
Angular提供lifecycle hooks,可以讓我們在各個階段加上我們要讓元件做的事情。
directive具有相同的lifecycle hooks。

如何使用Lifecycle Hooks

下面為一個使用範例

export class PeekABoo implements OnInit {
  constructor(private logger: LoggerService) { }

  // implement OnInit's `ngOnInit` method
  ngOnInit() { this.logIt(`OnInit`); }

  logIt(msg: string) {
    this.logger.log(`#${nextId++} ${msg}`);
  }
}

Lifecycle sequence

目的和時機
ngOnChanges() Angular設置數據綁定的輸入屬性。該方法接收SimpleChanges當前和以前的屬性值的對象。

會在ngOnInit()之前被呼叫

ngOnInit() 在Angular之後初始化指令/組件首先顯示數據綁定屬性並設置指令/組件的輸入屬性。
ngDoCheck() 檢測Angular無法或無法自行檢測到的更改並採取相應措施。
ngAfterContentInit() 在Angular將外部內容設置到template之後被呼叫。

 

ngAfterContentChecked() 在Angular檢查投影到組件中的內容之後被呼叫。
ngAfterViewInit() 初始化組件的template和sub-template之後被呼叫。
ngAfterViewChecked() 在Angular檢查組件的視圖和子視圖之後作出響應。
ngOnDestroy 在Angular破壞指令/組件之前進行清理。取消訂閱Observables和事件處理程序以避免內存洩漏。

範例練習

範例網址:https://angular.io/guide/lifecycle-hooks#peek-a-boo

Posted on Leave a comment

[功能介紹-2] 資料繫結的模版語法

Template Syntax

在上一篇的Angular架構中有提到,透過模版語法,template可以與component做許多的溝通。

那這一篇就會介紹Angular內建的模版語法參考。

對於tempalte.html來說,所有的html標籤都可以使用,除了<script>以外,這是為了維護模版的安全性,去除template被攻擊的風險,因此在template中所有的<script>會被忽略,並跳出警告。

接下來我們來介紹所有Angular的模板語法

{{…}}

以下為一個範例

<h3>
  {{title}}
  <img src="{{heroImageUrl}}" style="height:30px">
</h3>

大括號之間值通常是組件屬性的名稱。Angular使用相應組件屬性的字符串值替換該名稱。
在上面的例子中,Angular會取元件裡title和heroImageUrl的屬性,並會取代{{title}}及{{heroImageUrl}},因此在頁面上會顯示一個大的應用程序標題,然後一個英雄影像的位置。

在{{…}}之間,我們也可以使用模板表達式去轉換要顯示的值。
例如在刮弧中做運算:

<!-- "The sum of 1 + 1 is 2" -->
<p>The sum of 1 + 1 is {{1 + 1}}</p>

或者也可以呼叫component的function getVal():

<!-- "The sum of 1 + 1 is not 4" -->
<p>The sum of 1 + 1 is not {{1 + 1 + getVal()}}</p>

大多的運算符都可以用在表達式裡面,除了一些會影響到component的值的運算符,如=、+=、-=之類。

有時候{{…}}裡面要綁定的數值也可以是在template定義的(使用#符號),請見下面的範例

<div *ngFor="let hero of heroes">{{hero.name}}</div>
<input #heroInput> {{heroInput.value}}

「註」:如果在元件裡已經有變數名稱叫做hero,而template裡又有一個hero,在template會優先使用template內定義的變數。
在表達式中不能引用任何除了undefined外的全域變數,如window或document,也不能用consolo.log,只能使用元件內的屬性或方法,或者template裡上下文內的成員

在使用{{…}}時,有下面四個原則要注意:

  • No visible side effects:不應該改變任何元件內的值,在rendering整個表式示時應該是穩定的
  • Quick execution:表達式的運算應該要很快,因為它會在許多狀況下被呼叫,因此若是裡面含有許多複雜運算時,請考慮用快取以增加效能。
  • Simplicity:雖然可以在{{…}}裡面寫很複雜的運算但是不建議,最多在裡面使用!符號,不然還是建議將運算放到元件內去計算,以利閱讀及開發
  • Idempotent: idempotent的意思是如果相同的操作再執行第二遍第三遍,結果還是跟第一遍的結果一樣 (也就是說不管執行幾次,結果都跟只有執行一次一樣)。

(event)=”statement”

(event)=”statement”是Template statements。事件綁定是利用這樣的方式去做的,下面是一個範例:

<button (click)="deleteHero()">Delete hero</button>

與{{…}}不同的是,在這樣的語法中,是可以去改變元件的值的,並且被改變的值會透過單向綁定{{…}}顯示在畫面上。因此,(event)=”statement”的statement是能夠有支援=運算符的,但是+=、-=和++、–是不被允許的。

語句上下文也可以引用模板自己的上下文的屬性。在以下範例中,將模版$event對象,模板輸入變量(let hero)和模板引用變量(#heroForm)傳遞給組件的事件處理方法。
下面是一個範例

<button (click)="onSave($event)">Save</button>
<button *ngFor="let hero of heroes" (click)="deleteHero(hero)">{{hero.name}}</button>
<form #heroForm (ngSubmit)="onSubmit(heroForm)"> ... </form>

[target]=”statement”

以下是一個範例:

<!-- Bind button disabled state to `isUnchanged` property -->
<button &#91;disabled&#93;="isUnchanged">Save</button>

這樣的語法,能夠讓isUnchanged為true時,顯示這樣的畫面

<button disabled>Save</button>

綁定方法整理

資料方向 語法 類型
單向綁定
從資料源到view
{{expression}}
[target]="expression"
bind-target="expression"
Interpolation
Property
Attribute
Class
Style
單向綁定
從view的目標到資料源
(target)="statement"
on-target="statement"
Event
雙向綁定
[(target)]="expression"
bindon-target="expression"
Two-way

HTML attribute與DOM property

HTML attribute和DOM property(物件屬性)的區別對於理解Angular綁定是很重要的。一旦使用插值(`{{…}}`),就不是使用HTML attribute,而是在設置DOM property。

一些HTML attribute可以1:1的對應到一個DOM property,例如:id
一些HTML attribute沒有相應的DOM property,例如:colspan(無法使用插值)。
一些DOM property沒有相應的HTML attribute,例如:textContent
許多DOM property似乎對應到HTML attribute…但不是以你想像的方式!

HTML attribute的value指定初始值; DOM property的value屬性是當前值

例如,當瀏覽器執行下面HTML,它創建一個對應的DOM節點,其value屬性初始化為“Bob”。

<input type="text" value="Bob">

當用戶在輸入框中輸入“Sally”時,DOM property的value變成“Sally”。
但是,HTML attribute的value保持不變,如下所示:

input.getAttribute('value');
//取的的值還是返回“Bob”

disabled屬性是另一個特殊的例子。按鈕的disabled 屬性是false默認的,所以按鈕被啟用。當你添加disabled 屬性時,它的存在會初始化按鈕的disabled 屬性,true所以按鈕被禁用。添加和刪除disabled屬性禁用並啟用按鈕。屬性的值是不相關的,這就是為什麼你不能用下面的語法來將按鈕設為enable。

<button disabled="false">Still Disabled</button>

因此,HTML attribute和DOM property是不一樣的,即使它們具有相同的名稱。

綁定目標整理

類型 目標 例子
屬性 元素屬性
組件屬性
指令屬性
<img &#91;src&#93;="heroImageUrl">
<app-hero-detail &#91;hero&#93;="currentHero"></app-hero-detail>
<div &#91;ngClass&#93;="{'special': isSpecial}"></div>
事件 元素事件
組件事件
指令事件
<button (click)="onSave()">Save</button>
<app-hero-detail (deleteRequest)="deleteHero()"></app-hero-detail>
<div (myClick)="clicked=$event" clickable>click me</div>
雙向 事件和財產
<input &#91;(ngModel)&#93;="name">
屬性 屬性(例外)
<button &#91;attr.aria-label&#93;="help">help</button>
class 屬性
<div &#91;class.special&#93;="isSpecial">Special</div>
樣式 style 屬性
<button &#91;style.color&#93;="isSpecial ? 'red' : 'green'">

Property binding or interpolation?

屬性綁定與插值常常能達到相同的功效,如下面的範例:

<p><img src="{{heroImageUrl}}"> is the <i>interpolated</i> image.</p>
<p><img &#91;src&#93;="heroImageUrl"> is the <i>property bound</i> image.</p>

<p><span>"{{title}}" is the <i>interpolated</i> title.</span></p>
<p>"<span &#91;innerHTML&#93;="title"></span>" is the <i>property bound</i> title.</p>

一般而言,為了易讀性,會使用插值({{…}})。但當沒有要綁定的元素屬性時,必須使用屬性綁定。例如:

<tr><td colspan="{{1 + 1}}">Three-Four</td></tr>

會得到這個錯誤
Template parse errors:
Can't bind to 'colspan' since it isn't a known native property

這是因為插值只能設定properties而不能設定attributes。
這時可以改成

<table border=1>
  <!--  expression calculates colspan=2 -->
  <tr><td &#91;attr.colspan&#93;="1 + 1">One-Two</td></tr>

  <!-- ERROR: There is no `colspan` property to set!
    <tr><td colspan="{{1 + 1}}">Three-Four</td></tr>
  -->

  <tr><td>Five</td><td>Six</td></tr>
</table>

則可以正常顯示如下圖

Built-in structural directives

常見的結構指令如下:

  • NgIf – 有條件地從DOM中添加或刪除一個元素,要注意,這和css的show、hide不一樣,當元素被dom移除時,是沒有辦法去操作DOM元素裡的物件的。
    <app-hero-detail *ngIf="isActive"></app-hero-detail>
  • NgSwitch – 一組在不同視圖之間切換的指令
    <div &#91;ngSwitch&#93;="currentHero.emotion">
      <app-happy-hero    *ngSwitchCase="'happy'"    &#91;hero&#93;="currentHero"></app-happy-hero>
      <app-sad-hero      *ngSwitchCase="'sad'"      &#91;hero&#93;="currentHero"></app-sad-hero>
      <app-confused-hero *ngSwitchCase="'confused'" &#91;hero&#93;="currentHero"></app-confused-hero>
      <app-unknown-hero  *ngSwitchDefault           &#91;hero&#93;="currentHero"></app-unknown-hero>
    </div>

  • NgForOf – 為列表中的每個項目重複一個模板
    <app-hero-detail *ngFor="let hero of heroes" &#91;hero&#93;="hero"></app-hero-detail>

Template reference variables ( #var )

在Angular也可以使用#開頭來將使用者在網頁上input輸入的值轉為一個變數,如下:

<input #phone placeholder="phone number">

<!-- lots of other elements -->

<!-- phone refers to the input element; pass its `value` to an event handler -->
<button (click)="callPhone(phone.value)">Call</button>

這個功能在做表單驗證時非常的方便

<form (ngSubmit)="onSubmit(heroForm)" #heroForm="ngForm">
  <div class="form-group">
    <label for="name">Name
      <input class="form-control" name="name" required &#91;(ngModel)&#93;="hero.name">
    </label>
  </div>
  <button type="submit" &#91;disabled&#93;="!heroForm.form.valid">Submit</button>
</form>
<div &#91;hidden&#93;="!heroForm.form.valid">
  {{submitMessage}}
</div>

允許外部元件讀取元件內的屬性

要讓元件內的屬性能夠給其他元件使用,或者讀取其他元件的屬性,可以在component.ts內宣告

@Input()  hero: Hero;
@Output() deleteRequest = new EventEmitter<hero>();

或者這樣也可以

@Component({
  inputs: ['hero'],
  outputs: ['deleteRequest'],
})

輸入屬性通常接收數據值。 輸出屬性會發送事件,如EventEmitter。
下面的圖顯示元件屬性的輸入和輸出的範例。

safe navigation operator ( ?. )

為了防止出現null reference exception,我們可以使用?.,當值為空值時,會直接傳回空白,可以避免產生不必要的exception
以下為範例

The current hero's name is {{currentHero?.name}}

參考資料

完整綁定範例資料請見此:線上範例

Posted on Leave a comment

[功能介紹-1] Angular架構

Angular是一個框架,用來編寫html的應用程式,可以用javascript或typescript去編寫。

下圖是官網上所繪製的Angular架構圖:

首先先看最中間那一塊,是由template、metadata、component所構成的,這三個是一個component必備的元素。
所謂的元件可以看之前創建hero那篇文章,我們可以用下面指令創一個元件

ng generate component selectSystem

創完一個元件後,可以看見下面這些檔案

Templates

裡面的component.html檔就是template,它看起來像是一個html的檔案,可以在裡面用資料綁定與事件綁定與controller裡的物件做繫結。
下面是一個template的範例:

<h2>Hero List</h2>

<p><i>Pick a hero from the list</i></p>
<ul>
  <li *ngFor="let hero of heroes" (click)="selectHero(hero)">
    {{hero.name}}
  </li>
</ul>

<app-hero-detail *ngIf="selectedHero" &#91;hero&#93;="selectedHero"></app-hero-detail>

可以注意到上面有些地方與一般的html不相同,例如像是*ngFor、{{hero.name}}、(click)="selectHero(hero)"等…
這就是架構圖畫面上用來連繫Component以及Template的兩個箭頭,property binding以及event binding。

例如click事件繫結是(click)=’functionName()’,物件繫結可以用{{data}})。
透過這樣的繫結可以讓template將使用者操作的事件傳給component,component也可以將資料的更動即時的反饋到template所顯示的資料上。

下圖是非常清楚的binding類型列圖

<li>{{hero.name}}</li>
<app-hero-detail &#91;hero&#93;="selectedHero"></app-hero-detail>
<li (click)="selectHero(hero)"></li>

其中{{hero.name}}為值繫結,可以綁定component裡的值。
[hero]="selectedHero"為property binding,可以將某個元件裡的變數塞進一個HTML元素的屬性裡。。
(click)="selectHero(hero)" event binding可以呼叫component裡的function

<input &#91;(ngModel)&#93;="hero.name">

這個則是雙向數據綁定,在雙向綁定中,與屬性綁定一樣,數據屬性值將從組件輸入到輸入框中。用戶的更改也會返回到組件,將屬性重置為最新值,就像事件綁定一樣。

數據綁定在模板及其組件之間的通信中起著重要的作用。

數據綁定對於父組件和子組件之間的通信也很重要。

Component

而component.ts檔則是component內容,裡面會有一些屬性或方法來供template呼叫,下面是一個Component的簡單範例:

export class HeroListComponent implements OnInit {
  heroes: Hero[];
  selectedHero: Hero;

  constructor(private service: HeroService) { }

  ngOnInit() {
    this.heroes = this.service.getHeroes();
  }

  selectHero(hero: Hero) { this.selectedHero = hero; }
}

當用戶在應用程序中切換畫面時,Angular會創建,更新和銷毀組件。你的應用程序可以通過可選參加這個生命週期的每個時刻動作的lifecycle hooks,像ngOnInit()。

Metadata

metadata則是在component.ts裡由@Component開頭的區塊來宣告,裡面會定義這個Component在要如何在別的元件的template裡被引用,templateUrl則是定義自己這個元件要顯示的html模版位置及styleUrls是css檔案位置。
例如下面這個metadata宣告
@Component({
selector: ‘app-heroes’,
templateUrl: ‘./heroes.component.html’,
styleUrls: [‘./heroes.component.css’]
})
就可以用下面的方式來顯示這個元件

<app-heroes></app-heroes>

Modules

接著來介紹左上方的區塊

Angular應用程序是模組化的,Angular稱自己為NgModules的模組化系統。NgModules是一個很大的議題,在後面會有另一篇文章專門介紹ngModules。
一個小的application至少會有一個模組,稱為root module。雖然有些小的專案可能就只有一個模組,但大多大的專案都會有多個模組,稱為feature modules,一個模組內會是相同工作範疇的一組元件,它們有許多工作流程或功能上緊密相關,彼此協同運作。
當我們執行了這樣的指令來創建一個新的專案時

ng new my-app

可以看到src/app資料夾內有app.module.ts這個檔案,這個檔案就是angular預設的根模組。
其內容如下:
import { NgModule } from ‘@angular/core’;
import { BrowserModule } from ‘@angular/platform-browser’;
@NgModule({
imports: [ BrowserModule ],
providers: [ Logger ],
declarations: [ AppComponent ],
exports: [ AppComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
一個NgModule,無論是root module還是feature modules,該class裡一定會有@ngModule的宣告區域,在angular裡會有許多@開頭的宣告,這樣的宣告稱為decorator,可以在元件或模組裡設定許多metadata。關於ngModule更多說明可以看@NgModule的說明關於decorators更多的說明則可以看這邊,這兩項之後都會有專門的文章來介紹。

NgModule是一個用來描述這個模組裡有那些component的metadata,是一個decorator function。
在ngModule裡最重要的屬性有下面這五點:

  • declarations:屬於這個模組的成員。Angular有三種成員:components, directives,以及pipes。
  • exports:要將declarations宣告的那一些成員公開。讓其他模組引用此模組時,可以存取該成員的public function。
  • imports:需引用的模組,所有在這個模組內的元件要引用的模組都要在此宣告
  • providers:要引用的Service需在此宣告
  • bootstrap:這個屬性只有根模組需要設定,在此設定在一開始要顯示的application view

註:上面的範例中,因為有設定bootstrap代表其為一個根組件,而根組件是不需要exports出去的,因為不會有任何其他modules需要用到它。

而要啟動整個應用程序可以在main.ts中加上這段
import { enableProdMode } from ‘@angular/core’;
import { platformBrowserDynamic } from ‘@angular/platform-browser-dynamic’;

import { AppModule } from ‘./app/app.module’;
import { environment } from ‘./environments/environment’;

if (environment.production) {
enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule);//用這個指令來啟動root modules

在js裡我們透過export一個class來供其他modules使用。並在其他js檔案利用import來將這個class引入。

export class AppModule { }
import { NgModule }     from '@angular/core';
import { AppComponent } from './app.component';

NgModules與JavaScript modules是完全不同的兩樣東西,我們可以同時透過ngModule與JS Modules來交互使用以達成我們所需要達成的目的。

詳細了解Web上的JavaScript模組系統

Angular libraries


angular內建了許多可供應用的模組,我們稱為library module。
所有的library module都以@angular為前綴開頭,可以使用npm來安裝管理它們。
例如要使用Component功能要引用@angular/core的Component

import { Component } from '@angular/core';

或是使用BrowserModule

import { BrowserModule } from '@angular/platform-browser';

Directives


在架構圖中的template右邊,可以看到有一個directive指向template。directive是透過Angular使用內建或自訂directive用來自己定義html元素,並簡化dom操作的功能。可以讓template裡去使用。
一個directive會由@Directive來宣告。其實directive與component的本質是相同的,只是component是一個有template的directive,而directive沒有。我們可以視component為directive的擴展,擴展了template的功能。不過因為component在angular是非常重要的,和directive有不同的意義,因此會將component以及directive在架構上分開來。

directives分為架構型的和屬性型的:
下面是兩個內建架構型的directive範例

<li *ngFor="let hero of heroes"></li>
<app-hero-detail *ngIf="selectedHero"></app-hero-detail>

*ngFor 會讓angular重覆寫許多的 <li>將heroes裡的資料跑過一圈
*ngIf 則會讓angular只有在selectedHero的值為true時才會顯示app-hero-detail元件
而屬性型的directive則例如像是做雙向繫結的ngModel,下面的片段程式會將hero.name的值塞入input的value屬性內,並且監聽使用者修改input的值的事件將修改傳回至hero.name

<input &#91;(ngModel)&#93;="hero.name">

Services


架構圖的左下區是很多的Service注入至Component裡。幾乎所有功能都可以是service,但是它應該是目的明確且狹義的功能。
例如:記錄服務、數據服務、消息服務、稅計算器、應用程序配置等…

以下是一個範例,在這個例子中可以看到我們可以利用getHeroes()和service取得Hero列表,而Service則負責與Backend溝通由API取得資料並且回傳給component

export class HeroService {
  private heroes: Hero[] = [];

  constructor(
    private backend: BackendService,
    private logger: Logger) { }

  getHeroes() {
    this.backend.getAll(Hero).then( (heroes: Hero[]) => {
      this.logger.log(`Fetched ${heroes.length} heroes.`);
      this.heroes.push(...heroes); // fill cache
    });
    return this.heroes;
  }
}

Dependency injection

依賴注入是一種設計模式,可參考這篇文章了解:理解 Dependency Injection 實作原理
在Angular中,大多數依賴是服務,Angular的元件使用依賴注入為自己提供他們需要的服務,對於元件來說,服務必須是完全依賴的,component會在元件內使用service所提供的方法來取得自己所需的資料。

HeroService依賴注入的過程看起來像這樣:

injector會有一個所有Service的集合。如果所需要的Service不在這個集合中,那麼injector將創建一個Service並加進Service集合裡。當component所需要的Service都已經取得後,只要在constructor設定該服務在此元件內的名稱,就可以在元件裡自由的使用服務了。

下面是一個依賴注入的範例

constructor(private service: HeroService) { }

例如上面的程式碼就會讓該component裡的程式碼可以使用service.getHeroes()來取得英雄列表。

在上圖中,上面要被injector選擇的那些service是怎麼來的呢?我們可在根模組或者自己所在的模組裡去提供所有需用到的服務,這樣在這個模組內的所有元件都可以使用這個服務,如下:
src / app / app.module.ts

providers: [
  BackendService,
  HeroService,
  Logger
],

或者也可以在該元件的metadata裡用providers來設定這個元件要使用這個服務,如下:

@Component({
  selector:    'app-hero-list',
  templateUrl: './hero-list.component.html',
  providers:  [ HeroService ]
})

參考資料

Posted on Leave a comment

[新手教程-7] 使用http來與API溝通

Angular處理http是使用rx(Reactive Programming)來實作的,類別的名稱為rxjs
在閱讀此篇之前,建議可以了解一下何謂Reactive Programming,其核心概念為何,這樣會比較容易理解本篇的內容
推薦閱讀:Reactive Programming 簡介與教學(以 RxJS 為例)官網 ReactiveX
RxJS教學:30 天精通 RxJS

使用http來取得api資料

將src/app/hero.service.ts取得資料的方式改由API取得

import { Injectable } from '@angular/core';

import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';

import { Hero } from './hero';
import { HEROES } from './mock-heroes';
import { MessageService } from './message.service';
import { HttpClient, HttpHeaders } from '@angular/common/http'; //加入http類別

@Injectable()
export class HeroService {
  private heroesUrl = 'api/heroes';//設定要讀的api的位置

  //於constructor增加private http: HttpClient
  constructor(private http: HttpClient, private messageService: MessageService) { }

  /** 改由api取得資料,註解掉舊程式
   getHeroes(): Observable<hero&#91;&#93;> {
    // Todo: send the message _after_ fetching the heroes
    this.messageService.add('HeroService: fetched heroes');
    return of(HEROES);
  }*/
  getHeroes (): Observable<hero&#91;&#93;> {
    return this.http.get<hero&#91;&#93;>(this.heroesUrl)
  }
  getHero(id: number): Observable<hero> {
    const url = `${this.heroesUrl}/${id}`;
    return this.http.get<hero>(url);
  }
}

上面程式碼註解地方代表有修改過的地方。
所有的HttpClient方法都返回一個Observable的物件。一般來說,Observable物件會傳送多次資料給接收者,但http.get所取得的值為例外,因為http方法是發出要求並收到回應後就不會再有動作,因此它只會傳一次資料給取得這個資料的接收者。並且需要在被subscribe後才會啟動動作
http.get預設接收的格式為json,會自動對照所取得的json轉化成一個對應屬性的物件以方便取得資料。

處理錯誤

如果在取得api時發生網路錯誤或其他問題導致無法順利取得伺服器資料時,可以用下面的方法來偵聽錯誤
首先,導入rxjs/operators類別

import { catchError, map, tap } from 'rxjs/operators';

接著,使用pipe方法擴展Observable並在裡面下達catchError()來處理錯誤的狀況

getHeroes (): Observable<hero&#91;&#93;> {
  return this.http.get<hero&#91;&#93;>(this.heroesUrl)
    .pipe(
      catchError(this.handleError('getHeroes', []))
    );
}

下面則是catchError的內容

/**
 * 處理http發生的錯誤,讓程式可以繼續正確的運作而不產生exception
 * @param operation - 失敗的操作,這邊是getHeroes
 * @param result - 可不傳入,最後要回傳出去的Observable物件內容,可在裡面塞一些與api連線失敗時要回傳的資料
 */
private handleError<t> (operation = 'operation', result?: T) {
  return (error: any): Observable<t> => {
    return of(result as T);
  };
}

在上面的T是類型參數,在這個例子中,T代表Hero[]。這可以讓程式在打api失敗時依舊可取得符合應用程式所期望的類型的回傳值。

使用http修改伺服器資料

取得資料時使用get,而修改資料時使用put,其參數意義如下:

/** 更新伺服器上的資料 */
updateHero (hero: Hero): Observable<any> {
  // 該HttpClient.put()方法有三個參數:網址、要更新的數據、選項
  return this.http.put(this.heroesUrl, hero, httpOptions).pipe(
    tap(_ => this.log(`updated hero id=${hero.id}`)),
    catchError(this.handleError<any>('updateHero'))
  );
}

其中httpOptions較常設定的是headers,也就是宣告傳去的格式

const httpOptions = {
  headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};

下面是使用updateHero的範例

save(): void {
   this.heroService.updateHero(this.hero)
     .subscribe(() => this.goBack());
 }

在RxJS中,有兩個角色,ObservableSubscription,Observable負責產生資料,創建後不會馬上啟動,而在關注(subscribe)後開始啟動。

使用http新增伺服器資料

新增資料在rxjs裡是使用http.post()

addHero (hero: Hero): Observable<hero> {
  return this.http.post<hero>(this.heroesUrl, hero, httpOptions).pipe(
    catchError(this.handleError<hero>('addHero'))
  );
}

使用addHero方法的範例如下:

add(name: string): void {
  name = name.trim();
  if (!name) { return; } //假如輸入的名稱為空白則不處理
  this.heroService.addHero({ name } as Hero) //{ name } as Hero > 代表創建一個Hero,並且其name的值為輸入值,id為空白
    .subscribe(hero => {
      this.heroes.push(hero); // 將所回傳的物件塞回入列表內
    });
}

使用http刪除伺服器資料

刪除資料我們使用http.delete(),範例如下

deleteHero (hero: Hero | number): Observable<hero> {
  const id = typeof hero === 'number' ? hero : hero.id;
  const url = `${this.heroesUrl}/${id}`;

  return this.http.delete<hero>(url, httpOptions);
}

而呼叫deleteHero的地方則要這樣寫:

delete(hero: Hero): void {
  this.heroes = this.heroes.filter(h => h !== hero);
  this.heroService.deleteHero(hero).subscribe();
}

上面我們可以看到在呼叫deleteHero時,即便刪除完成後沒有要多做任何時,仍然需要加上subscribe()
就像上面說過的,所有rxjs的動作都會在有人subscribe後才會呼叫,因此如果忽略subscribe(),http將不會將刪除請求發送到伺服器!
Observable需要等到有東西subscribe它,才會被執行。

使用搜尋功能

於src/app/hero.service.ts增加搜索功能

searchHeroes(term: string): Observable<hero&#91;&#93;> {
  if (!term.trim()) {
    // 假如沒有傳值則回傳空資料
    return of([]);
  }
  return this.http.get<hero&#91;&#93;>(`api/heroes/?name=${term}`).pipe(
    tap(_ => this.log(`found heroes matching "${term}"`)),
    catchError(this.handleError<hero&#91;&#93;>('searchHeroes', []))
  );
}

創建一個HeroSearchComponent的class

ng generate component hero-search

修改src/app/hero-search/hero-search.component.html

<div id="search-component">
  <h4>Hero Search</h4>

  <input #searchBox id="search-box" (keyup)="search(searchBox.value)" />

  <ul class="search-result">
    <li *ngFor="let hero of heroes$ | async" >
      <a routerLink="/detail/{{hero.id}}">
        {{hero.name}}
      </a>
    </li>
  </ul>
</div>

上面的程式碼中,要注意的是這一行

<li *ngFor="let hero of heroes$ | async" >

如果光使用for迴圈去使用heroes$,Observable不會做任何事,async透過|這個pipe來自動做subscribe的動作,我們可以不用再次的透過subscribe()來讓Observable被執行
而heroes$則是告知這個for迴圈操作的對象是一個Observable而不是一般的值。

接著我們修改src/app/hero-search/hero-search.component.ts

import { Component, OnInit } from '@angular/core';

import { Observable } from 'rxjs/Observable';
import { Subject }    from 'rxjs/Subject';
import { of }         from 'rxjs/observable/of';

import {
   debounceTime, distinctUntilChanged, switchMap
 } from 'rxjs/operators';

import { Hero } from '../hero';
import { HeroService } from '../hero.service';

@Component({
  selector: 'app-hero-search',
  templateUrl: './hero-search.component.html',
  styleUrls: [ './hero-search.component.css' ]
})
export class HeroSearchComponent implements OnInit {
  heroes$: Observable<hero&#91;&#93;>;
  private searchTerms = new Subject<string>();

  constructor(private heroService: HeroService) {}

  // Push a search term into the observable stream.
  search(term: string): void {
    this.searchTerms.next(term);
  }

  ngOnInit(): void {
    this.heroes$ = this.searchTerms.pipe(
      // wait 300ms after each keystroke before considering the term
      debounceTime(300),

      // ignore new term if same as previous term
      distinctUntilChanged(),

      // switch to new search observable each time the term changes
      switchMap((term: string) => this.heroService.searchHeroes(term)),
    );
  }
}

上面的程式碼中,heroes也需要寫為heroes$並宣告它是一個Observable

heroes$: Observable<hero&#91;&#93;>;

searchTerms在這邊被宣告為RxJS裡的Subject.

private searchTerms = new Subject<string>();

search(term: string): void {
  this.searchTerms.next(term);
}

一個Subject自己本身是observable,並且接收一個observable為參數。因此我們可以對Subject做許多和Observable相同的動作。
也可以使用next(value)去傳值至searchTerms裡,對任何的Observable物件也都能這樣操作。

<input #searchBox id="search-box" (keyup)="search(searchBox.value)" />

在這邊的search()方法與keyup事件綁定,searchTerms會返回一個Observable的觀察結果。

另外searchTerms的寫法

this.heroes$ = this.searchTerms.pipe(
  // 每次擊鍵後等待300毫秒,然後再搜尋他
  debounceTime(300),

  // 假如與上次的值相同則忽略
  distinctUntilChanged(),

  // 當term變更時更新搜索結果,它取消並丟棄先前的搜索可見性,只返回最新的可見的搜索服務。
  switchMap((term: string) => this.heroService.searchHeroes(term)),
);

注意:使用switchMap(),每個有資格的鍵事件都可以觸發HttpClient.get()方法調用。即使在請求之間暫停了300毫秒,也可能有多個HTTP請求在運行,並且可能不會按照發送的順序返回。
switchMap()保留最初的請求順序,同時只返回最近的HTTP方法調用的observable。來自先前搜尋的結果被取消並被丟棄。
不過,取消先前的searchHeroes() Observable 並不會中止掛起的HTTP請求。不需要的結果在到達應用程序代碼之前就被丟棄了。

最後成品樣子如下:

完整範例檔請見: live example / download example.

Posted on Leave a comment

[新手教程-6] 使用Routing來切換頁面

什麼是Routing?

Routing意指路由器,也就是由一個路由器來決定現在要顯示的頁面是什麼
在套用Routing時,會有下列的實踐流程
1. 套用轉址設定(讓伺服器不去真正網址所在的位置去讀取資料,而改由Routing來決定現在要顯示什麼畫面)
2. 由url分析要顯示的狀態是什麼
3. 由狀態去獲得真正要取得那些資訊
4. 從這些資訊組成實體
5. 套用導覽動作,由這個畫面切換至另一個畫面

在Angular裡,最佳做法是將載入和設定Routing放在一個top-level的模組內,並於AppModule內來import所有的路由資訊。
按照慣例,模組AppRoutingModuleapp-routing.module.ts會放在src/app

Angular的Routing 產生的虛擬 URL,並不是真的存在於檔案系統裡,因此需要伺服器能夠支援,否則重新整理時會出現404 not found。

新增AppRoutingModule

透過下列CLI的指令來新增一個Routing

ng generate module app-routing --flat --module=app

--flat 的意思是將產出文件放在src/app裡,而非自己一個資料夾
--module=app 則是告知CLI註冊這個Routing在AppModule的imports裡

新產生的src/app/app-routing.module.ts檔案內容如下:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

@NgModule({
  imports: [
    CommonModule
  ],
  declarations: []
})
export class AppRoutingModule { }

一般我們不會在Routing裡宣告元件,因此可以將@NgModule.declarations及CommonModule的宣告刪除
通常我們會使用Routes與RouterModule來實做Routing,修改後的檔案如下:

import { NgModule }             from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

@NgModule({
  exports: [ RouterModule ]
})
export class AppRoutingModule {}

增加Routes

此時我們來設定不同路徑要導向的位置,如下面的程式碼

import { HeroesComponent }      from './heroes/heroes.component';

const routes: Routes = [
  { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
  { path: 'heroes', component: HeroesComponent },
  { path: 'detail/:id', component: HeroDetailComponent }
];

上述程式碼代表在網址為http://localhost/heroes時,會在標籤內顯示HeroesComponent這元件的內容。
另外,也要設定當伺服器剛運行時(http://localhost/),要顯示的模組。

設定完Router的map後,將之設定進RouterModule.forRoot()裡
然後再將這整個Router功能設定為可以被export,之後便可以在app.module裡import這個Router

import { NgModule }             from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { DashboardComponent }   from './dashboard/dashboard.component';
import { HeroesComponent }      from './heroes/heroes.component';
import { HeroDetailComponent }  from './hero-detail/hero-detail.component';

const routes: Routes = [
  { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
  { path: 'dashboard', component: DashboardComponent },
  { path: 'detail/:id', component: HeroDetailComponent },
  { path: 'heroes', component: HeroesComponent }
];

@NgModule({
  imports: [ RouterModule.forRoot(routes) ],
  exports: [ RouterModule ]
})
export class AppRoutingModule {}

而detail/:id裡的:id可於HeroDetailComponent裡去取得,來做進一步更詳細的資料顯示
取得方法如下:

ngOnInit(): void {
  this.getHero();
}

getHero(): void {
  const id = +this.route.snapshot.paramMap.get('id');//用route.snapshot.paramMap.get取得Routing時傳入的變數
  this.heroService.getHero(id)
    .subscribe(hero => this.hero = hero);
}

我們可以發現,例子中不同的path為所傳入的參數不同,它們代表不同的意義,可以更多元化的設定Routing的功能。Routes可傳入的參數有下面這些

interface Route {
  path?: string //瀏覽器上方的網址列的字串
  pathMatch?: string //當導航至此網址時要顯示的元件
  matcher?: UrlMatcher //網址列過濾器
  component?: Type<any>
  redirectTo?: string //要轉址到那邊
  outlet?: string
  canActivate?: any[]
  canActivateChild?: any[]
  canDeactivate?: any[]
  canLoad?: any[]
  data?: Data //要傳入元件裡的資料
  resolve?: ResolveData
  children?: Routes
  loadChildren?: LoadChildren
  runGuardsAndResolvers?: RunGuardsAndResolvers
}

在這邊可看一下APP_BASE_HREF的設定方式
如果沒有設定,會跑出錯誤訊息如下:

另一個簡單的方式則是在index.html增加下面這行,也可以解決這個錯誤

<base href="/">

在src/app/app.component.html中增加這個導航功能

<h1>{{title}}</h1>

<router-outlet></router-outlet>

利用routerLink來增加導航用超連結

修改src/app/app.component.html如下:

<h1>{{title}}</h1>


<nav>
  <a routerLink="/heroes">Heroes</a>
</nav>

<router-outlet></router-outlet>

如果要回上一頁則使用

goBack(): void {
  this.location.back();
}

這篇文章的範例檔案可由此觀看: live example / download example

參考資料

Posted on Leave a comment

[新手教程-5] 建立Service

這邊是上一篇Angular的主從元件開發的範例檔案:按此下載

創建 HeroService

使用Angular CLI創建一個名為hero的服務

ng generate service hero

我們會看到在src/app下多出了兩個檔案:hero.service.spec.ts和hero.service.ts

打開src/app/hero.service.ts可以看到下面內容

import { Injectable } from '@angular/core';

@Injectable()
export class HeroService {

  constructor() { }

}

注意到我們用@Injectable() 來宣告HeroService這個類別,代表這個類別可能本身有依賴注入,對Service來說是強烈建議要加上這個宣告

將HeroService提供給app使用

Service可以供appModule、或任何Component使用,如果要給appModule使用,
則需在src/app/app.module.ts的@NgModule裡加入

providers: [ HeroService ],

這邊有更多NgModule的說明:NgModule

另外,若要使用CLI來自動完成providers的設定,則可以用下面的指令

ng generate service hero --module=app

修改HeroesComponent去使用HeroService裡的資料

打開src/app/heroes/heroes.component.ts,修改成以下的內容

import { Component, OnInit } from ‘@angular/core’;
import { HeroService } from ‘../hero.service’;//1. 增加HeroService並移除HEROES
import { Hero } from ‘../hero’;

@Component({
selector: ‘app-heroes’,
templateUrl: ‘./heroes.component.html’,
styleUrls: [‘./heroes.component.css’]
})
export class HeroesComponent implements OnInit {

heroes: Hero[];//2. 在這邊先不設定內容

selectedHero: Hero;

constructor(private heroService: HeroService) { }//3. 宣告注入的service

getHeroes(): void {
this.heroes = this.heroService.getHeroes();//4. 取service內的資料放進變數內
}

ngOnInit() {
this.getHeroes();//5. ngOnInit會在初始化元件時被呼叫,在此時去取得heroes的值
}

onSelect(hero: Hero): void {
this.selectedHero = hero;
}
}

讓getHeroes被更動時能夠被通知

Observable是RxJS library中很重要的一個功能。

當我們在src/app/hero.service.ts的getHeroes方法回傳值前面加上Observable宣告,當service內的資料變動時,其他注入的內容也會同步被異動

首先要先import下面兩個檔案

import { Observable } from 'rxjs/Rx';
import { of } from 'rxjs/observable/of';

然後在getHeroes的回傳直類型前加上Observable宣告

getHeroes(): Observable<hero&#91;&#93;> {
  return of(HEROES);
}

注:若使用HttpClient.get<Hero[]>()亦會傳回Observable<Hero[]>,只是內容是由http傳來

在接收Observable物件時的方法也與非Observable物件的方式不同,過去若非Observable物件時,在hero.component.ts裡的getHeroes函數如下

getHeroes(): void {
  this.heroes = this.heroService.getHeroes();
}

若要使用Observable宣告則須改為:

getHeroes(): void {
  this.heroService.getHeroes()
      .subscribe(heroes => this.heroes = heroes);
}

Observable.subscribe()是最關鍵的相異之處
最大的差異在於我們送出修改到伺服器傳回回應之間,未使用Observable.subscribe()方法時,它不會先凍結UI不讓使用者修改,而有可能會造成資料的不一致。而使用Observable.subscribe()可以避免這個問題。

本日範例下載: live example / download example

Posted on Leave a comment

[新手教程-4] Angular的主從元件開發

創立hero-detail元件

在前一篇新手教程3-使用angular的迴圈及判斷式等功能裡,我們在顯示selectedHero的資訊時是與列表寫在同一個頁面
但如果顯示詳細資訊的地方有需要額外拆分出來,可以在創立一個元件,並將selectedHero傳入元件

ng generate component hero-detail

寫入hero-detail的內容

開啟檔案src/app/hero-detail/hero-detail.component.html,這邊會將selectedHero的名稱改為hero


<div *ngIf="hero">

<h2>{{ hero.name | uppercase }} Details</h2>


<div><span>id: </span>{{hero.id}}</div>


<div>
    <label>name:
      <input &#91;(ngModel)&#93;="hero.name" placeholder="name"/>
    </label>
  </div>

</div>

讓元件能接受外部傳送物件進來

開啟src/app/hero-detail/hero-detail.component.ts

import { Hero } from '../hero';

在hero-detail.component.ts裡面,hero一定要以@Input來宣告這個物件

@Input() hero: Hero;

而在HeroesComponent裡,則會以下面的宣告來將hero的值傳入

<app-hero-detail &#91;hero&#93;="selectedHero"></app-hero-detail>

現在我們打開heroes.component.html,修改後的網頁內容會如下

<h2>My Heroes</h2>



<ul class="heroes">

<li *ngFor="let hero of heroes" &#91;class.selected&#93;="hero === selectedHero" (click)="onSelect(hero)">
    <span class="badge">{{hero.id}}</span> {{hero.name}}
  </li>

</ul>


<app-hero-detail &#91;hero&#93;="selectedHero"></app-hero-detail>

今日練習的範例連結:live example / download example

Posted on Leave a comment

[新手教程-3] 使用Angular的迴圈及判斷式等功能

延續上一篇的範例: 請按此下載.

利用*ngFor來做迴圈顯示完整列表

首先開啟src/app/heroes/heroes.component.ts
設定一個變數heroes

    heroes: Hero[] = [
    { id: 11, name: 'Mr. Nice' },
    { id: 12, name: 'Narco' },
    { id: 13, name: 'Bombasto' },
    { id: 14, name: 'Celeritas' },
    { id: 15, name: 'Magneta' },
    { id: 16, name: 'RubberMan' },
    { id: 17, name: 'Dynama' },
    { id: 18, name: 'Dr IQ' },
    { id: 19, name: 'Magma' },
    { id: 20, name: 'Tornado' }
  ];

接著開啟app.component.html,利用*ngFor來迴圈式的顯示列表內容

<ul class="heroes">
  <li *ngFor="let hero of heroes">
    <span class="badge">{{hero.id}}</span> {{hero.name}}
  </li>
</ul>

這時網頁上就可以看到成果如下圖:

更多資訊有關於ngFor

使用(click)來設定觸發事件

開啟src/app/heroes/heroes.component.ts,在li裡面增加click的事件

  <li *ngFor="let hero of heroes" (click)="onSelect(hero)">

在src/app/heroes/heroes.component.ts增加處理的函數

selectedHero: Hero;

onSelect(hero: Hero): void {
  this.selectedHero = hero;
}

更多資訊有關於event-binding

以條件式去增加元件的類別

打開heroes.component.html,在li內增加 [class.要增加的類別名稱]=”條件式為true時增加”

<li *ngFor="let hero of heroes"
  &#91;class.selected&#93;="hero === selectedHero"
  (click)="onSelect(hero)">
  <span class="badge">{{hero.id}}</span> {{hero.name}}
</li>

這樣當hero === selectedHero為真時,這個li就會被加上selected這個類別
接下來再到src/app/heroes/heroes.component.css設定該類別的CSS,就能突顯現在所選擇的是那一個了
.selected{
color:red;
}

Posted on Leave a comment

[新手教程-2] 創立Angular的元件

使用CLI來為專案建立一個元件

ng generate component heroes

用這個指令,CLI會為我們初始化一個新的元件樣版
這時我們開啟app/heroes/heroes.component.ts

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.css']
})
export class HeroesComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }

}

只要創建元件,都必需從Angular去import Component。
而@Component則是用來定義這一個元件的相關資訊,有三個metadata

  1. selector: the components CSS element selector以及在HTML裡要宣告的TAG名稱
  2. templateUrl: 要使用的HTML樣版位置
  3. styleUrls: 專為這個元件設定的CSS

要注意的是,我們通常會使用export class,以方便在其他的模組裡可以import來使用

修改元件內容

打開heroes.component.ts

import { Component, OnInit, ViewEncapsulation } from '@angular/core';

@Component({
  selector: 'app-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.css'],
  encapsulation: ViewEncapsulation.None
})
export class HeroesComponent implements OnInit {
  hero = 'heroes works!';//增加一個變數
  constructor() { }

  ngOnInit() {
  }

}

修改heroes.component.html,使用{{hero}}來顯示剛剛在TS檔裡定義的變數

<h1>{{hero}}</h1>

將元件放進頁面裡

打開src/app/app.component.html

<app-heroes></app-heroes>

這時候就可以在頁面中看到剛剛我們所增加的內容

使用物件

創建一個Hero物件
src/app/hero.ts

export class Hero {
   id: number;
   name: string;
}

打開src/app/heroes/heroes.component.ts,給予物件正確的值

import { Component, OnInit } from '@angular/core';
import { Hero } from '../hero';

@Component({
selector: 'app-heroes',
templateUrl: './heroes.component.html',
styleUrls: ['./heroes.component.css']
})
export class HeroesComponent implements OnInit
{
//在這邊設定物件內容
hero: Hero = {
id: 1,
name: 'Windstorm'
};

constructor() { }

ngOnInit() {
}

}

顯示在heroes.component.ts所設定的值



<h2>{{ hero.name }} Details</h2>




<div><span>id: </span>{{hero.id}}</div>




<div><span>name: </span>{{hero.name}}</div>


如果希望將變數格式化
則可以使用


<h2>{{ hero.name | uppercase }} Details</h2>


這個格式化的功能叫做Pipes,更多的說明請見Pipes說明

雙向繫結

Angular一個很方便的功能就是可以支持雙向繫結,使用[(ngModel)]能做到當欄位的值改變時,TS裡變數的值也同時被更改。
這個功能在做表單驗證時非常方便
詳細使用說明請見:NgModules
使用方法: 在src/app/heroes/heroes.component.html加上下面這段

<div>
    <label>name:
      <input &#91;(ngModel)&#93;="hero.name" placeholder="name">
    </label>
</div>

接著要去app.module.ts去加上import資訊讓專案能夠使用ngModel標籤
要注意是在app.module.ts裡喔!
先import並且將FormsModule加進@ngModule的imports列表內,讓下面所有的元件都可以使用FormsModule的功能
import { FormsModule } from ‘@angular/forms’; // <-- NgModel lives here[/code] [code lang="js"]imports: [ BrowserModule, FormsModule ],[/code] 接著,要把剛剛我們所建立的元件HeroesComponent放進@NgModule.declarations裡 [code lang="js"]import { HeroesComponent } from './heroes/heroes.component';[/code] 在app.module.ts的@NgModule增加 [code lang="js"] declarations: [ AppComponent, HeroesComponent ], [/code] 這時,我們會發現我們更動input裡的文字時,model的值也會被更改 今日練習成果下載:live example / download example.

Posted on Leave a comment

[新手教程-1] 建立一個Angular5的專案

這系列的文章為我在官網學習Angular 5時所紀錄下來的學習筆記。
原文的原始教程都可在Angular的Docs看到。

這三十天的筆記大綱預計分為新手教程、功能介紹、技術支援三個部份:

  • 新手教程:一個最簡單的專案,用step by step的方式引導大家去做
  • 功能介紹:一個個介紹Angular裡面各別的功能和特性
  • 技術支援:涵蓋如何佈署、設定以及angular所使用的npm、typescript等的設定

建立專案

確認電腦已有安裝NodeJS(6.9.x以上版本)以及NPM(3.x.x以上版本)

創建專案

ng new my-app

開啟專案

cd my-app
ng serve --open

編輯第一個自己的頁面

打開src/app/app.component.ts,改為

import { Component } from '@angular/core';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent {
    title = '我的第一個網頁';
}

打開src/app/app.component.html,將內容改為

這是 {{title}}!

src資料夾內的檔案架構

檔案 目的
app/app.component. {ts,html,css,spec.ts} 所有的 Componet、Service、Pipe、Unit test 等等..程式碼都是放在這個資料夾,而 app.component 則是 Angular CLI 預設建立的 root component
app/app.module.ts 預設的 root module , 告訴 Angular 有哪些 Components 、Modules、 Services,讓 Angular 知道如何 assemble the application
assets/* 放圖片或者是建立 application 需要用的到材料
environments/* 設定 Angular 程式碼會用到的參數,很像 Web.config 的東西。 預設為 environment.ts , 要產生不同的 environment 的話,參考命名規則為 environment.xxx.ts,在執行或者 build Angular application 的時候加入 xxx 的參數,則可以指定到該 environmnet ,例如 : ng build -xxx、 ng serve -xxx
favicon.ico 網頁頁籤的 icon
index.html 網頁進入點,當使用者拜訪網站的時候,是執行到這個頁面。 在大部分的情況,是不需用編輯的, Angular CLI 在 build application 的時候會自動加入 js 和 css
main.ts 若使用JIT,這個文件為JIT compiler和bootstraps的root module,也就是編譯的起始點。也可以用ng serve –aot,改為AOT編譯而不用修改任何的code
polyfills.ts 因為不同的瀏覽器會支援不同的 web standards, polyfills 就像補丁的概念,補足些沒有支援的部分。Browser Support guide for more information.
styles.css 放置 global styles 的 CSS
test.ts unit tests 的進入點,有些 unit tests 的設定也寫在這邊
tsconfig.{app|spec}.json TypeScript 編譯設定檔(tsconfig.app.json) and for the unit tests (tsconfig.spec.json).

 

根目錄資料夾檔案列表

檔案 目的
e2e/ 負責放 End-to-End 測試程式碼的資料夾
node_modules/ Node.js 建立的資料夾,負責放 third party modules 的資料夾,其 thrid party modules 清單則放在 package.json裡面
.angular-cli.json 放 Angular CLI 的設定檔 Angular CLI Config Schema
.editorconfig 幫助開發者使用不同的 IDEs 保持檔案格式一致性的設定檔。更多資訊請見此
.gitignore Git 的設定檔,讓指定檔案不會 commit 到 Source control
karma.conf.js Karma test 的 unit tests 設定
package.json npm 設定檔, third party 清單與版本資訊
protractor.conf.js Angular end-to-end test framework Protractor 設定檔
README.md 專案基本說明文件
tsconfig.json TypeScript 編譯器設定檔案
tslint.json Codelyzer(用以保持code style的一致性)和TSLint的設定檔。