本节将创建用户服务UserService。UserService用于为视图提供数据。并且,不需要使用new关键字来创建此服务的实例,而要依靠Angular的依赖注入机制把服务实例注入UsersComponent组件的构造函数中。
使用服务有以下好处:
(1)有利于代码分层管理。在一个模块中,服务位于处理核心业务逻辑层。
(2)依靠Angular的依赖注入机制,能够轻松地将服务注入需要使用服务的组件中,从而简化了服务的实例化。
使用Angular CLI创建一个名为“user”的服务。
可以在控制台上看到如下输出信息:
该命令在src/app/user.service.ts中生成的是UserService类的骨架。UserService类的代码如下:
在src/app/user.service.spec.ts中生成的是UserService类的测试代码,如下:
注意:这个新的服务导入了Angular的Injectable符号,并且给这个服务类添加了@Injectable()装饰器。UserService类将会提供一个可注入的服务,并且它还可以拥有自己的待注入的依赖。目前它还没有依赖,但是很快就会有了。
@Injectable()装饰器会接收该服务的元数据对象,就像@Component()对组件类的作用一样。
如果你熟悉Java中的注解,那么这个@Injectable()装饰器可以被简单地理解为Java中的@Inject注解。
UserService类用于提供用户数据的查询,而且这个服务的调用方是不需要关心UserService类的数据来源的。UserService类可以从任何地方获取数据,如Web 服务、本地存储(LocalStorage)或一个模拟的数据源。
我们将要从组件中移除数据访问逻辑,并将数据访问逻辑移到UserService类中,这样组件只需要依赖UserService类来提供数据。这意味着,将来任何时候都可以改变目前的UserService类的实现方式,而不用改动任何组件,因为这些组件不需要了解该服务的内部实现。
我们将原来在组件中实现的“获取模拟的用户列表”功能迁移至UserService类中。具体修改如下。
1. 导入 User 和 USERS
在UserService类中导入User和USERS。
2. 添加一个 getUsers() 方法
在UserService类中添加一个getUsers()方法,让它返回模拟的用户列表。
在Angular把UserService服务注入UsersComponent组件之前,必须先把这个服务提供给依赖注入系统。可以通过注册提供商来做到这一点。提供商用来创建和交付服务,在这个例子中,它会对UserService类进行实例化,以提供该服务。
现在,需要确保UserService类已经作为该服务的提供商进行了注册。需要用一个注入器注册它。注入器就是一个对象,负责在需要时选取和注入该提供商。
在默认情况下,Angular CLI 的“ng generate service”命令会通过给@Injectable装饰器添加元数据的形式,为该服务把提供商注册到根注入器上。
如果你看看UserService前面的@Injectable()语句定义,就会发现providedIn元数据的值是'root'。
当你在顶层提供该服务时,Angular就会为UserService类创建一个单一的、共享的实例,并把它注入任何想要它的类上。在@Injectable元数据中注册该提供商,还能让Angular通过移除那些完全没有用过的服务来进行优化。
如果需要,也可以在不同的层次上注册提供商,比如,在UsersComponent组件中,在AppComponent中,或在AppModule中。比如,可以通过附加“-module=app”参数来告诉Angular CLI 要自动在模块级提供该服务。
现在,UserService类已经准备好注入UsersComponent组件中了。
打开UsersComponent组件的类文件users.component.ts,删除USERS的导入语句,因为以后不会再用它了,转而导入UserService类。
把users属性的定义改为一句简单的声明。
1. 注入 UserService 类
向构造函数中添加一个私有的类型为UserService的服务实例userService,代码如下。
这个参数同时做了两件事。
● 声明了一个私有userService属性。
● 把userService属性标记为一个UserService类的注入点。
当Angular创建UsersComponen类时,依赖注入系统就会把这个userService参数设置为UserService类的单例对象。
2. 添加 getUsers() 函数
创建一个getUsers()函数,以便从服务中获取这些用户数据。
3. 在 ngOnInit() 中调用 getUsers() 函数
你固然可以在构造函数中调用getUsers()函数,但那不是最佳实践。
让构造函数保持简单,只做初始化操作,比如,把构造函数的参数赋值给属性。构造函数不应该做任何事。它肯定不能调用某个函数来向远端服务(比如真实的数据服务)发起HTTP请求。
而是选择在ngOnInit()生命周期钩子中调用 getUsers()函数,之后交由Angular处理。它会在构造出UsersComponent的实例之后的某个合适的时机调用ngOnInit()函数。
4. 查看运行效果
执行“ng serve”命令以启动应用,在浏览器中访问http://localhost:4200/。该应用的运行效果应该跟之前是一样的。
Observable方式属于响应式编程(Reactive Programming),数据流是异步传输的。采用 Observable方式需要引入 RxJS 库(http://reactivex.io/rxjs/)。当然,Angular自带了RxJS库,我们开箱即用即可。
1. 在 UserService 类中实现 Observable
打开UserService类的文件user.service.ts,并从RxJS库中导入Observable和of符号。
把 getUsers()方法改成下面这样:
其中,of(USERS)会返回一个Observable<User[]>,它会发出单个值,这个值就是这些模拟用户所在的数组。
2. 在 UsersComponent 中订阅
UserService.getUsers()方法之前返回一个User[],现在它返回的是Observable<User[]>。所以,必须在UsersComponent中修改使用UserService.getUsers()方法的方式。
找到getUsers()方法,并且把它替换为如下代码:
Observable.subscribe()是关键的差异点。这样,只要数据源发生变化,this.users的值就能立即得到最新的变化,实现异步。
当然,目前还无法体会到Observable所带来的好处,因为现在的数据是模拟的、硬编在代码里的。在后续章节中,还会继续展示Observable所带来的响应式编程的好处。
在本节中,我们将:
● 添加一个MessagesComponent,它在屏幕的底部显示应用中的消息。
● 创建一个可注入的、全应用级别的MessageService,用于发送要显示的消息。
● 把MessageService注入UserService类中。
● 当UserService类成功获取了用户数据时显示一条消息。
1. 创建 MessagesComponent
使用Angular CLI创建MessagesComponent。
Angular CLI在src/app/messages中创建了组件文件,并且把MessagesComponent声明在AppModule 中。详细的控制台输出信息如下:
修改AppComponent的模板文件app.component.html来显示所生成的MessagesComponent。
这样,就可以在页面的底部看到来自MessagesComponent的默认内容。
2. 创建 MessageService
使用Angular CLI在src/app中创建MessageService。
MessageService创建完成之后,能够在控制台上看到详细的输出信息,如下:
打开MessageService的文件message.service.ts,并把它的内容修改成如下这样:
该服务对外暴露了它的 messages 缓存及两个方法。
● add()方法用于向缓存中添加一条消息。
● clear()方法用于清空缓存。
3. 把 MessageService 注入 UserService 中
我们将实现一个典型的“服务中的服务”场景:把MessageService注入UserService中,而 UserService又被注入UsersComponent 中。
重新打开 UserService 的文件user.service.ts,并且导入 MessageService。
修改构造函数constructor(),添加一个私有的messageService属性。Angular将会在创建 UserService时把MessageService的单例注入这个属性中。
4. 从 UserService 中发送一条消息
修改getUsers()方法,在获取到用户列表时发送一条消息。
5. 从 UserService 中显示消息
MessagesComponent可以显示所有消息,包括当UserService获取到用户列表时发送的那条消息。
打开MessagesComponent的文件messages.component.ts,并且导入MessageService。
修改MessagesComponent的构造函数,添加一个公有的messageService属性。Angular将会在创建MessagesComponent的实例时把MessageService的实例注入这个属性中。
这个messageService 属性必须是公有属性,因为你将会在模板中绑定到它。
提示:
Angular只会绑定到组件的公有属性。
6. 绑定到 MessageService
把Angular CLI生成的MessagesComponent的模板文件messages.component.html修改成如下这样:
这个模板直接绑定到了组件的messageService属性上。其中,
●*ngIf只有在有消息时才会显示消息区。
●*ngFor用来在一系列<div>标签中展示消息列表。
● Angular的事件绑定机制把按钮的click事件绑定到了MessageService.clear()上。
为了让消息变得好看,需要把样式代码添加到messages.component.css中,这些样式只会作用于MessagesComponent。
7. 运行查看效果
执行“ng serve”命令以启动应用。访问“http://localhost:4200”并滚动到底部,就会在消息区看到来自UserService的消息。单击“clear”按钮,消息区不见了。
效果如图4-13所示。
图4-13 运行效果