1

一直想写关于 Angular 1.x 与 Angular 2.x (Angular 4.x 已发布) 区别的文章,方便 Angular 1.x 的用户快速的过渡到 Angular 2.x。在浏览文章的时候,发现 Todd Motto 大神,已经写了相关的系列文章。英文好的同学,建议直接阅读 Creating an Angular 2 Injectable Service 原文哈,因为我并不打算完整地翻译,另外需要注意的是原文的示例是使用ES 2015,本文 Angular 2 示例是使用 TypeScript哈。废话不多说,接下来我们开始进入正题。

目录

  • Angular 1.x

    • Service definition

    • Service DI

  • Angular 2

    • Service setup

    • @Injectable() and DI

Angular 1.x

在 Angular 1.x 中,我们通过使用 .service() API 来创建服务。

Service Definition

我们使用 ES 2015 中的 Class 来定义服务,getTodos() 方法只是简单地返回 Todos 列表。在后续的部分,我们会引入 HTTP 模块。

class TodoService {
  constructor() {}
  getTodos() {
    return [{
      "id": 1,
      "label": "delectus aut autem",
      "completed": false
    },{
      "id": 2,
      "label": "quis ut nam facilis et officia qui",
      "completed": false
    },{
      "id": 3,
      "label": "fugiat veniam minus",
      "completed": false
    },{
      "id": 4,
      "label": "et porro tempora",
      "completed": true
    },{
      "id": 5,
      "label": "laboriosam mollitia et enim quasi adipisci quia provident illum",
      "completed": false
    }];
  }
}

angular
  .module('app') // 获取已注册的app模块
  .service('TodoService', TodoService); // 通过service API注册TodoService服务

接下来我们来注入 $http 服务,在 Angular 1.x 中声明依赖项的方式有3种,分为如下:

// 方式一: 使用 $inject annotation 方式 - 严格DI
var fn = function (a, b) {};
fn.$inject = ['a', 'b'];

// 方式二: 使用 array-style annotations 方式 - 严格DI
var fn = ['a', 'b', function (a, b) {}];

// 方式三: 使用隐式声明方式 
var fn = function (a, b) {}; // 不推荐

我们使用第一种方式来声明依赖,具体代码如下:

class TodoService {
  constructor($http) {
    this.$http = $http;
  }
  getTodos() {
    return [{..},{..},{..},{..},{..}];
  }
}

TodoService.$inject = ['$http'];

angular
  .module('app')
  .service('TodoService', TodoService);

Service DI

const todo = {
  template: `
    <div>
      My Todo List:
      <ul>
        <li ng-repeat="todo in $ctrl.todos">
          {{ todo.label }}
        </li>
      </ul>
    </div>
  `,
  controller(TodoService) {
    $onInit() {
      this.todos = TodoService.getTodos();
    }
  }
};

上面代码中,我们在 controller 中使用 $onInit 生命周期钩子,用于在组件初始化的时候,设置组件的初始数据。示例中的 getTodos() 是同步操作,如果使用 $http 服务从远程服务器获取数据的话,返回的是一个 Promise 对象,我们就需要在 then() 方法中进行 todos 属性的赋值操作。

(备注:有兴趣了解 Angular 1.x DI 内容的话,可以参考我之前的文章 - Angular 2 DI - IoC & DI - 1 )

Angular 2

Service setup

首先定义 TodoService 服务类 (使用TypeScript):

export class TodoService {
    getTodos(): Array<{ id: number, label: string, completed: boolean }> {
        return [{
            "id": 1,
            "label": "delectus aut autem",
            "completed": false
        }, {
            "id": 2,
            "label": "quis ut nam facilis et officia qui",
            "completed": false
        }, {
            "id": 3,
            "label": "fugiat veniam minus",
            "completed": false
        }, {
            "id": 4,
            "label": "et porro tempora",
            "completed": true
        }, {
            "id": 5,
            "label": "laboriosam mollitia et enim quasi adipisci quia provident illum",
            "completed": false
        }];
    }
}

@Injectable() and DI

接下来我们使用 @Injectable 类装饰器,来装饰 TodoSevice 类:

import {Injectable} from '@angular/core';

@Injectable()
export default class TodoService {
  getTodos(): Array<{ id: number, label: string, completed: boolean }> {
    return [{..},{..},{..},{..},{..}];
  }
}

然后,我们通过 @Component() 装饰器创建 todo 组件,为了跟 Angular 1.x 的示例一样,在组件初始化的时候,设置组件的初始数据,我们需要在组件中引入 OnInit 接口,并在组件类中实现该接口。具体示例如下:

import { Component, OnInit } from '@angular/core';
import { TodoService } from './services/todo.service';

@Component({
    selector: 'todo',
    template: `
    <div>
      My Todo List:
      <ul>
        <li *ngFor="let todo of todos">
          {{ todo.label }}
        </li>
      </ul>
    </div>
  `
})
export default class TodoComponent implements OnInit {
    public todos: Array<{ id: number, label: string, completed: boolean }>;

    constructor(public todoService: TodoService) { } // 使用构造方式,注入TodoService
    ngOnInit() {
        this.todos = this.todoService.getTodos(); // 获取待办事项列表
    }
}

app.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'exe-app',
  template: `
    <todo></todo>
  `
})
export class AppComponent { }

app.module.ts

import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import TodoComponent from './todo.component';
import { AppComponent }  from './app.component';

import { TodoService } from './services/todo.service';

@NgModule({
  imports:      [ BrowserModule ],
  declarations: [ TodoComponent, AppComponent ],
  providers: [TodoService]
  bootstrap:    [ AppComponent ]
})
export class AppModule { }

以上成功运行后,浏览器中的显示结果如下:
图片描述

我有话说

1.ngOnit 与 constructor 的区别和应用场景

在 Angular 2 中 constructor 一般用于依赖注入或执行简单的数据初始化操作,ngOnInit 钩子主要用于执行组件的其它初始化操作或获取组件输入的属性值。

详细内容请参考 - Angular 2 constructor & ngOnInit

2.@Injectable装饰器的作用

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

详细内容请参考 - Angular 2 Inject


阿宝哥
15.8k 声望10.2k 粉丝

聚焦全栈,专注分享 Angular、TypeScript、Node.js/Java 、Spring 技术栈等全栈干货