Angular初学者入门第三课——工厂函数(精品)

一、定义

在 Angular 中,工厂函数是一个返回服务或对象的函数。它被用来定义和创建服务、对象或者一些特定逻辑的实例,以方便在整个应用中复用。在 Angular 的依赖注入系统中,工厂函数是提供者(Provider)的一部分,用于创建某些对象或值,并将其注入到组件或服务中。

使用场景如下:

  • 动态配置:在运行时根据不同的环境条件返回不同的配置或服务实例
  • 复杂对象生成:需要组合多个服务或依赖项来生成一个复杂的对象时,可以使用工厂函数
  • 跨模块共享逻辑:当多个模块需要共享某些生成逻辑时,可以使用工厂函数来统一生成和管理这些逻辑

二、通过providers注入含简单参数的工厂函数

我们准备实现一个含简单参数的工厂函数来返回 service 实例,因此要先定义 service,然后再实现工厂函数。因为工厂函数的参数是简单类型,所以不需要添加到 deps 中,完整的示例如下:

TypeScript 复制代码
export class My01Service {
  constructor(private config: any) { }

  getConfig() {
    return this.config;
  }
}
TypeScript 复制代码
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);
}
TypeScript 复制代码
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'}) 装饰器,则相反。完整的示例如下:

TypeScript 复制代码
export class StringService {
  constructor() { }

  appendSuffix(input: string) {
    return input + "aaa";
  }
}

@Injectable({
  providedIn: 'root'
})
export class CountService {
  count:number = 0

  constructor() { }

  addCount() {
    this.count++;
    console.log(`In countService: ${this.count}`)
  }

  getCount() {
    return this.count
  }
}
TypeScript 复制代码
export class My02Service {

  constructor(private config: any, private stringService: StringService, private countService: CountService) { }

  getConfig() {
    let result = this.stringService.appendSuffix("01");
    return this.config + result;
  }
}
TypeScript 复制代码
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);
}
TypeScript 复制代码
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#中的匿名函数意思相同,都是指那些只有参数和方法体,没有方法名的函数。工厂函数本身可以接收参数,工厂函数所返回的匿名函数也可以有参数和返回值。

TypeScript 复制代码
//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) | 是,可以在定义之前使用 | 否,必须先定义后使用 |
| 定义方式 | 定义并命名函数 | 定义一个常量变量(可以是箭头函数或其他值) |
| 灵活性 | 只能导出函数 | 可以导出任何值,包括函数、对象、数组等 |
| 适用场景 | 工具函数、纯函数 | 匿名函数、模块内方法 |

TypeScript 复制代码
//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 了。

TypeScript 复制代码
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 装饰器。

TypeScript 复制代码
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;
  }
}
TypeScript 复制代码
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',
}
TypeScript 复制代码
@NgModule({
  providers: [
    { provide: API_CONFIG_IT, useValue: API_CONFIG },
  ],
})
export class AppModule {}

八、总结

相信各位看官认真读到这里后对工厂函数应该有一个清楚的认识和理解了,虽然说平常开发时经常会用到 @Injectable,但关于它的更底层的理论知识我们也应该了解一下,这样开发时对代码为什么这么些才会更通透。

OK,如果各位看官觉得本文对你有所帮助,请点赞、收藏、评论支持一下,我将感激不尽。

相关推荐
siroi几秒前
【nginx】NJS 的简单实践
前端
饮水机战神2 分钟前
震惊!多核性能反降11%?node接口压力测试出乎意料!
前端·node.js
一只叁木Meow3 分钟前
JavaScript数学库深度对比
前端
顾辰逸you5 分钟前
uniapp--咸虾米壁纸项目(一)
前端·微信小程序
方方洛19 分钟前
电子书阅读器:epub电子书文件的解析
前端·产品·电子书
idaibin20 分钟前
Rustzen Admin 前端简单权限系统设计与实现
前端·react.js
GISer_Jinger26 分钟前
Trae Solo模式生成一个旅行足迹App
前端·javascript
zhangbao90s27 分钟前
Intl API:浏览器原生国际化API入门指南
前端·javascript·html
艾小码29 分钟前
构建现代前端工程:Webpack/Vite/Rollup配置解析与最佳实践
前端·webpack·node.js
跟橙姐学代码34 分钟前
Python 集合:人生中最简单的真理,只有一次
前端·python·ipython