Angular初学者入门第三课——工厂函数(精品)
一、定义
在 Angular 中,工厂函数是一个返回服务或对象的函数。它被用来定义和创建服务、对象或者一些特定逻辑的实例,以方便在整个应用中复用。在 Angular 的依赖注入系统中,工厂函数是提供者(Provider)的一部分,用于创建某些对象或值,并将其注入到组件或服务中。
使用场景如下:
- 动态配置:在运行时根据不同的环境条件返回不同的配置或服务实例
- 复杂对象生成:需要组合多个服务或依赖项来生成一个复杂的对象时,可以使用工厂函数
- 跨模块共享逻辑:当多个模块需要共享某些生成逻辑时,可以使用工厂函数来统一生成和管理这些逻辑
二、通过providers注入含简单参数的工厂函数
我们准备实现一个含简单参数的工厂函数来返回 service 实例,因此要先定义 service,然后再实现工厂函数。因为工厂函数的参数是简单类型,所以不需要添加到 deps 中,完整的示例如下:
export class My01Service {constructor(private config: any) { }getConfig() {return this.config;}
}
import { My01Service } from "./my01.service";export function my01ServiceFactory(environment: any): My01Service {const config = {apiUrl: environment.production ? 'https://api.prod.com' : 'http://api.dev.com',};return new My01Service(config);
}
providers: [/* 按需静态加载图标 */provideNzIcons(icons),{provide: APP_INITIALIZER,/* 项目启动时加载缓存中的主题样式 */useFactory: loadTheme,deps: [ThemeService],multi: true},{provide: My01Service, //与useFactory配合使用表示工厂函数的返回值就是My01Service类型的对象useFactory: () => my01ServiceFactory(environment)}
]
三、通过providers注入含service类型参数的工厂函数
如果工厂函数所返回的 service 实例依赖其他的 service 对象,那我们就以参数的形式把依赖的对象传给工厂函数。如果依赖的 service 对象(以CountService为例)有@Injectable({providedIn: 'root'}) 装饰器,就不需要添加到 providers、deps 中。如果依赖的 service 对象(以StringService为例)没有@Injectable({providedIn: 'root'}) 装饰器,则相反。完整的示例如下:
export class StringService {constructor() { }appendSuffix(input: string) {return input + "aaa";}
}@Injectable({providedIn: 'root'
})
export class CountService {count:number = 0constructor() { }addCount() {this.count++;console.log(`In countService: ${this.count}`)}getCount() {return this.count}
}
export class My02Service {constructor(private config: any, private stringService: StringService, private countService: CountService) { }getConfig() {let result = this.stringService.appendSuffix("01");return this.config + result;}
}
import { CountService } from "./count.service";
import { My02Service } from "./my02.service";
import { StringService } from "./string.service";export function my02ServiceFactory(environment: any, stringService: StringService, countService: CountService): My02Service {const config = {apiUrl: environment.production ? 'https://api.prod.com' : 'http://api.dev.com',};return new My02Service(config, stringService, countService);
}
providers: [StringService,/* 按需静态加载图标 */provideNzIcons(icons),{provide: APP_INITIALIZER,/* 项目启动时加载缓存中的主题样式 */useFactory: loadTheme,deps: [ThemeService],multi: true},{provide: My01Service,useFactory: () => my01ServiceFactory(environment)},{provide: My02Service, //与useFactory配合使用表示工厂函数的返回值就是My02Service类型的对象useFactory: (stringService: StringService, countService: CountService) => my02ServiceFactory(environment, stringService, countService),deps: [StringService]}
]
providers: [StringService] 等价于 providers: [{provide: StringService, useClass: StringService}]
四、export function返回匿名函数
二、三节都是通过工厂函数返回一个 service 对象,我们也可以通过工厂函数来返回一个匿名函数。这里的匿名函数跟C#中的匿名函数意思相同,都是指那些只有参数和方法体,没有方法名的函数。工厂函数本身可以接收参数,工厂函数所返回的匿名函数也可以有参数和返回值。
//export function可以在定义之前使用
loggerFactory01('add','guo')(3,5);export function loggerFactory01(environment: string, userId: string): (num01: number, num02: number) => number {return (n01: number, n02: number) => {if (environment == "add") {let result = n01 + n02;console.log(`Add result is ${result}`)return result;}else {let result = n01 - n02;console.log(`Subtract result is ${result}`)return result;}}
}
五、export const返回匿名函数
特性 | export function | export const |
是否支持提升(Hoisting) | 是,可以在定义之前使用 | 否,必须先定义后使用 |
定义方式 | 定义并命名函数 | 定义一个常量变量(可以是箭头函数或其他值) |
灵活性 | 只能导出函数 | 可以导出任何值,包括函数、对象、数组等 |
适用场景 | 工具函数、纯函数 | 匿名函数、模块内方法 |
//export const支持多种类型的导出值,不仅限于函数,还可以导出对象、数组等
export const config = {apiUrl: 'https://example.com',timeout: 5000
};export const loggerFactory02 = (environment: string): ((num01: number, num02: number) => number) => (n01: number, n02: number) => {if (environment == "multiply") {let result = n01 * n02;console.log(`Multiply result is ${result}`)return result;}else {let result = n01 / n02;console.log(`Divide result is ${result}`)return result;}}
六、使用刚才通过providers注入的service
在 app.module.ts 中注入完毕后我们就可以直接在 component 的构造函数中接收并使用这些 sercive 了。
import { Component, Inject, inject, OnInit } from '@angular/core';
import { loggerFactory01, loggerFactory02 } from '../../services/factoryFunc'
import { My01Service } from '../../services/my01.service';
import { My02Service } from '../../services/my02.service';@Component({selector: 'app-resource',standalone: true,imports: [ CommonModule ],templateUrl: './resource.component.html',styleUrls: ['./resource.component.less']
})
export class ResourceComponent implements OnInit {//为构造函数添加刚才注入的service类型参数,Angular 的 DI 系统会把相应的对象传进来constructor(private store: Store, private my01Service: My01Service, private my02Service: My02Service) { //先在组件内部import引用工厂函数再调用loggerFactory01('add','guo')(3,5);loggerFactory01('subtract','guo')(5,6);loggerFactory02('multiply')(2,3);loggerFactory02('divide')(10,2);//使用构造函数的实参进行方法调用let config01 = this.my01Service.getConfig();console.log(`config01 is ${config01}`)let config02 = this.my02Service.getConfig();console.log(`config02 is ${config02}`)}
}
七、@Injectable的用法
@Injectable 是从 Angular2 引入的功能, 用于标记一个类可以被依赖注入系统管理。通过 @Injectable 装饰器,我们可以声明一个服务或其他类为可注入的依赖项。这使得 Angular 的 DI 系统能够将该类注册到提供者(providers)中,并在需要的地方提供其实例。从 Angular6 开始引入了 providedIn。
除了第三部分中 CountService 体现的通常用法外,当需要显式指定依赖项的注入令牌(Injection Token)时,可以使用 @Injectable 装饰器。
import { Injectable, Inject } from '@angular/core';
import { API_CONFIG_IT, AppConfig } from './api-config';@Injectable()
export class MyService {constructor(@Inject(API_CONFIG_IT) private config: ApiConfig) {}getApiEndpoint(): string {return this.config.apiEndpoint;}
}
import { InjectionToken } from '@angular/core';export interface ApiConfig {apiEndpoint: string;title: string;
}export const API_CONFIG_IT = new InjectionToken<ApiConfig>('api.config');export const API_CONFIG: ApiConfig = {apiEndpoint: 'https://api.example.com',title: 'My Angular App',
}
@NgModule({providers: [{ provide: API_CONFIG_IT, useValue: API_CONFIG },],
})
export class AppModule {}
八、总结
相信各位看官认真读到这里后对工厂函数应该有一个清楚的认识和理解了,虽然说平常开发时经常会用到 @Injectable,但关于它的更底层的理论知识我们也应该了解一下,这样开发时对代码为什么这么些才会更通透。
OK,如果各位看官觉得本文对你有所帮助,请点赞、收藏、评论支持一下,我将感激不尽。