How to use Dependency Injection (DI) correctly in Angular2?

杀马特。学长 韩版系。学妹 提交于 2019-11-27 04:23:22
Thierry Templier

Dependency injection in Angular2 relies on hierarchical injectors that are linked to the tree of components.

This means that you can configure providers at different levels:

  • For the whole application when bootstrapping it. In this cases, all sub injectors (the component ones) will see this provider and share the instance associated with. When interacting, it will be the same instance
  • For a specific component and its sub components. Same as before but for à specific component. Other components won't see this provider. If you redefine something defined above (when bootstrapping for example), this provider will be used instead. So you can override things.
  • For services. There are no providers associated with them. They use ones of the injector from the element that triggers (directly = a component or indirectly = a component that triggers the call of service chain)

Regarding your other questions:

  • @Injectable. To inject into a class, you need a decorator. Components have one (the @Component one) but services are simple classes. If a service requires dependencies to be injected in it, you need this decorator.
  • @Inject. In most times, the type of constructor parameters is enough to let Angular2 determines what to inject. In some cases (for example, if you explicitly use an OpaqueToken and not a class to register providers), you need to specify some hints about what to inject. In such cases, you need to use @Inject.

See these questions for additional details:

Ankit Singh

Broad question, TL;DR version


@Injectable()

  • is a decorator which tells the typescript that decorated class has dependencies and does not mean that this class can be injected in some other.

  • And then TypeScript understands that it needs to Inject the required metadata into decorated class when constructing, by using the imported dependencies.

bootstrap(app, [service])

  • bootstrap() takes care of creating a root injector for our application when it’s bootstrapped. It takes a list of providers as second argument which will be passed straight to the injector when it is created.

  • You bootstrap your application with the services that are gonna be used in many places like Http, which also means you'll not need to write providers: [Http] in your class configuration.

providers: [service]

  • providers also does the work of passing all the services' arguments to Injector .

  • You put services in providers if it's not bootstrap()ped with. And is needed only in a few places.

@Inject()

  • is also a decorator a function that does the work of actually injecting those services
    like this. constructor(@Inject(NameService) nameService)
  • but if you use TS all you need to do is this constructor(nameService: NameService) and typescript will handle the rest.

Further Reading

Hope this helps. :)

I need to either use providers: []

For dependency injection to be able to create instances for you, you need to register providers for these classes (or other values) somewhere.

Where you register a provider determines the scope of the created value. Angulars DI is hierarchical.
If you register a provider at the root of the tree


>=RC.5

@NgModule({
  providers: [/*providers*/]
  ...
})

or for lazy loaded modules

static forRoot(config: UserServiceConfig): ModuleWithProviders {
  return {
    ngModule: CoreModule,
    providers: [
      {provide: UserServiceConfig, useValue: config }
    ]
  };
}

<=RC.4

(bootstrap(AppComponent, [Providers}) or @Component(selector: 'app-component', providers: [Providers]) (root component)


then all components and services that request an instance get the same instance.

If a provider is registered in one of the child components a new (different) instance is provided for descendants of this component.

If a component requests an instance (by a constructor parameter), DI looks "upwards" the component tree (starting from leaf towards the root) and takes the first provider it finds. If an instance for this provider was already created previously, this instance is used, otherwise a new instance is created.

@Inject()

When a component or service requests a value from DI like

constructor(someField:SomeType) {}

DI looks up the provider by the type SomeType. If @Inject(SomeType) is added

constructor(@Inject(SomeType) someField:SomeType) {}

DI looks up the provider by the parameter passed to @Inject(). In the above example the parameter passed to @Inject() is the same as the type of the parameter, therefore @Inject(SomeType) is redundant.

However there are situations where you want to customize the behavior for example to inject a configuration setting.

constructor(@Inject('someName') someField:string) {}

The type string isn't sufficient to distinguish a specific configuration setting when you have a several registered.
The configuration value needs to be registered as provider somewhere like


>=RC.5

@NgModule({
  providers: [{provide: 'someName', useValue: 'abcdefg'})]
  ...
})
export class AppModule {}

<=RC.4

bootstrap(AppComponent, [provide('someName', {useValue: 'abcdefg'})])

Therefor you don't need @Inject() for FormBuilder if the constructor looks like

constructor(formBuilder: FormBuilder) {}

I'll add a few things that I didn't see mentioned in the other answers. (At the time I'm writing this, that means the answers from Thierry, Günter, and A_Singh).

  • Always add Injectable() to services you create. Although it is only needed if your service itself needs to inject something, it's a best practice to always include it.
  • The providers array on directives/components and the providers array in NgModules are the only two ways to register providers that are not built-in. (Examples of built-in objects that we don't have to register are ElementRef, ApplicationRef, etc. We can simply inject these.)
  • When a component has a providers array, then that component gets an Angular injector. The injectors are consulted when something wants to inject a dependency (as specified in the constructor). I like to think of the injector tree as a sparer tree than the component tree. The first injector that can satisfy a dependency request does so. This hierarchy of injectors allows dependencies to be singletons or not.

Why @Injectable()?

@Injectable() marks a class as available to an injector for instantiation. Generally speaking, an injector will report an error when trying to instantiate a class that is not marked as @Injectable().

As it happens, we could have omitted @Injectable() from our first version of HeroService because it had no injected parameters. But we must have it now that our service has an injected dependency. We need it because Angular requires constructor parameter metadata in order to inject a Logger.

SUGGESTION: ADD @INJECTABLE() TO EVERY SERVICE CLASS We recommend adding @Injectable() to every service class, even those that don't have dependencies and, therefore, do not technically require it. Here's why:

Future proofing: No need to remember @Injectable() when we add a dependency later.

Consistency: All services follow the same rules, and we don't have to wonder why a decorator is missing.

Injectors are also responsible for instantiating components like HeroesComponent. Why haven't we marked HeroesComponent as @Injectable()?

We can add it if we really want to. It isn't necessary because the HeroesComponent is already marked with @Component, and this decorator class (like @Directive and @Pipe, which we'll learn about later) is a subtype of InjectableMetadata. It is in fact InjectableMetadata decorators that identify a class as a target for instantiation by an injector.

Source: https://angular.io/docs/ts/latest/guide/dependency-injection.html

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!