Angular 6+ complete dependency deployment guide. providedIn vs providers: []

Original author: Tomas Trajan
  • Transfer
image

In Angular 6, a new improved syntax for introducing service dependencies into the application ( provideIn ) has appeared. Despite the fact that Angular 7 has already been released, this topic is still relevant. There is a lot of confusion in GitHub, Slack and Stack Overflow comments, so let's take a closer look at this topic.

In this article we will consider:


  1. Dependency injection ( dependency injection );
  2. The old way to add dependencies to Angular ( providers: [] );
  3. A new way to embed dependencies in Angular ( providedIn: 'root' | SomeModule );
  4. Usage scenarios provideIn ;
  5. Recommendations for the use of new syntax in applications;
  6. Let's sum up.

Dependency Injection


You can skip this section if you already have an idea about DI .
Dependency Injection ( DI ) is a way to create objects that depend on other objects. The dependency injection system provides dependent objects when it creates an instance of a class.

- Angular documentation

Formal explanations are good, but let's take a closer look at what dependency injection is.

All components and services are classes. Each class has a special constructor method , which, when called, creates an instance object of this class that is used in the application.

Suppose one of our services has the following code:

constructor(private http: HttpClient)

If you create it without using the dependency injection mechanism, you must add the HttpClient manually. Then the code will look like this:

const myService = new MyService(httpClient)

But where does httpClient come from ? It also needs to be created:

const httpClient = new HttpClient(httpHandler)

But where does httpHandler come from ? And so on, until instances of all necessary classes are created. As we can see, manual creation can be complicated and errors can occur in the process.

Angular's dependency injection mechanism does it all automatically. All we have to do is specify the dependencies in the component constructor, and they will be added without any effort on our part.

The old way to add dependencies to Angular (providers: [])


To run the application, Angular needs to know about each individual object that we want to embed in components and services. Before the release of Angular 6, the only way to do this was to specify the services in the providers property : [] decorators @NgModule , @ Сomponent and @Directive .

image

Let's look at three main use cases for providers: [] :

  1. In the decorator @NgModule immediately loadable module ( eager );
  2. In the decorator @NgModule module with delayed loading ( lazy );
  3. In decorators @ Сomponent and @Directive .

Modules loaded with the application (Eager)


In this case, the service is registered in the global scope as a singleton. It will be singleton even if it is included in the providers [] of several modules. A single instance of the service class is created, which will be registered at the root level of the application.

Delayed Load Modules (Lazy)


An instance of the service connected to the lazy module will be created during its initialization. Adding such a service to the module's eager component will result in an error: No provider for MyService! error .

Implementation in @ Сomponent and @Directive


When implemented in a component or a directive, a separate instance of the service is created, which will be available in this component and all its children. In this situation, the service will not be a singleton; its instance will be created each time the component is used and deleted along with the removal of the component from the DOM.

image

In this case, RandomService is not implemented at the module level and is not a singleton,
but is registered with the providers: [] component RandomComponent . As a result, we will receive a new random number each time using <randоm </ randоm> .

New way to embed dependencies in Angular (providedIn: 'root' | SomeModule)


In Angular 6, we got the new “Tree-shakable providers” tool to inject dependencies into the application, which can be used with the providedIn property of the decorator @Injectable .

You can provide providedIn as dependency injection in the opposite direction: earlier, the module described the services to which it will be connected, now the service determines the module to which it is connected.

The service can be embedded in the application root ( providedIn: 'root' ) or in any module ( providedIn: SomeModule ). providedIn: 'root' is short for implementation in AppModule .

image

Let us analyze the main scenarios for using the new syntax:

  1. Embedding into the application root module ( providedIn: 'root' );
  2. Implementing an immediately loadable module ( eager );
  3. Deploy in a lazy module .

Embedding into the application root module (providedIn: 'root')


This is the most common version of dependency injection. In this case, the service will be added to the bundle application only if it is actually used, i.e. embedded in a component or other service.

When using the new approach there will not be much difference in the monolithic SPA application, where all the written services are used, however providedIn: 'root' will be useful when writing libraries.

Previously, all library services needed to be added to the providers: [] of its module. After importing the library into the application, all services were added to the bundle, even if only one was used. In the case of providedIn: 'root', there is no need to connect the library module. Simply implement the service in the desired component.

A lazy module and providedIn: 'root'


What happens if you implement the service with providedIn: 'root' in the lazy module?

Technically, 'root' stands for AppModule , but Angular is smart enough to add the service to the lazy bundle of the module if it is embedded only in its components and services. But there is one problem (although some people claim that this is a feature). If you later inject the service used only in the lazy module into the main module, the service will be transferred to the main bandl. In large applications with many modules and services, this can lead to problems with dependency tracking and unpredictable behavior.

Be careful! The introduction of one service in a variety of modules can lead to hidden dependencies that are difficult to understand and impossible to unravel.

Fortunately, there are ways to prevent this, and we’ll look at them below.

Dependency injection in immediately loadable module (eager)


Typically, this case does not make sense, and instead we can use providedIn: 'root' . Connecting a service in EagerModule can be used to encapsulate and prevent deployment without plugging in a module, but in most cases this is not necessary.

If you really need to limit the scope of the service, it is easier to use the old way of providers: [] , since it will not exactly lead to cyclic dependencies.

If possible, try to use providedIn: 'root' in all eager modules.

Note. Advantage of modules with delayed loading (lazy)


One of the main features of Angular is the ability to easily break the application into fragments, which provides the following benefits:

  1. The small size of the main application bundle, which is why the application loads and starts faster;
  2. The module with delayed loading is well isolated and is connected in the application once in the loadChildren property of the corresponding route.

Due to deferred loading, a whole module with hundreds of services and components can be deleted or put into a separate application or library, with little or no effort.

Another advantage of the isolation of the lazy module is that the error made in it will not affect the rest of the application. Now you can sleep well even on the day of release.

Implementing a Delayed Load Module (providedIn: LazyModule)


image

The implementation of dependencies in a specific module does not allow using the service in the rest of the application. This allows you to maintain the structure of dependencies, which is especially useful for large applications in which indiscriminate dependency injection can lead to confusion.

Interesting fact: If the lazy service is introduced into the main part of the application, then the build (even the AOT) will pass without errors, but the application will fail with the error "No provider for LazyService".

Problem with cyclical dependency


image

You can reproduce the error as follows:

  1. Create a module LazyModule ;
  2. Create the LazyService service and connect using providedIn: LazyModule ;
  3. Create the LazyComponent component and connect to the LazyModule ;
  4. Add LazyService to the constructor of the LazyComponent component ;
  5. We get an error with a cyclic dependency.

Schematically, it looks like this: service -> module -> component -> service .

This problem can be solved by creating a LazyServiceModule sub- module that will be connected to the LazyModule . To connect to the submodule services.
image

In this case, you will have to create an additional module, but this does not require much effort and will give the following advantages:

  1. Prevents service integration into other application modules;
  2. The service will be added to the bundle only if it is embedded in the component or another service used in the module.

Implementing a service in a component (providedIn: SomeComponent)


Is it possible to introduce the service in @ COMPONENT or @Directive using the new syntax?

Not at the moment!

To create a service instance for each component still must use providers: [] in the decorator @ COMPONENT or @Directive .

image

Recommendations for using new syntax in applications


Libraries


providedIn: 'root' is well suited for creating libraries. This is really a convenient way to plug into the main application only the directly used part of the functionality and reduce the size of the final assembly.

One practical example is the ngx-model library , which was rewritten using the new syntax and is now called @ angular-extensions / model . In the new implementation there is no need to connect the NgxModelModule to the application, it is enough just to embed the ModelFactory into the required component. Details of the implementation can be found here .

Lazy Load Modules


Use for services a separate module providedIn: LazyServicesModule and connect it to LazyModule . This approach encapsulates services and will not allow them to be connected to other modules. This will mark the boundaries and help create a scalable architecture.

In my experience, accidental embedding into a main or additional module (using providedIn: 'root') can be confusing and is not the best solution!

providedIn: 'root' will also work correctly, but when using providedIn: LazyServideModule, we will get a “missing provider” error when deployed to other modules and will be able to fix the architecture. Move the service to a more suitable place in the main part of the application.

When should providers: [] be used?


In cases when it is necessary to configure the module. For example, connect the service only to SomeModule.forRoot (someConfig) .

image

On the other hand, in such a situation, you can use providedIn: 'root'. This will ensure that the service will be added to the application only once.

findings


  1. Use providedIn: 'root' to register the service as a singleton, available throughout the application.
  2. For the module included in the main bundle, use providedIn: 'root' , but not providedIn: EagerlyImportedModule . In exceptional cases, use providers: [] to encapsulate .
  3. Create a sub-module with services to limit their scope providedIn: LazyServiceModule when using deferred loading.
  4. Connect the LazyServiceModule module to the LazyModule to prevent circular dependencies.
  5. Use providers: [] in the decorator @ COMPONENT and @Directive to create a new instance of the service for each new instance of the component. The service instance will also be available in all child components.
  6. Always limit dependency areas to improve architecture and avoid confusing dependencies.

Links


Original article.
Angular - Russian-speaking community.
Angular Meetups in Russia

Also popular now: