【Angular 】Angular 中的依赖注入

Angular 理解依赖注入(DI)

一、依赖注入(DI)核心概念

  1. 定义与地位:DI 是 Angular 核心概念之一,内置于框架中,支持带 Angular 装饰器的类(组件、指令、管道、可注入对象)配置所需依赖。
  2. 核心角色:DI 系统包含两个关键角色------依赖消费者(需使用依赖的类)和依赖提供者(提供依赖的来源)。
  3. 注入器(Injector)作用:作为连接消费者与提供者的抽象层,当请求依赖时,先检查自身注册表是否有可用实例,无则创建新实例并存储。Angular 在应用启动时自动创建应用级"根注入器",多数场景下无需手动创建注入器。
  4. 依赖类型:除类之外,还支持函数、对象、原始类型(字符串、布尔值等)作为依赖,具体可参考"依赖项提供程序"相关内容。

二、依赖项提供方式

依赖项可在多个层级提供,不同方式对应不同使用范围和特性,具体如下:

提供方式 配置方法 适用场景 关键特性
应用根级别(providedIn,首选) @Injectable 装饰器中设置 providedIn: 'root' 需在整个应用中共享依赖(如全局服务) 1. 生成单一共享实例,注入到所有请求该依赖的类 2. 支持代码优化(树摇,移除未使用服务)
组件级别 @Component 装饰器的 providers 字段中配置(如 providers: [HeroService] 依赖仅需在特定组件及其实例、模板内组件/指令中使用 1. 每个组件实例对应一个新的依赖实例 2. 即使依赖未使用,也会被包含在应用中,无法树摇
应用根级别(ApplicationConfig 1. 定义 ApplicationConfig 对象,在 providers 中配置依赖(如 providers: [{ provide: HeroService }]) 2. 将配置传入 bootstrapApplication 函数(在 main.ts 中) 需在应用全局配置依赖,且不适合用 providedIn 的场景 即使依赖未使用,也会被包含在应用中,无法树摇
基于 NgModule 的应用 @NgModule 装饰器的 providers 字段中配置 基于 NgModule 架构的应用,需在模块范围内共享依赖 1. 依赖对模块的所有声明项及共享同一 ModuleInjector 的其他模块可用 2. 即使依赖未使用,也会被包含在应用中,无法树摇 3. 特殊场景需参考"分层注入器"文档

基础配置前提

无论哪种提供方式,首先需为类添加 @Injectable 装饰器,表明该类可被注入,基础代码如下:

typescript 复制代码
@Injectable()
class HeroService {}

三、依赖注入/使用方法

  1. 核心工具 :使用 Angular 的 inject 函数获取依赖。
  2. 使用场景 :可在注入上下文中使用,常见场景包括组件、指令、服务、管道的类属性初始化器或类构造函数。
  3. 代码示例
typescript 复制代码
import { inject, Component } from 'angular/core';

@Component({/* 组件配置 */})
export class UserProfile {
  // 在属性初始化器中注入依赖
  private userClient = inject(UserClient);
  
  constructor() {
    // 在构造函数中注入依赖
    const logger = inject(Logger);
  }
}
  1. 注入流程
    • 当 Angular 发现组件依赖某服务时,先检查注入器是否有该服务的现有实例;
    • 若无实例,注入器通过已注册的提供者创建新实例,并添加到注册表,再返回给 Angular;
    • 所有请求的服务解析完成后,Angular 以这些服务为参数调用组件构造函数。

Angular 创建可注入服务

一、核心概念与原则

  1. 服务定义:服务是涵盖应用所需值、函数或特性的广泛类别,通常是具有明确、单一用途的类;组件是可使用依赖注入(DI)的类之一。
  2. Angular 设计原则:区分组件与服务以提升模块性和可重用性,将组件视图相关功能与其他处理分离,使组件类精简高效。
  3. 组件与服务职责划分:组件仅负责支持用户体验,提供数据绑定所需的属性和方法,协调视图与应用逻辑;服务承担组件委托的任务,如从服务器获取数据、验证用户输入、控制台日志记录等,且服务可通过配置同类型不同提供程序增强应用适应性。
  4. Angular 支持方式:不强制上述原则,但通过 DI 机制,方便开发者将应用逻辑拆分到服务中,并让组件可使用这些服务。

二、服务示例

  1. 日志服务(Logger) :向浏览器控制台输出日志,包含 log(普通日志)、error(错误日志)、warn(警告日志)三种方法,代码如下:
typescript 复制代码
export class Logger {
  log(msg: unknown) { console.log(msg); }
  error(msg: unknown) { console.error(msg); }
  warn(msg: unknown) { console.warn(msg); }
}
  1. 英雄服务(HeroService) :依赖 Logger 服务记录日志,使用 BackendService 获取英雄数据,且 BackendService 可能依赖 HttpClient 服务从服务器异步获取英雄数据,代码如下:
typescript 复制代码
import { inject } from "@angular/core";
export class HeroService {
  private heroes: Hero[] = [];
  private backend = inject(BackendService);
  private logger = inject(Logger);
  async getHeroes() {
    // 从后端获取英雄数据
    this.heroes = await this.backend.getAll(Hero);
    // 记录获取到的英雄数量日志
    this.logger.log(`Fetched ${this.heroes.length} heroes.`);
    return this.heroes;
  }
}

三、使用 CLI 创建可注入服务

  1. 生成服务命令 :在 src/app/heroes 文件夹生成 HeroService 类,运行 Angular CLI 命令 ng generate service heroes/hero
  2. 默认生成的服务代码 :包含 @Injectable() 装饰器,指定该类可在 DI 系统中使用,providedIn: 'root' 表示服务在整个应用中可提供,代码如下:
typescript 复制代码
import { Injectable } from '@angular/core';
@Injectable({
  providedIn: 'root',
})
export class HeroService {}
  1. 完善服务功能 :添加 getHeroes() 方法,从 mock-heroes.ts 中获取英雄模拟数据,代码如下:
typescript 复制代码
import { Injectable } from '@angular/core';
import { HEROES } from './mock-heroes';
@Injectable({
  // 声明该服务由根应用注入器创建
  providedIn: 'root',
})
export class HeroService {
  getHeroes() {
    return HEROES;
  }
}
  1. 开发建议:为保证代码清晰性和可维护性,组件与服务应分别定义在不同文件中。

四、服务注入方式

(一)注入到组件

  1. 使用 inject 函数 :在组件中声明表示依赖项的类字段,通过 inject 函数初始化,示例(HeroListComponent 中注入 HeroService):
typescript 复制代码
import { inject } from "@angular/core";
export class HeroListComponent {
  private heroService = inject(HeroService);
}
  1. 使用组件构造函数:在组件构造函数中声明依赖项,示例:
typescript 复制代码
constructor(private heroService: HeroService)
  1. 注入限制inject 方法可在类和函数中使用,构造函数注入仅能在类构造函数中使用;两种方式均需在有效的注入上下文(通常是组件构造或初始化过程)中进行。

(二)注入到其他服务

  1. 注入模式 :与注入到组件的模式一致,在依赖服务中通过 inject 函数获取所需服务。
  2. 示例(HeroService 中注入 Logger 服务)
typescript 复制代码
import { inject, Injectable } from '@angular/core';
import { HEROES } from './mock-heroes';
import { Logger } from '../logger.service';
@Injectable({
  providedIn: 'root',
})
export class HeroService {
  private logger = inject(Logger);
  getHeroes() {
    // 获取英雄数据时记录日志
    this.logger.log('Getting heroes.');
    return HEROES;
  }
}

Angular 定义依赖项提供程序

一、核心概述

  1. 依赖项扩展类型 :除类实例外,booleanstringDate及对象等也可作为依赖项,Angular 提供灵活 API 支持这些值在 DI 中使用。
  2. 提供程序配置基础 :类提供程序语法是简写形式,实际会扩展为符合Provider接口的配置对象,核心包含provide(依赖项令牌,作为消费依赖值的键)和提供程序定义对象(告知注入器如何创建依赖值)。

二、提供程序令牌(Provider Token)

  1. 默认行为 :若将服务类指定为提供程序令牌,注入器默认用new运算符实例化该类,示例:

    typescript 复制代码
    providers: [Logger] // 简写形式
  2. 扩展配置形式 :上述简写会展开为完整配置对象,可通过该形式关联令牌与不同类或值,示例:

    typescript 复制代码
    [{ provide: Logger, useClass: Logger }] // 完整配置

三、五种核心提供程序类型

1. 类提供程序(useClass)

  • 作用:创建并返回指定类的新实例,可替换通用/默认类的实现(如不同策略、扩展默认类、测试中模拟真实类行为)。

  • 基础示例 :请求Logger依赖时实例化BetterLogger

    typescript 复制代码
    [{ provide: Logger, useClass: BetterLogger }]
  • 带依赖的类提供 :若替换类(如EvenBetterLogger)自身有依赖(如UserService),需在父模块/组件的providers中同时配置依赖,示例:

    typescript 复制代码
    [UserService, { provide: Logger, useClass: EvenBetterLogger }]

    其中EvenBetterLogger通过注入UserService实现用户名称日志输出:

    typescript 复制代码
    @Injectable()
    export class EvenBetterLogger extends Logger {
      private userService = inject(UserService);
      override log(message: string) {
        const name = this.userService.user.name;
        super.log(`Message to ${name}: ${message}`);
      }
    }

2. 别名提供程序(useExisting)

  • 作用:将一个令牌映射为另一个令牌,使第一个令牌成为第二个令牌关联服务的别名,实现通过两种方式访问同一服务实例。

  • 示例 :请求OldLoggerNewLogger时,均注入NewLogger的单例实例:

    typescript 复制代码
    [NewLogger, { provide: OldLogger, useExisting: NewLogger }]
  • 注意事项 :不可用useClass实现别名(会创建两个不同实例)。

3. 工厂提供程序(useFactory)

  • 作用:通过调用工厂函数创建依赖对象,支持基于 DI 及应用中其他信息动态生成值。
  • 使用场景 :如根据用户授权状态控制HeroService是否返回秘密英雄数据。
  • 实现步骤
    1. 定义HeroService,接收isAuthorized标志控制秘密英雄显示:

      typescript 复制代码
      class HeroService {
        constructor(private logger: Logger, private isAuthorized: boolean) { }
        getHeroes() {
          const auth = this.isAuthorized ? 'authorized' : 'unauthorized';
          this.logger.log(`Getting heroes for ${auth} user.`);
          return HEROES.filter(hero => this.isAuthorized || !hero.isSecret);
        }
      }
    2. 创建工厂函数,结合UserService获取授权状态:

      typescript 复制代码
      const heroServiceFactory = (logger: Logger, userService: UserService) => 
        new HeroService(logger, userService.user.isAuthorized);
    3. 配置工厂提供程序,指定依赖(deps数组按顺序注入工厂函数参数):

      typescript 复制代码
      export const heroServiceProvider = {
        provide: HeroService,
        useFactory: heroServiceFactory,
        deps: [Logger, UserService]
      };

4. 值提供程序(useValue)

  • 作用:将静态值与 DI 令牌关联。
  • 适用场景:提供运行时配置常量(如网站基础地址、功能标志),或单元测试中用模拟数据替代生产数据服务。

5. InjectionToken 提供程序

  • 作用:为非类依赖项(如对象、基本类型)提供令牌,解决接口无法作为 DI 令牌的问题(TypeScript 接口无运行时表示)。
  • 使用步骤
    1. 定义InjectionToken及对应接口:

      typescript 复制代码
      import { InjectionToken } from '@angular/core';
      export interface AppConfig { title: string; }
      export const APP_CONFIG = new InjectionToken<AppConfig>('app.config description');
    2. 注册值与令牌关联:

      typescript 复制代码
      const MY_APP_CONFIG_VARIABLE: AppConfig = { title: 'Hello' };
      providers: [{ provide: APP_CONFIG, useValue: MY_APP_CONFIG_VARIABLE }]
    3. 注入使用:

      typescript 复制代码
      export class AppComponent {
        constructor() {
          const config = inject(APP_CONFIG);
          this.title = config.title;
        }
      }

四、接口与 DI 的关系

  1. 局限性:TypeScript 接口仅为设计时工具,转译为 JavaScript 后消失,无运行时表示,无法作为 DI 令牌,也不能直接注入。
  2. 错误示例
    • 不可用接口作为提供程序令牌:[{ provide: AppConfig, useValue: MY_APP_CONFIG_VARIABLE }](错误)
    • 不可用接口作为注入类型:private config = inject(AppConfig)(错误)

Angular 注入上下文(Injection Context)

一、核心定义

Angular 的依赖注入(DI)系统依赖运行时的注入上下文 ,该上下文需确保当前注入器(injector)可被访问,只有在注入上下文中执行代码,注入器才能正常工作;在注入上下文中,可使用 inject() 函数注入实例。

二、注入上下文的可用场景

  1. 由 DI 系统实例化的类(如带 @Injectable@Component 装饰器的类)的构造函数(constructor)执行期间。
  2. 上述类的字段初始化器中。
  3. Provider@InjectableuseFactory 所指定的工厂函数中。
  4. InjectionToken 所指定的 factory 函数中。
  5. 运行在注入上下文中的调用栈帧内(如路由器守卫等特定 API 场景)。

三、关键使用场景与示例

(一)类构造函数(Class constructors)

DI 系统实例化类时,会自动在注入上下文中执行类的构造函数,可在构造函数或字段初始化时用 inject() 注入依赖。

typescript 复制代码
class MyComponent  {  
  private service1: Service1;  
  private service2: Service2 = inject(Service2); // 字段初始化时注入(在上下文中)  
  constructor() {    
    this.service1 = inject(Service1) // 构造函数中注入(在上下文中)  
  }
}

(二)上下文内调用栈帧(Stack frame in context)

部分 Angular API 设计为在注入上下文中运行,如路由器守卫(CanActivateFn),可在守卫函数内用 inject() 访问服务。

typescript 复制代码
const canActivateTeam: CanActivateFn =    
  (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {      
    return inject(PermissionsService).canActivate(inject(UserToken), route.params.id);    
  };

(三)在注入上下文中运行函数(Run within an injection context)

若需在非注入上下文执行函数,可通过 runInInjectionContext() 手动指定注入器(如 EnvironmentInjector),使函数在注入上下文中运行。

typescript 复制代码
@Injectable({  providedIn: 'root',})
export class HeroService {  
  private environmentInjector = inject(EnvironmentInjector);  
  someMethod() {    
    runInInjectionContext(this.environmentInjector, () => {      
      inject(SomeService); // 在指定注入上下文中注入服务  
    });  
  }
}

注:inject() 仅在注入器能解析所需令牌时返回实例。

(四)断言注入上下文(Asserts the context)

使用 assertInInjectionContext() 辅助函数,可断言当前上下文为注入上下文,若不是则抛出清晰错误(需传入调用函数引用,使错误信息指向正确 API 入口)。

  1. 定义断言函数
typescript 复制代码
import { ElementRef, assertInInjectionContext, inject } from '@angular/core';
export function injectNativeElement<T extends Element>(): T {    
  assertInInjectionContext(injectNativeElement);    
  return inject(ElementRef).nativeElement;
}
  1. 调用限制:仅能在注入上下文(构造函数、字段初始化器、提供者工厂、runInInjectionContext() 执行的代码)中调用,非上下文调用会失败。
typescript 复制代码
import { Component, inject } from '@angular/core';
import { injectNativeElement } from './dom-helpers';
@Component({ /* ... */ })
export class PreviewCard {  
  readonly hostEl = injectNativeElement<HTMLElement>(); // 成功:字段初始化在注入上下文中  
  onAction() {    
    const anotherRef = injectNativeElement<HTMLElement>(); // 失败:不在注入上下文中  
  }
}

四、错误情况

在非注入上下文调用 inject()assertInInjectionContext(),会抛出 error NG0203

优化注入令牌

一、网页基本信息

  • 网页类型:普通网站(Angular官方文档页面)
  • 核心主题:介绍如何通过轻量级注入令牌(lightweight injection tokens)优化Angular客户端应用体积,尤其针对库开发者提供依赖注入设计方案
  • 所属文档体系:隶属于Angular官方文档的"深度指南(In-depth Guides)- 依赖注入(Dependency Injection)"板块,是该板块下的重要内容之一

二、核心背景与目标

  1. 问题根源:Angular中注入令牌的存储方式可能导致未使用的组件/服务无法被"摇树优化"(tree-shaking),即便应用未实际使用,其代码仍会保留在最终bundle中,增加应用体积
  2. 核心目标:帮助库开发者设计依赖结构,确保客户端应用仅引入实际使用的库功能代码,未使用代码可被有效移除,优化应用bundle体积
  3. 责任主体:由于应用开发者无法察觉或解决库中的摇树问题,因此优化注入令牌以支持摇树的责任落在库开发者身上

三、关键概念与问题解析

(一)令牌保留的场景与原因

  1. 令牌保留的核心场景:当组件被用作注入令牌且处于"值位置"(value position)时,编译器会在运行时保留该令牌,导致组件无法被摇树
  2. 具体案例说明 :以库中的lib-cardlib-header组件为例
    • 常规实现中,LibCardComponent通过@ContentChild(LibHeaderComponent)获取lib-header组件,此时LibHeaderComponent存在两个引用
      • 类型位置(type position):header: LibHeaderComponent,TypeScript转译后会被编译器清除,不影响摇树
      • 值位置(value position):@ContentChild(LibHeaderComponent),编译器需在运行时保留,导致即便应用不使用lib-header,其代码仍会保留在bundle中
  3. 其他令牌保留场景 :除内容查询(content query)的"值位置"外,构造函数注入中用作类型说明符的令牌,也会被转换为"值位置"引用(如constructor(@Optional() other: OtherComponent)会转为constructor(@Optional() @Inject(OtherComponent) other)),导致组件无法摇树

(二)轻量级注入令牌的适用场景

当组件被用作注入令牌时,需使用轻量级注入令牌模式,具体包括两种情况:

  • 令牌用于内容查询(content query)的"值位置"
  • 令牌用作构造函数注入的类型说明符

四、轻量级注入令牌的实现方案

(一)核心设计思路

通过"抽象类作为注入令牌+后续提供具体实现"的方式,让体积小、无实际逻辑的抽象类被保留,而有具体实现的组件可被摇树(未使用时移除)

(二)分步实现步骤

  1. 定义轻量级注入令牌 :创建一个无具体实现的抽象类(如LibHeaderToken),作为注入令牌
  2. 实现组件并关联令牌 :在具体组件(如LibHeaderComponent)中,让组件继承抽象令牌类,并在组件的providers配置中,将抽象令牌与具体组件关联({provide: LibHeaderToken, useExisting: LibHeaderComponent}
  3. 注入令牌而非具体组件 :在依赖该组件的父组件(如LibCardComponent)中,通过@ContentChild()/@ContentChildren()注入抽象令牌(如@ContentChild(LibHeaderToken) header: LibHeaderToken|null = null),不再直接引用具体组件
  4. 示例代码框架
typescript 复制代码
// 1. 定义抽象令牌
abstract class LibHeaderToken {}

// 2. 具体组件继承令牌并关联
@Component({
  selector: 'lib-header',
  providers: [{provide: LibHeaderToken, useExisting: LibHeaderComponent}]
  // 其他配置...
})
class LibHeaderComponent extends LibHeaderToken {}

// 3. 父组件注入令牌
@Component({
  selector: 'lib-card'
  // 其他配置...
})
class LibCardComponent {
  @ContentChild(LibHeaderToken) header: LibHeaderToken|null = null;
}

(三)扩展:令牌用于API定义

当父组件需调用子组件方法时,可在抽象令牌类中声明抽象方法,具体实现放在子组件中,既保证类型安全,又不影响摇树:

typescript 复制代码
// 抽象令牌声明方法
abstract class LibHeaderToken {
  abstract doSomething(): void;
}

// 子组件实现方法
class LibHeaderComponent extends LibHeaderToken {
  doSomething(): void {
    // 具体逻辑...
  }
}

// 父组件调用(需先判断子组件是否存在)
class LibCardComponent implements AfterContentInit {
  @ContentChild(LibHeaderToken) header: LibHeaderToken|null = null;

  ngAfterContentInit(): void {
    this.header?.doSomething(); // 子组件未使用时,无运行时引用,不会调用
  }
}

五、命名规范

遵循Angular风格指南,轻量级注入令牌命名需满足:

  • 与关联组件保持命名关联,同时明确区分
  • 推荐格式:组件基础名 + 后缀"Token",如组件LibHeaderComponent对应的令牌命名为LibHeaderToken

实际应用中的依赖注入

(一)使用@Inject创建自定义提供器(Custom providers with @Inject

  1. 应用场景:为隐式依赖项(如浏览器内置API)提供具体实现,同时便于测试时替换为模拟服务
  2. 核心原理 :通过InjectionToken定义令牌,结合factory函数提供依赖实例,再用inject函数在服务中注入该令牌对应的依赖
  3. 示例案例 :实现BrowserStorageService服务,将浏览器localStorage作为依赖注入
    • 定义令牌:创建BROWSER_STORAGE注入令牌,指定providedIn: 'root'(根级别提供),factory函数返回localStorage
    • 注入依赖:在BrowserStorageService中用inject(BROWSER_STORAGE)初始化storage属性,封装get/set方法操作本地存储
    • 测试优势:测试时可替换BROWSER_STORAGE的实现为模拟localStorage,避免依赖真实浏览器API
  4. 关键代码片段
typescript 复制代码
import { inject, Injectable, InjectionToken } from '@angular/core';
// 定义注入令牌
export const BROWSER_STORAGE = new InjectionToken<Storage>('Browser Storage', {
  providedIn: 'root',
  factory: () => localStorage // 提供localStorage实例
});
// 注入令牌并封装方法
@Injectable({ providedIn: 'root' })
export class BrowserStorageService {
  public storage = inject(BROWSER_STORAGE);
  get(key: string) { return this.storage.getItem(key); }
  set(key: string, value: string) { this.storage.setItem(key, value); }
}

(二)注入组件的DOM元素(Inject the component's DOM element)

  1. 应用场景:当视觉效果实现、第三方工具集成等场景需要直接操作DOM时,安全获取组件/指令的底层DOM元素
  2. 核心原理 :Angular通过ElementRef注入令牌,暴露@Component@Directive对应的DOM元素,开发者可通过nativeElement属性访问原生DOM节点
  3. 示例案例 :实现HighlightDirective指令,修改元素文本颜色
    • 注入ElementRef:在指令中用inject(ElementRef)获取DOM元素引用
    • 操作DOM:在update方法中通过this.element.nativeElement.style.color修改文本颜色为红色
  4. 关键代码片段
typescript 复制代码
import { Directive, ElementRef } from '@angular/core';
@Directive({ selector: '[appHighlight]' })
export class HighlightDirective {
  private element = inject(ElementRef); // 注入DOM元素引用
  update() {
    this.element.nativeElement.style.color = 'red'; // 操作DOM样式
  }
}

(三)使用前向引用解决循环依赖(Resolve circular dependencies with a forward reference)

  1. 问题背景 :TypeScript中类声明有顺序限制,未定义的类无法直接引用;当两个类互相引用(如A引用B、B引用A),或类在自身providers中引用自身时,会产生循环依赖问题
  2. 核心解决方案 :使用Angular的forwardRef()函数创建间接引用,让Angular在后续解析阶段识别依赖关系,打破循环引用
  3. 示例案例 :在MenuItem组件的providers中引用自身
    • 场景:MenuItem组件需在providers中提供PARENT_MENU_ITEM令牌,且令牌对应的值为组件自身
    • 解决方式:通过forwardRef(() => MenuItem)间接引用MenuItem类,避免因类未定义导致的引用错误
  4. 关键代码片段
typescript 复制代码
// 在组件的providers数组中使用forwardRef解决自引用
providers: [
  {
    provide: PARENT_MENU_ITEM,
    useExisting: forwardRef(() => MenuItem), // 间接引用未定义的MenuItem类
  },
],
相关推荐
crary,记忆4 天前
MFE: React + Angular 混合demo
前端·javascript·学习·react.js·angular·angular.js
苏打水com15 天前
前端框架深度解析:Angular 从架构到实战,掌握企业级开发标准
angular
KenkoTech1 个月前
Angular由一个bug说起之十九:Angular 实现可拓展 Dropdown 组件
angular
Liquad Li1 个月前
Angular 面试题及详细答案
前端·angular·angular.js
KenkoTech2 个月前
Angular由一个bug说起之十八:伴随框架升级而升级ESLint遇到的问题与思考
angular
KenkoTech3 个月前
Angular进阶之十三:Angular全新控制流:革命性的模板语法升级
angular
KenkoTech4 个月前
Angular进阶之十二:Chrome DevTools+Angular实战诊断指南
angular
crary,记忆4 个月前
微前端MFE:(React 与 Angular)框架之间的通信方式
前端·javascript·学习·react.js·angular
crary,记忆4 个月前
MFE微前端高级版:Angular + Module Federation + webpack + 路由(Route way)完整示例
前端·webpack·angular·angular.js