1

I was checking one of the nestjs module's source code (nestjs-firebase-admin) and I saw something weird. In this line:

private static createProviders(app: admin.app.App): Provider<any>[] {
  return PROVIDERS.map<Provider>((ProviderService) => ({
    provide: ProviderService,
    // here, instantiate each service class
    useFactory: () => new ProviderService(app),
  }));
}

Why do they instantiate each service class? This should be handled by nest core. As I know we just instantiate plain js classes when wrapping up another native plugin. But these are nestjs services. So we should not instantiate them manually. Any idea?

Note: PROVIDERS defined as (all of them are services):

const PROVIDERS = [
  FirebaseAuthenticationService,
  FirebaseMessagingService,
  FirebaseRemoteConfigService,
  FirebaseDatabaseService,
  FirebaseFirestoreService,
  FirebaseStorageService,
];
Vahid Najafi
  • 4,654
  • 11
  • 43
  • 88

1 Answers1

2

When we use useFactory we are telling Nest function should be called when the ProviderService (whatever that injection token may be) is used in the application for injection. It looks like the reason for this is that app is not an immediately injectable value (this can be fixed by making it a custom provider, but I guess the nestjs-firebase-admin team thought this was an easier solution). So instead of having injection errors about not being able to inject app automatically, the firebase-admin team decided to instantiate the providers themselves. This is a perfectly valid approach as well, and the reason things like useFactory exists (though, they could have easily used useValue: new ProviderService(app) and avoided the function all). It's just another way to do things

Jay McDoniel
  • 57,339
  • 7
  • 135
  • 147
  • Thank you so much. Is it possible to add your preferred method (custom provider) implementation? (no need for exact code, I just want to see the concept) – Vahid Najafi Apr 12 '21 at 18:55
  • I like `useClass` personally, cause in many cases (at least for dynamic modules) it allows for clean modules. You can extract out factory functions to other files as well. It really just depends on what the situation calls for. Need injection? `useClass` or `useFactory`. Got a class you want to use but don't want to instantiate? `useClass`. Need to just pass a string or object? `useValue`. There's use cases for all of them – Jay McDoniel Apr 12 '21 at 18:57
  • As I know we use `useValue` when we want to make an object injectable in that module, and we use `useClass` to have a class implementation in the same way. How can we use `useClass` for this scenario? – Vahid Najafi Apr 12 '21 at 19:10
  • After reading your answer over and over, I guess I got my answer. So I can accept it. But still have no idea about `useClass` and custom provider implementation :D – Vahid Najafi Apr 12 '21 at 19:12
  • @VahidNajafi what about `useClass: TypeOrmConfigService` line here: https://docs.nestjs.com/techniques/database#async-configuration – Micael Levi Apr 12 '21 at 19:20
  • Another use case for `useClass` besides what Micael showed, is when you have a symbol/abstract class that you want to use regularly, but want a different class per module, so you do something like `{ provide: 'Foo', useClass: FooBar }` and now `@Inject('Foo')` uses the `FooBar` class instance. It's definitely something that you see as you work with it. [Here's Nest's docs on it as well](https://docs.nestjs.com/fundamentals/custom-providers#class-providers-useclass) – Jay McDoniel Apr 12 '21 at 19:22
  • Thank you, guys. So you mean, In my specific example (nestjs-firebase), they could do `{ provide: 'ADMIN', useClass: app }` and access to that in all services instead of using `useFactory`? – Vahid Najafi Apr 12 '21 at 19:28
  • More like `{ provide: 'ADMIN', useValue: app } ` (as `app` is already a known value) and in each provider use `@Inject('ADMIN')` in the constructor and now each provider could be passed instead of having to use a factory. In the end, it's design choice – Jay McDoniel Apr 12 '21 at 19:30
  • Thank you so much. Now that really makes sense to me. – Vahid Najafi Apr 12 '21 at 19:34
  • The different provider methods useClass and useValue are explained here fairly well. https://docs.nestjs.com/fundamentals/custom-providers – m8a Apr 13 '21 at 04:01
  • @JayMcDoniel sorry for the inconvenience, I had one more question, if you don't mind. In this library, they created some services that wrap up each method of the native module (firebase-admin) with a service method. Is it neccasary? Is there any way not to do it? (i.g. https://github.com/Aginix/nestjs-firebase-admin/blob/master/lib/services/firebase-admin-authentication.service.ts#L15) – Vahid Najafi Apr 14 '21 at 11:52
  • 1
    It depends on how everything is exported. You could make an immediate provider that just gives the provided instance an injection token and use `@Inject(token)` to inject it. The service creating these wrapper methods is just to (hopefully) make for a better dev XP, but it's note necessary at all – Jay McDoniel Apr 14 '21 at 15:39