When to use viewProviders in Angular – Simplified

I have often seen developers are confused about viewProviders property of the @Componnet decorator.  This post explains to you about the viewProviders option.

The viewProviders defines the set of injectable objects that are visible to its view, DOM children. They are not visible to the content children.

At the component level, you can provide a service in two ways:

  1. Using the providers array
  2. Using the viewProviders array.

If you want to use different classes for the same token used in a component and its content children, you should use providers array to provide objects to content children and viewProviders array to provide an object to the current view, child components, and its view children.

Perhaps, you might already be aware of the functionalities and behaviour of providers array. You can revise DI and Services in Angular by watching a video on the same here.  

Now, to understand the purpose of viewProviders, let us start with creating two services. Create a LogService, as shown below.


import { Injectable } from '@angular/core';

@Injectable()
export class LogService {
  constructor() { }

  sayHello(name: string) {
    return "hello" + name + "Log Service Class";
  }
}

And then create one more service called LogUpdatedService as shown below,


import { Injectable } from '@angular/core';

@Injectable()
export class LogupdatedService {
  constructor() { }

  sayHello(name: string) {
    return "hello" + name + "Log Updated Service Class";
  }
}

Both services are straightforward with just one function; however, one crucial thing you may notice that for both of the services provideIn option is not set, because we will provide these services at the component level.

After creating the services, next, create two components that usage LogService. So, create a component Child2Component and to use the LogService in it, inject it in the constructor of the component.


import { Component, OnInit } from '@angular/core';
import { LogService } from '../log.service';

const template =`
<hr/>
<h2>Child 2 Component</h2>
<h3>{{message}}</h3>
`;
@Component({
  selector: 'app-child2',
  template:template
})
export class Child2Component implements OnInit {

  message: string;
  constructor(private logservice: LogService) {

  }
  ngOnInit(): void {
    this.message = this.logservice.sayHello("Child 2");
  }
}

Child2Component calls sayHello function of LogService and simply displays the message.  Next, create one more component Child1Component as shown in the next code listing,


import { Component, OnInit } from '@angular/core';
import { LogService } from '../log.service';
import { LogupdatedService } from '../logupdated.service';
 
const template = `
<hr/>
<h2>Child 1 Component</h2>
<h3>{{message}}</h3>
<ng-content></ng-content>
`

@Component({
  selector: 'app-child1',
  template:template,
  providers:[LogService]
})
export class Child1Component implements OnInit {

  message: string;
  constructor(private logservice: LogService) {

  }
  ngOnInit(): void {
    this.message = this.logservice.sayHello("Child 1");
  }
}

Let us walk through the Child1Component,

  • Its template has <ng-content>, which means whenever Child1Component would be used, an HTML element or other component can be projected in it.  We will project Child2Component here.
  • The LogService is provided using the providers array.

You can use Child1Component by projecting Child2Component in it as shown below,


<h2>App Componnet</h2>
<app-child1>
    <app-child2></app-child2>
</app-child1>

Here Child2Component is a Content Child of Child1Component.  As of now, Child1Component and Child2Component both are using LogService.

There are a couple of essential points, you should notice

  • You have injected LogService token in both Child1Component and Child2Component
  • The Child2Component searches provider for the LogService token and finds in parent Child1Component, and use that.
  • The Child1Component use LogService from its providers array.
  • The providers array of ChildComponent resolves the token LogService and returns an object of LogService class to both the components.

Here, you find that both the components using the instance of LogeService class.

So far so good, now consider a scenario,

  • Both the Child1Component and Child2Component has LogService token injected
  • For Child1Component, you want to use the LogUpdatedService class
  • For Child2Component, you want to use the LogService class.

Keep in mind that both the components have the same token and Child2Component is a content child of Child1Component.

 You can configure LogService token to use LogUpdatedService class using the useClass as below,


@Component({
  selector: 'app-child1',
  template:template,
  providers:[{provide:LogService,useClass:LogupdatedService}]
})
export class Child1Component implements OnInit {

Now when you run the application, you find an object of LogupdatedService is passed to both the components.

But we require to use different classes for these two components. And If I put it more technically, we want to use a different class for Content Children and View Children with the same injector token.  

You can achieve it by using viewProviders array of the @Component decorator.  Let us modify the Child1Component to use viewProviders as below,


@Component({
  selector: 'app-child1',
  template:template,
  providers:[{provide:LogService,useClass:LogService}],
  viewProviders:[{provide:LogService,useClass:LogupdatedService}]
})
export class Child1Component implements OnInit {


Above, you have set both providers and viewProviders array for Child1Component. The viewProvider allows us to make providers available only for the component’s view, and it would not be available to the content children.

Some of the characteristics of the providers array and the viewProviders array are as below,

  • The viewProvider allows us to make providers available only for the component’s view, and it is not available for the content children.
  • If set, used by the component, child components, and View Children
  • For an injector, the first component searches for the viewProviders, then providers array and so on

Content children always use providers array.

On running the application, you find different classes are used for Child1Component and Child2Component

In summary, if you want to use different classes for the same token used in a component and its content children, you should use providers array to provide objects to content children and viewProviders array to provide an object to the current view, child components, and its view children.

I hope you find this post useful. Thanks for reading.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s