动机
组件负责呈现到模板中的数据。拥有外部_服务_可以简化这一责任。另外,封装外部更容易维护。
将过多的职责委托给单个组件会使组件类复杂化。如果这些责任适用于多个组件怎么办?复制和粘贴这种逻辑是非常糟糕的做法。未来对逻辑的任何更改都将难以实现和测试。
Angular意味着通过服务和依赖注入来解决这个问题。这两个概念协同工作以提供_模块化_功能。
组件也不需要提供任何无关的信息。服务代表其_服务_的组件导入其运行所需的内容。组件只需要实例化服务。从那里,他们用_服务_实例化的服务实例自己的需要。
至于测试和未来的修改,所有逻辑都集中在一个地方。该服务从其源实例化。对源的测试和修改适用于注入服务的任何位置。
服务简介
服务是Angular中可用的一种原理图 。它由命令行界面(CLI) ng generate service [name-of-service]
: ng generate service [name-of-service]
。将[name-of-service]
替换为更好的名称。 CLI命令产生以下内容。
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class LoggerService {
constructor() { }
}
服务的逻辑在同类中是不同的。 Angular将类解释为基于@Injectable
装饰器的_可注入_服务。注射服务必须_注册_注射器。
该组件在注入器提供该实例时实例化服务。请继续阅读下一节,了解有关注入器的更多信息。
提供的@Injectable
元数据字段providedIn: 'root'
以当前应用程序的根模块( app.module.ts
)为目标。它注册到模块的注入器的服务,以便它可以_注入_该服务到任何孩子。
注射器是Angular依赖注射系统的构建模块。在继续提供服务之前,注射器是集中注意力的好地方。
注入器
从app.module.ts
开始的应用程序包含一个注入器层次结构。它们与应用程序树中的每个模块和组件一起存在。
绿色圆圈表示注射器。它们为实例化组件提供服务实例。根据注册服务的注入器,组件可能有也可能没有。
在应用程序根目录( app.module.ts
)注册的服务可供所有组件使用。组件的注入器可能没有注册某个服务。如果是这种情况并且组件请求其实例化,则注入器将推迟到其父实例。这种趋势持续到达根注入器或找到服务。
查看图表,假设服务在B点的注入器处注册。 C点和向下的所有组件都无法访问B注入器处注册的服务。注射器永远不会将他们的孩子推迟到服务实例。
依赖注入
有多种方法可以使用应用程序的注入器注册服务。
@Injectable
的providedIn: 'root'
元数据字段提供了最推荐的方法。 Angular 6发布了此元数据字段。
如前所述, providedIn: 'root'
使用根模块注入器注册服务。因此,它可以在整个应用程序中实例化。
providedIn: 'root'
的新颖性providedIn: 'root'
是树木震动 。如果该服务,尽管其注册未使用时,它会从在运行时应用动摇 。这样它就不会消耗任何资源。
另外两种方式更直接,更传统。当然,他们不提供树木摇晃。
服务可以向组件树中的任何注入器注册。您在@Component
元数据字段中将服务作为提供者插入: providers: []
。该组件及其子组件可以使用该服务
在第三个注册策略中, providers: []
元数据在@NgModule
装饰器中作为自己的字段存在。该服务可从模块实例化到底层组件树。
请记住,与@NgModule
providedIn: 'root'
, @NgModule
注册不提供树抖动。两种策略都是相同的。一旦服务向@NgModule
注册,即使应用程序未使用它,它也会消耗资源。
服务继续
接下来写下一个实际的服务。回顾一下,服务代表应用程序的组件处理某些功能。
服务擅长处理常见操作。这样做可以使组件免除责任。它节省了不必在多个组件上重写常用操作的时间。它也更容易测试,因为代码在一个地方。更改只需要在一个地方进行,而无需在其他地方搜索。
用例
几个例子对完全理解服务有很大帮助。
控制台日志
API请求
两者在大多数应用中都很常见。拥有处理这些操作的服务将降低组件的复杂性。
控制台日志
此示例从基础@Injectable
骨架构建。通过执行CLI( ng generate service [name-of-service]]
)可以获得框架。
// services/logger.service.ts
import { Injectable } from '@angular/core';
interface LogMessage {
message:string;
timestamp:Date;
}
@Injectable({
providedIn: 'root'
})
export class LoggerService {
callStack:LogMessage[] = [];
constructor() { }
addLog(message:string):void {
// prepend new log to bottom of stack
this.callStack = [{ message, timestamp: new Date() }].concat(this.callStack);
}
clear():void {
// clear stack
this.callStack = [];
}
printHead():void {
// print bottom of stack
console.log(this.callStack[0] || null);
}
printLog():void {
// print bottom to top of stack on screen
this.callStack.reverse().forEach((logMessage) => console.log(logMessage));
}
getLog():LogMessage[] {
// return the entire log as an array
return this.callStack.reverse();
}
}
LoggerService通过@Injectable
元数据向根模块注册。因此它可以在app.component.html
实例化。
// app.component.ts
import { Component, OnInit } from '@angular/core';
import { LoggerService } from './services/logger.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
logs:object[] = [];
constructor(private logger:LoggerService) { }
updateLog():void {
this.logger.printHead();
this.logs = this.logger.getLog();
}
logMessage(event:any, message:string):void {
event.preventDefault();
this.logger.addLog(`Message: ${message}`);
this.updateLog();
}
clearLog():void {
this.logger.clear();
this.logs = [];
}
ngOnInit():void {
this.logger.addLog(“View Initialized”);
this.updateLog();
}
}
模板HTML提供了对组件使用LoggerService的进一步了解。
<!-- app.component.html -->
<h1>Log Example</h1>
<form (submit)="logMessage($event, userInput.value)">
<input #userInput placeholder="Type a message...">
<button type="submit">SUBMIT</button>
</form>
<h3>Complete Log</h3>
<button type="button" (click)="clearLog()">CLEAR</button>
<ul>
<li *ngFor="let log of logs; let i=index">{{ logs.length - i }} > {{ log.message }} @ {{ log.timestamp }}</li>
</ul>
这有ToDo应用程序的感觉。您可以记录消息并清除消息日志。想象一下,如果服务中的所有逻辑都被推入AppComponent!它会使代码变得复杂。 LoggerService保留从核心AppComponent类封装的与日志相关的代码。
获取请求
这是另外一个值得玩的例子。这个例子可以归功于typicode的JSONPlaceholder 1。 API是公开的,可以免费使用。
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
// https://jsonplaceholder.typicode.com
// public API created by typicode @ https://github.com/typicode
interface Post {
userId:number;
id:number;
title:string;
body:string;
}
@Injectable({
providedIn: 'root'
})
export class PlaceholderService {
constructor(private http:HttpClient) { }
getPosts():Observable<Post[]> {
return this.http.get('https://jsonplaceholder.typicode.com/posts');
}
getPost(id:number):Observable<Post> {
return this.http.get(`https://jsonplaceholder.typicode.com/posts/${id}`);
}
}
这更像是一个独立的部分,而不是一个完全充实的例子。获取请求往往更适合作为可注入服务。替代方案是一个过于复杂的组件。注入的类订阅了PlaceholderService预配置的内容。
结论
服务和依赖注入非常有用。它们允许开发人员封装通用逻辑并注入多个不同的组件。仅此一项对于任何未来的维护都是非常方便的。
注射器作为中间人。它们在实例化组件和注册服务库之间进行调解。注入者为其分支子项提供这些可实例化的服务。
有关服务和依赖注入的更多信息,请参阅下面的几个链接。
更多Angular教程
学习更多Angular教程