16

I already know what/how to use deps.
It is when a factory method needs Injected tokens , so we need to supply them like :

const randomFactory = (car,engine) => { return ... };
 ...
 providers: [Car,Engine, 
             { provide: 'Random', 
               useFactory: randomFactory ,  
               deps: [Car, Engine],
             },
            ]

But I've read here :

so it's basically deps only relevant when useFactory is used, correct?
->Exactly - only for useFactory

But then I've asked in other place :

Can deps be used with useClass ? I thought they are only for useFactory –
-> Yes they can. It would be useful when you’re injecting generic dependencies that require explicitly named tokens

I didn't want to continue comments in two places and hence my question :

Question:

In which scenarios would I use useClass with deps ?

Also , even if I used it , say class Foo :

Class Foo
{
 constructor ( private s1:Service1 , private s2:service2){}
}

^ Which already(!) have a ctor of its own. Where would deps dependencies be injected ? ( appended to ctor??)

}

An example of scenario + code would be much appreciated.

Royi Namir
  • 144,742
  • 138
  • 468
  • 792

3 Answers3

35

There are two kinds of providers:

StaticProvider and Provider

enter image description here

StaticProvider

It's kind of providers that are used to configure Injector in a static way(without Reflection).

According to the commit

platformXXXX() no longer accepts providers which depend on reflection. Specifically the method signature went from Provider[] to StaticProvider[].

Changelog

What does it mean?

  1. When we pass provider to platform we have to specify deps because we have to use StaticClassProvider or ConstructorProvider instead of just ClassProvider (see picture above).

    platformBrowserDynamic().bootstrapModule(AppModule, { providers: [ { provide: ElementSchemaRegistry, useClass: CustomDomElementSchemaRegistry, deps: [] <===================== required here } ] } );

  2. When we create Injector dynamically we have to specify deps because Injector.create takes StaticProvider array.

For instance:

export const MyParams = new InjectionToken<string[]>('params');

export class MyService {
  constructor(@Inject(MyParams) public someParameters: string[]) {}
}

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  name = 'Angular ' + VERSION.full;

  constructor() {
      const inj = Injector.create([
      { provide: MyService, useClass: MyService  } <=== we will get an error because we have to define deps
    ])
  }
}

https://ng-run.com/edit/5Xm4jwAoXXyAIspwF571

Provider

It's kind of providers that we usually use when write providers in @NgModule or @Component/@Directive metadata

Looking at this answer: how the parameters of a forRoot() module's method is passed to a provider? I would say that deps is not required there. We only need to provide Params in providers array and angular will do all job for us.


@estus said that:

deps are available only in useFactory providers but not in useClass providers.

because he meant Provider(more precisely ClassProvider) not StaticProvider.


P.S. You can also read my article about StaticInjector :)

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
yurzui
  • 205,937
  • 32
  • 433
  • 399
  • If i understand correctly , i should see deps in the demo . But i didnt find it. (It shows an error) https://imgur.com/a/BG3Eg – Royi Namir Feb 03 '18 at 08:50
  • You should add `, deps: [MyParams]` to do it working. I just showed that it's required in case of `Injector.create` here is the fixed version https://ng-run.com/edit/hqquxCxesyu4TQp9hwED – yurzui Feb 03 '18 at 08:58
4

Yes, deps may be used by useFactory or useClass.

You can see that this is true by looking at the Angular source (5+) for packages\compiler\src\metadata_resolver.ts. If deps is populated for either useClass or useFactory, then deps will be returned in the provider metadata:

getProviderMetadata(provider: cpl.ProviderMeta): cpl.CompileProviderMetadata {
    let compileDeps: cpl.CompileDiDependencyMetadata[] = undefined !;
    let compileTypeMetadata: cpl.CompileTypeMetadata = null !;
    let compileFactoryMetadata: cpl.CompileFactoryMetadata = null !;
    let token: cpl.CompileTokenMetadata = this._getTokenMetadata(provider.token);

    if (provider.useClass) {
      compileTypeMetadata = this._getInjectableMetadata(provider.useClass, provider.dependencies);
      compileDeps = compileTypeMetadata.diDeps; <-- ***HERE***
      if (provider.token === provider.useClass) {
        // use the compileTypeMetadata as it contains information about lifecycleHooks...
        token = {identifier: compileTypeMetadata};
      }
    } else if (provider.useFactory) {
      compileFactoryMetadata = this._getFactoryMetadata(provider.useFactory, provider.dependencies);
      compileDeps = compileFactoryMetadata.diDeps;  <-- ***HERE***
    }

    return {
      token: token,
      useClass: compileTypeMetadata,
      useValue: provider.useValue,
      useFactory: compileFactoryMetadata,
      useExisting: provider.useExisting ? this._getTokenMetadata(provider.useExisting) : undefined,
      deps: compileDeps, <-- ***HERE ***
      multi: provider.multi
    };
}

You can see from packages\compiler\src\view_compiler\provider_compiler.ts that deps is used to instantiate a multi provider:

if (provider.useClass) {
      const depExprs = convertDeps(providerIndex, provider.deps || provider.useClass.diDeps);
      expr = ctx.importExpr(provider.useClass.reference).instantiate(depExprs); <-- ***HERE***
} else if (provider.useFactory) {
      const depExprs = convertDeps(providerIndex, provider.deps || provider.useFactory.diDeps);
      expr = ctx.importExpr(provider.useFactory.reference).callFn(depExprs);<-- ***HERE***
} 

The same is true for single providers (https://github.com/angular/angular/blob/5.2.x/packages/compiler/src/view_compiler/provider_compiler.ts#L89).

if (providerMeta.useClass) {
  providerExpr = ctx.importExpr(providerMeta.useClass.reference);
  flags |= NodeFlags.TypeClassProvider;
  deps = providerMeta.deps || providerMeta.useClass.diDeps; <-- ***HERE***
} else if (providerMeta.useFactory) {
  providerExpr = ctx.importExpr(providerMeta.useFactory.reference);
  flags |= NodeFlags.TypeFactoryProvider;
  deps = providerMeta.deps || providerMeta.useFactory.diDeps; <-- ***HERE***
}

So even though it is not well documented, deps can be used by useClass or useFactory.

As an aside, deps is ignored when using useExisting or useValue providers (https://github.com/angular/angular/blob/5.2.x/packages/compiler/src/view_compiler/provider_compiler.ts#L108):

} else if (providerMeta.useExisting) {
  providerExpr = o.NULL_EXPR;
  flags |= NodeFlags.TypeUseExistingProvider;
  deps = [{token: providerMeta.useExisting}];
} else {
  providerExpr = convertValueToOutputAst(ctx, providerMeta.useValue);
  flags |= NodeFlags.TypeValueProvider;
  deps = [];
}

That being said, in the typical case, having a useClass provider and explicitly naming dependencies in the deps array is usually not necessary. You should let DI handle that for you implicitly.

I found some obscure use cases for it when trying to implement a static forRoot method as referenced by these questions here and here.

Michael Kang
  • 52,003
  • 16
  • 103
  • 135
  • Do you have ideas how this is supposed to work in practice? [It seems like it isn't](http://plnkr.co/edit/WVFqXblegWWDrIWOMGzE?p=preview). I didn't investigate that far to figure out why `deps` are in source code you've posted. But since ClassProvider interface doesn't have deps, this means that it's unsupported to some degree (to my knowledge, it never was). – Estus Flask Feb 03 '18 at 17:09
  • It seems like support for `deps` was intentional (based on the source) and it does work if you choose to use them together. As to whether it may be deprecated in the future, I cannot say... I agree that by not having `deps` in the class provider interface seems to suggest that it may not be the intended use case. – Michael Kang Feb 03 '18 at 17:19
  • There is no technical reason why `deps` should not work for either as the implementation is very similar. This may be a case where Angular has chosen to support `deps` for the `useClass` provider and give the developer flexibility to choose whether to use it or not. Perhaps this inconsistency in the design of the framework should be addressed (anyone want to raise it to the core team?). – Michael Kang Feb 03 '18 at 17:36
  • As I've figured out (I guess this is mentioned in another answer that mentions StaticInjector), `deps` can be used with classes only as [ConstructorProvider](https://angular.io/api/core/StaticProvider), i.e. it is workable with AOT but not JIT. That's likely the reason why it doesn't work in plunker. – Estus Flask Feb 06 '18 at 18:48
  • Agreed - it is inconsitent. It works for AOT but ignored for JIT. – Michael Kang Feb 06 '18 at 19:37
1

deps are available only in useFactory providers but not in useClass providers.

That's because the method that was chosen by Angular team for DI annotation as preferable (emitted type metadata) is applicable only to class constructors. While regular functions that are used in useFactory providers can't make use of type metadata and need alternative annotation method, which is dep array.

As explained in this answer, classes can be alternatively annotated for DI with parameters static property. Both class provider parameters and factory provider deps accept an array consisting of provider tokens or arrays of decorator instances (like [new Optional(), new Inject(...)]).

Estus Flask
  • 206,104
  • 70
  • 425
  • 565