正如人子來,不是要受人的服事,乃是要服事人,並且要捨命,作多人的贖價。
在這一回,我們談一下幕後英雄,前面我們介紹過的元件(component) 是有頭有臉的人物,有內涵(ts), 有容貌,是個枱面上的人物,包含許多顯示(html, css) 的設定;現在我們介紹服務(service)則沒有臉可露,就當個幕後工作人員,但也算是個角色,通常用以讀取後端程式的資料(以後會介紹)或是存放一些大家共用的資料(如本例)。
首先,產生一個服務, 可以用 CLI/VS.Code 或是 stackblitz, 在這個服務中,我們以存放聖經,舊約中37卷書的中英文名稱及縮寫為例,取名 abbreviations, 用CLI指令是
ng generate service abbreviations
注意產生的位置,各種方法會略有不同,stackblitz 會放在 /src, 而CLI 會放在 /src/service,這和將來(import)時使用的相對路徑會有些影響。在寫程式時,scope 很重要, 以這個服務而言,scope 有二層意義,第一層是何時產生,何時消滅,第二層是誰可以使用。有一個專有名詞,叫做注入(injection), 把服務想成是自來水,但注入點就像是水龍頭,水龍頭所在的位置會影響到 scope (何處有水?哪裡可以取水?誰可以用水?).
要設定注入點有二個方式,一個是註記在使用方
,一個是註記在被使用方
(服務本身)。不論是註記在哪一方,都是在建構函式(constructor)時注入(實化,產生物件)。若是註記在使用方,則寫在app.module.ts 中 @NgModule / provider 內,稱作 module injection, 例如:
@NgModule({
providers: [ AbbreviationsService ]
})
export class AppModule {
constructor(private service: AbbreviationsService) { … }
}
服務將在應用起啟時創建,同時整個應用的所有模組均可以使用,只有一份實體。稱作 module injection。若只在某元件內使用時,稱作 component injection, 可以在元件的.ts 檔中 @Component / provider 內註記,如:
@Component({
providers: [AbbreviationsService]
})
export class MyComponent {
constructor(private service: AbbreviationsService) {
// ...
}
}
另一種方式是註記在服務內(被使用方),先看一下我們用的服務內容:
abbreviations.service.ts
@Injectable({
providedIn: 'root',
})
這和module injection 相當 (app.module.ts) 就不用改了。Root 也可以改成模組名稱,此時必須import 這個模組。
在寫服務之前,有個相關文內,因為我們要透過服務存取共用的資料,所以還要創建資料結構。因為在本例中,共有四個欄位,英文縮寫,英文書名,中文縮寫,中文書名。資料結構,即是所謂 model, 假設命名為 book, 用的是一般的class, 可以用
ng generate class book
其內容(book.ts)程式如下:
export class Book {
constructor(
private abbr_en: string,
private en: string,
private abbr_tw: string,
private tw: string
){};
}
看不太懂?這是新版本提供物件宣告的寫法,這就相當於
export class Book {
private abbr_en: string,
private en: string,
private abbr_tw: string,
private tw: string
constructor(a: string, b: string, c: string, d: string){
this.abbr_en=a;
this.en=b;
this.abbr_tw=c;
this.tw=d;
};
}
是不是簡單多了,如果這個也看不懂,就要留言了。原本在這系統的文章發文前,目標是給完全初學者的,寫著寫著,其實還是有一些基本要求,例如,要有OOP,Java的基礎的。
如果程式比較複雜,可以把所有的資料結構放在同一個檔案夾內,如 /src/app/model, 服務也可以,例如 /src/app/service (就是注意 import 時的相對路徑即可), abbreviations.service.ts 如下:
import { Injectable } from '@angular/core';
import { Book } from './book';
@Injectable({
providedIn: 'root',
})
export class AbbreviationsService {
private BOOKS: Book[] = [
new Book("Gen", "Genesys", "創", "創世紀"),
new Book("Exo", "Exodus", "出", "出埈及記"),
… (省略, 若要全文, 請參考附錄連結) …
]
constructor() { }
public books(): Book[] { return this.BOOKS; }
}
通常我們用全部大寫來表示常數,第一個字母大寫表示資料結構,第一個字母小寫表示變數。
範例中,在app.component中使用這個服務,因此只要改 app.component.ts
import { Component, OnInit} from '@angular/core';
import { AbbreviationsService } from '../abbreviations.service';
import { Book } from '../book';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
name = 'Faust-Service';
books: Book[];
constructor(private service: AbbreviationsService) { }
ngOnInit() {
this.books = this.service.books();
}
}
可以留意AbbreviationsService是在constructor創建,在ngOnInit()中使用,設定成變數 this.books, 這個變數會在 app.component.html 中使用。即 {{ name }}
<div class="card my-5">
<div class="card-body">
<table class="table table-bordered table-striped">
<tr>
<th colspan="4">This is {{ name }} speaking!</th>
</tr>
<tbody>
<tr *ngFor="let book of books">
<td class="book_abbr">{{ book.abbr_en }}</td>
<td class="book_name">{{ book.en }}</td>
<td class="book_abbr">{{ book.abbr_tw }}</td>
<td class="book_name">{{ book.tw }}</td>
</tr>
</tbody>
</table>
</div>
</div>
在這裡,我們看見Angular產生迴圈的作法 *ngFor="let book of books"
這相當於 Java 中的指令 for( book : books )
, 而這個 books 即為 app.component.ts, export AppComponent, ngInit() 中的 this.books, 最後在 stackblitz 中顯示的結果如下:
Link to project in stackblitz: demo
Link to project in stackblitz: source