在開發元件的時候,我們會希望元件間的相依性不要太高,否則當專案越來越大時,修改一個小地方,就會有一連串元件都受影響,牽一髮而動全身。
所以我們會希望,將一些功能獨立出來,寫成 Service,再將這個 Service 注入到個別的元件裡,降低元件間彼此的相依性。並且因為這樣的方式,未來若需要抽換 Service,也比較不費力。
這邊的方法叫依賴注入(Dependency Injection),簡稱 DI,是一種降低相依性的設計模式。簡單來講,就是把一些自己做的事情,交給第三方,第三方再透過注入的方式,把手伸進來幫你做事。
那依照慣例,我們先用 Angular-CLI 建立一個 service:
ng generate service <service name>
ng g s <service name>
然後你會發現這次 Angular-CLI 沒有去改動 module.ts
的檔案了,((沒甚麼了不起的。
我創建一個叫 http 的 Service,將來要透過它來 call 各種 Web API:
初始的 service.ts
會有一個 @Injectable
的 Decorator,並且幫我們填入了 providedIn: 'root'
,這表示當有任何一個元件,調用這個 Service 時,Angular 會去 root injector 找看看有沒有這個 Service 的實體,如果沒有,就創建一個實體,並放在 root injector。
往後其他元件要調用這個 Service,就會到 root injector 去找,並且使用。所以整個程式只會有一個 Service 的實體,裡面的變數、 function 都是共用的。
那如果你不想要讓整個程式都使用這個 Service,或是有其他的考量,可以把它拿掉,然後到 Module 裡把剛剛的 Service 加到 providers
裡:
// http.service.ts
@Injectable()
export class HttpService {
...
}
// account.module.ts
import { HttpService } from './http.service';
@NgModule({
...
providers: [HttpService]
})
這樣一來,同一個 module 下的元件,在調用 HttpService
時就會取得同一個實體。
現在我們可以在 Service 裡寫一些程式碼了:
export class HttpService {
display() {
alert('Alert from HttpService.');
}
}
接下來我想在其他元件也調用這個 function,我們到 login.component.ts
,將這個服務注入。
我們新增一個成員變數叫 http
,並在 constructor()
內初始化。由於我們剛剛有把 HttpService
註冊進 account.module.ts
的 providers
中,所以在初始化這個元件時,會自動完成「到 injector 找 Service 實體」的這段動作。
private http: HttpService;
constructor(httpService: HttpService) {
this.http = httpService;
}
也可以只寫這樣的語法,這樣在 constructor()
時,會自動產生一個成員變數叫 http
。
constructor(private http: HttpService) { }
在完成注入後,如果你的 @Injectable()
沒有設定 providedIn: 'root'
,也沒有把 Service 放到該放的 providers
中。那執行 constructor()
時就會噴一堆 Error:
現在 LoginComponent
已經被注入了 HttpService
,這表示我們可以去調用 HttpService
的 function 了。
於是我不小心在 loginAction()
加入了 HttpService
的 display()
:
loginAction() {
this.loginRequest.emit(true);
this.http.display();
}
點下 Login 時,就可以調用 HttpService
的 display()
:
如此我們就完成了一個簡單的 Service 實作。