浅谈angular依赖注入

一、什么是依赖注入?

首先,用最通俗的语言来说,某公司财务类要给员工发放工资,它需要人事类提供一个薪资标准,财务类想要完成工资发放,就必须实例化人事类,,也就是说财务类依赖于人事类;
官方回答: 依赖注入 Dependency Injection(简称DI)是用来创建对象及其依赖的其它对象的一种方式。 当依赖注入系统创建某个对象实例时,会负责提供该对象所依赖的对象(称为该对象的依赖)。
以下面分页示例讲解:

page基类
page数据类
文章列表
员工列表
视频列表

有一个page数据类,依赖于page基类,下面有三个组件,分别是文章列表、员工列表、视频列表组件,因为三个都是列表,所以肯定用到分页,三个组件从数据库中获取当前数据的总条数,然后计算出分页数,然后重新设置分页信息,假如文章列表让其一页显示20条数据,员工列表一页显示30条数据,视频列表一页显示15条数据,这时我们就应该给每一个列表都提供一个数据提供商,也就是说三个列表都是基于page数据类,但是由于每个组件设置分页数都不一样,所以必须把page数据类作为一个单独的数据提供商提供给每一个列表,他们的作用域是互不相干的;

二、angular是如何实现依赖注入的?

1. 注入器

每一个组件都有一个注入器实例,负责注入组件依赖的对象,注入器是angular提供的一个服务类,一般注入器会通过组件中的构造函数将组件中所需的对象注入进组件。(injector.get()和构造函数中注入对比见下文)

constructor(private translate: TranslateService,
            private activatedRoute : ActivatedRoute,
            private router: Router,
            private http: Http) {}

注意

1. 在angular中存在一个与组件树类似的注入器树; 
2. 注入器冒泡——在自己注入器中没有找到服务实例,就回去父组件注入器或模板注入器中查找,直到跟注入器都没有找到,angular就会报错	

2. 提供器(provider)

用于配置注入器,注入器通过它来创建被依赖对象的实例,Provider把标识映射到工厂方法中,被依赖的对象就是通过该方法创建的。

2.1 基本方式
providers: [LoginUserInfoService]

基本方法中,可以直接在providers中写入需要的服务,相当于使用new实例化一个对象,这样的话就可以在其他的服务或组件中使用该服务;

2.2 useClass
providers: 
[{ provide: UserInfoService, useClass:LoginUserInfoService }]

使用useClass可以根据需要,选择注入非默认的服务。provide指定了提供器的token, useClass表示实例化属性为new。

2.3 useFactory
providers: [
    {
      provide: APP_INITIALIZER,
      useFactory: appInitializerFactory,		//appInitializerFactory为函数(自定义),可以根据不同的条件,使用不同的服务
      deps: [TranslateService, Injector],
      multi: true
    }

使用useFactory可以根据不同的条件,使用不同的服务。在其他地方调用APP_INITIALIZER时就会使用不同的服务,展示不同的结果。其中,depsFactory中需要注入的内容。

三、injector.get()和在构造函数中注入有什么区别?

export function appInitializerFactory(translate: TranslateService, injector: Injector) {
  return () => new Promise<any>((resolve: any) => {
    const locationInitialized = injector.get(LOCATION_INITIALIZED, Promise.resolve(null));
    locationInitialized.then(() => {
      const lang = translate.getBrowserLang() === 'zh' ? 'zh' : 'en';
      translate.setDefaultLang(lang);
      translate.use(lang).subscribe(() => {
        console.info(`Successfully initialized '${lang}' language.'`);
      }, err => {
        console.error(`Problem with '${lang}' language initialization.'`);
      }, () => {
        resolve(null);
      });
    });
  });
}

使用构造函数注入的时候明确的声明了那些服务是需要注入的,这样通过依赖检查可以直接查找该组建所需的依赖项。

使用Injector注入是为了解决一些动态注入的需求,此时Angular不知道你想要具体注入哪些依赖项,所以其实是注入了所有服务的DI容器(注意不是所有服务),

首选应该考虑使用构造函数注入,当有动态注入需求的时候再考虑使用Injector

四、单例服务

在这里插入图片描述
单例服务是指在应用中只存在一个实例的服务;
下面看一个单例服务示例:
新建项目, 并创建两个组件(parentchild),再创建一个logger服务;
app.component.htmlapp.component.ts改成如下所示:

<h1>{{ title }}</h1>
<ul>
  <li *ngFor="let log of loggerService.getLogges()">{{ log }}</li>
</ul>
<button (click)="addLog()">AddLogAppModule</button>
<hr />
<app-parent></app-parent>
import { Component } from '@angular/core';
import {LoggerService} from './logger.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title = 'test-teach';
  constructor(private loggerService: LoggerService) {}
  addLog() {
    this.loggerService.addLog('add log from appComponent successfully');
  }
}

logger.service.ts修改为如下:

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

@Injectable({
  providedIn: 'root'
})
export class LoggerService {
  private logges: string[] = [];
  constructor() { }

  addLog(log: string) {
    this.logges.push(log);
  }
  getLogges() {
    return this.logges;
  }
}

同时,将parent.component.htmlparent.component.ts修改为如下:

<ul>
  <li *ngFor="let log of loggerService.getLogges()">{{ log }}</li>
</ul>
<button (click)="addLog()">AddLogParentt</button>
<app-child></app-child>
import { Component, OnInit } from '@angular/core';
import { LoggerService } from '../logger.service';
@Component({
  selector: 'app-parent',
  templateUrl: './parent.component.html',
  styleUrls: ['./parent.component.scss']
})
export class ParentComponent implements OnInit {
  constructor(private loggerService: LoggerService) {}
  ngOnInit() {}
  addLog() {
    this.loggerService.addLog('add log from parent component');
  }
}

child.component.htmlchild.component.ts修改为如下:

<p>child works!</p>
<ul>
  <li *ngFor="let log of loggerService.getLogges()">{{ log }}</li>
</ul>
<button (click)="addLog()">AddLog</button>
import { Component, OnInit } from '@angular/core';
import { LoggerService } from '../logger.service';
@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  styleUrls: ['./child.component.scss']
})
export class ChildComponent implements OnInit {
  constructor(private loggerService: LoggerService) {}
  ngOnInit() {}
  addLog() {
    this.loggerService.addLog('add log from child component');
  }
}

运行效果如下:
在这里插入图片描述
可以看出,无论我们点击那一个btn,都会同步消息,因此,appModuleparentchild 用的都是一个服务实例;

  • 组件层级关系是 appComponent > parent > child
  • 程序运行时,发现child中需要一个loggerService服务,就在child对应的注入器中找,没找到就去父注入器中查找,若无找到再次冒泡到父注入器中,也即是前面提到的注入器冒泡

那如何实现非单例服务了??
非单例服务也即是阻止注入器冒泡,因此只需要在需要的地方提供一个服务提供商,如下在parent中提供服务提供商providers: [LoggerService]
在这里插入图片描述
可以看出,appCompoentparent以及child之间的消息是不同步的;

常见干预注入器冒泡方法:

  1. @Self():只允许在自己的注入器中查找服务
  2. @Optional():使服务可选,也就是找到就用,没找到也不报错(没有该装饰器的情况下,没有找到是会报错的,就像上面的例子那样)
  3. @SkipSelf():跳过自己的注入器,向父级注入器中查找
  4. @Host():在其宿主组件注入器中查找服务实例
constructor(@Self() private translate: TranslateService) {}

具体的用法可以自己增加尝试,这里不再做说明;

五、摇树优化

摇树优化是指编译器选项,是把为用到的代码从最终生成的包中移除,前提是服务商是可摇树优化的 ,从而减小打包体积;

使用 providedIn: 'root’
@Injectable()providedIn元数据属性有两种参数,一种是 ‘root’注入器级别,也即是AppModule注入器,可以在整个工程中使用,不用再次导入;另一种是 ‘ngModule’级别,假如你只想让其在LoginModule中使用该服务,只需如此定义即可: providedIn: 'LoginModule'

懒加载 providedIn: ‘root’ 解决方案
如果我们在懒加载中使用providedIn: 'root' ,虽然 ‘root’是AppModule,但是如果该组件只是在懒性组件或服务中注入,那么就只会在延迟加载的bundle中;如果又额外将服务注入到其他正常加载的模块中,那么该服务会自动绑定到main的bundle中;

简单来讲:
1、如果服务仅被注入到懒加载模块,它将捆绑在懒加载包中
2、如果服务又被注入到正常模块中,它将捆绑在主包中

但是他也会导致一个新问题出现:—(循环依赖)
我们可以通过创建一个 LazyServiceModule 来避免这个问题,它将是 LazyModule 的一个子模块,并将被用作我们想要提供的所有懒加载服务的“锚”,这样写的优点:

1. 它防止我们将懒加载的服务注入应用程序的正常加载模块
2. 只有当服务被真正注入其他惰性组件时,它才会打包到服务中

那怎么样的提供商是可以要输优化的呢?
只要在服务本身的@Injectable()装饰器中指定,而不是在依赖该服务的@NgModule()或组件的provides数组中指定,就可以制作一个可摇树优化的提供商;

创建可摇树优化服务方法:

@Injectable({
  providedIn: 'root',	//root级别服务,root注入器就是 AppModule 注入器
})
export class Service {
}

如下方法不能摇树优化:

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

@Injectable()
export class Service {
  doSomething(): void {
  }
}

@NgModule({
  providers: [Service],
})
export class ServiceModule {
}

在我们项目中多使用的是这种提供商方式,虽然最后实现相同,但是假如此服务定义在LoginModule中,想在LogModule中使用该服务,就只能将LoginMoudle引入到LogMoudle中,而使用providedIn: 'root'就不同考虑这些,并且打包阶段若未使用到此服务,angular会自动摇掉该服务,减小打包体积;

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了小程序应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值