Angular 4 依赖注入教程之六 Injectable装饰器

前端之家收集整理的这篇文章主要介绍了Angular 4 依赖注入教程之六 Injectable装饰器前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

目录

阅读须知

本系列教程的开发环境及开发语言:

基础知识

装饰器是什么

  • 它是一个表达式

  • 该表达式被执行后,返回一个函数

  • 函数的入参分别为 targe、name 和 descriptor

  • 执行该函数后,可能返回 descriptor 对象,用于配置 target 对象 

装饰器的分类

  • 类装饰器 (Class decorators)

  • 属性装饰器 (Property decorators)

  • 方法装饰器 (Method decorators)

  • 参数装饰器 (Parameter decorators)

TypeScript 类装饰器

类装饰器声明:

  1. declare type ClassDecorator = <TFunction extends Function>(target: TFunction) =>
  2. TFunction | void

类装饰器顾名思义,就是用来装饰类的。它接收一个参数:

  • target: TFunction - 被装饰的类

看完第一眼后,是不是感觉都不好了。没事,我们马上来个例子:

  1. function Greeter(target: Function): void {
  2. target.prototype.greet = function (): void {
  3. console.log('Hello!');
  4. }
  5. }
  6.  
  7. @Greeter
  8. class Greeting {
  9. constructor() { // 内部实现 }
  10. }
  11.  
  12. let myGreeting = new Greeting();
  13. myGreeting.greet(); // console output: 'Hello!';

上面的例子中,我们定义了 Greeter 类装饰器,同时我们使用了 @Greeter 语法,来使用装饰器。

Injectable 类装饰器使用

  1. import { Injectable } from '@angular/core';
  2.  
  3. @Injectable()
  4. class HeroService {}

Injectable 装饰器

在介绍 Injectable 装饰器前,我们先来回顾一下 HeroComponent 组件:

  1. @Component({
  2. selector: 'app-hero',template: `
  3. <ul>
  4. <li *ngFor="let hero of heros">
  5. ID: {{hero.id}} - Name: {{hero.name}}
  6. </li>
  7. </ul>
  8. `
  9. })
  10. export class HeroComponent implements OnInit {
  11. heros: Array<{ id: number; name: string }>;
  12.  
  13. constructor(private heroService: HeroService,private loggerService: LoggerService) { }
  14.  
  15. ngOnInit() {
  16. this.loggerService.log('Fetching heros...');
  17. this.heros = this.heroService.getHeros();
  18. }
  19. }

HeroComponent 组件的 ngOnInit 生命周期钩子中,我们在获取英雄信息前输出相应的调试信息。其实为了避免在每个应用的组件中都添加 log 语句,我们可以把 log 语句放在 getHeros() 方法内。

更新前 HeroService 服务

  1. export class HeroService {
  2. heros: Array<{ id: number; name: string }> = [
  3. { 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' }
  4. ];
  5.  
  6. getHeros() {
  7. return this.heros;
  8. }
  9. }

更新后 HeroService 服务

  1. import { LoggerService } from './logger.service';
  2.  
  3. export class HeroService {
  4. constructor(private loggerService: LoggerService) { }
  5.  
  6. heros: Array<{ id: number; name: string }> = [
  7. { id: 11,name: 'Magneta' }
  8. ];
  9.  
  10. getHeros() {
  11. this.loggerService.log('Fetching heros...');
  12. return this.heros;
  13. }
  14. }

当以上代码运行后会抛出以下异常信息:

  1. Uncaught Error: Can't resolve all parameters for HeroService: (?).

上面异常信息说明无法解析 HeroService 的所有参数,而 HeroService 服务的构造函数如下:

  1. export class HeroService {
  2. constructor(private loggerService: LoggerService) { }
  3. }

该构造函数的输入参数是 loggerService 且它的类型是 LoggerService 。在继续深入研究之前,我们来看一下 HeroService 最终生成ES5 代码

  1. var HeroService = (function() {
  2. function HeroService(loggerService) {
  3. this.loggerService = loggerService;
  4. this.heros = [{...},...];
  5. }
  6. HeroService.prototype.getHeros = function() {
  7. this.loggerService.log('Fetching heros...');
  8. return this.heros;
  9. };
  10. return HeroService;
  11. }());

我们发现生成ES5 代码中,HeroService 构造函数中是没有包含任何类型信息的,因此 Angular Injector (注入器) 就无法正常工作了。那么要怎么保存 HeroService 类构造函数中参数的类型信息呢?相信你已经想到了答案 — 当然是使用 Injectable 装饰器咯。接下来我们更新一下 HeroService

  1. import { Injectable } from '@angular/core';
  2. import { LoggerService } from './logger.service';
  3.  
  4. @Injectable()
  5. export class HeroService {
  6. // ...
  7. }

更新完上面的代码,成功保存后,在 http://localhost:4200/ 页面,你将看到熟悉的 "身影":

  1. ID: 11 - Name: Mr. Nice
  2. ID: 12 - Name: Narco
  3. ID: 13 - Name: Bombasto
  4. ID: 14 - Name: Celeritas
  5. ID: 15 - Name: Magneta

现在我们再来看一下 HeroService生成ES5 代码

  1. var HeroService = (function() {
  2. function HeroService(loggerService) {
  3. this.loggerService = loggerService;
  4. this.heros = [{...},...];
  5. }
  6. HeroService.prototype.getHeros = function() {
  7. this.loggerService.log('Fetching heros...');
  8. return this.heros;
  9. };
  10. return HeroService;
  11. }());
  12. HeroService = __decorate([__webpack_require__.i(
  13. __WEBPACK_IMPORTED_MODULE_0__angular_core__["c"/* Injectable */
  14. ])(),__Metadata("design:paramtypes",...)],HeroService);

__decorate 函数

  1. var __decorate = (this && this.__decorate) || function(decorators,target,key,desc) {...};

__Metadata 函数

  1. var __Metadata = (this && this.__Metadata) || function(k,v) {
  2. if (typeof Reflect === "object" && typeof Reflect.Metadata === "function")
  3. return Reflect.Metadata(k,v);
  4. };

我们发现相比未使用 Injectable 装饰器,HeroService 服务生成ES5 代码多出了 HeroService = __decorate(...) 这些代码。简单起见,我稍微介绍一下,通过 Injectable 装饰器,在编译时会把 HeroService 服务构造函数中参数的类型信息,通过 Reflect API 保存在 window['__core-js_shared__'] 对象的内部属性中。当 Injector 创建 HeroService 对象时,会通过 Reflect API 去读取之前已保存的构造函数中参数的类型信息,进而正确的完成实例化操作。
有兴趣的读者,可以查看 Angular 4.x 修仙之路Decorator(装饰器) 章节的相关文章

我有话说

@Injectable() 是必须的么?

如果所创建的服务不依赖于其他对象,是可以不用使用 Injectable 类装饰器。但当该服务需要在构造函数中注入依赖对象,就需要使用 Injectable 装饰器。不过比较推荐的做法不管是否有依赖对象,在创建服务时都使用 Injectable 类装饰器。

猜你在找的Angularjs相关文章