freeCodeCamp/guide/chinese/angular/services-and-injectors/index.md

9.3 KiB
Raw Blame History

title localeTitle
Services and Injectors 服务和注射器

服务和注射器

动机

组件负责呈现到模板中的数据。拥有外部_服务_可以简化这一责任。另外封装外部更容易维护。

将过多的职责委托给单个组件会使组件类复杂化。如果这些责任适用于多个组件怎么办?复制和粘贴这种逻辑是非常糟糕的做法。未来对逻辑的任何更改都将难以实现和测试。

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注入器处注册的服务。注射器永远不会将他们的孩子推迟到服务实例。

依赖注入

有多种方法可以使用应用程序的注入器注册服务。

@InjectableprovidedIn: '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预配置的内容。

结论

服务和依赖注入非常有用。它们允许开发人员封装通用逻辑并注入多个不同的组件。仅此一项对于任何未来的维护都是非常方便的。

注射器作为中间人。它们在实例化组件和注册服务库之间进行调解。注入者为其分支子项提供这些可实例化的服务。

有关服务和依赖注入的更多信息,请参阅下面的几个链接。

来源

  1. Typicode JSONPlaceholder https //jsonplaceholder.typicode.com2018年 5月29日访问。

资源