Chapter 3: The Magic of Dependency Injection in Angular

This chapter is all about the magic of dependency injection (DI) in Angular. Here, you'll learn some detailed information about the concept of DI in Angular. DI is the process that Angular uses to inject different dependencies into components, directives, and services. You'll work with several examples using services and providers to get some hands-on experience that you can utilize in your later Angular projects.

In this chapter, we're going to cover the following recipes:

  • Configuring an injector with a DI token
  • Optional dependencies
  • Creating a singleton service using providedIn
  • Creating a singleton service using forRoot()
  • Providing different services to the app with the same Aliased class provider
  • Value providers in Angular

Technical requirements

For the recipes in this chapter, ensure you have Git and NodeJS installed on your machine. You also need to have the @angular/cli package installed, which you can do so using npm install -g @angular/cli from your Terminal. The code for this chapter can be found at https://github.com/PacktPublishing/Angular-Cookbook/tree/master/chapter03.

Configuring an injector with a DI token

In this recipe, you'll learn how to create a basic DI token for a regular TypeScript class to be used as an Angular service. We have a service (UserService) in our application, which currently uses the Greeter class to create a user with a greet method. Since Angular is all about DI and services, we'll implement a way in which to use this regular TypeScript class, named Greeter, as an Angular service. We'll use InjectionToken to create a DI token and then the @Inject decorator to enable us to use the class in our service.

Getting ready

The project that we are going to work with resides in chapter03/start_here/ng-di-token, which is inside the cloned repository. Perform the following steps:

  1. Open the project in Visual Studio Code.
  2. Open the Terminal and run npm install to install the dependencies of the project.
  3. Once done, run ng serve -o.

    This should open the app in a new browser tab; you should see something similar to the following screenshot:

Figure 3.1 – The ng-di-token app running on http://localhost:4200

Figure 3.1 – The ng-di-token app running on http://localhost:4200

Now that we have the app running, we can move on to the steps for the recipe.

How to do it...

The app we have right now shows a greeting message to a random user that has been retrieved from our UserService. And UserService uses the Greeter class as it is. Instead of using it as a class, we'll use it as an Angular service using DI. We'll start by creating an InjectionToken for our Greeter class, which is a regular TypeScript class, and then we'll inject it into our services. Perform these steps to follow along:

  1. We'll create an InjectionToken in the greeter.class.ts file, called 'Greeter', using the InjectionToken class from the @angular/core package. Additionally, we'll export this token from the file:

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

    import { User } from '../interfaces/user.interface';

    export class Greeter implements User {

      ...

    }

    export const GREETER = new InjectionToken('Greeter', {

      providedIn: 'root',

      factory: () => Greeter

    });

  2. Now, we'll use the Inject decorator from the @angular/core package and the GREETER token from greeter.class.ts so that we can use them in the next step:

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

    import { GREETER, Greeter } from '../classes/greeter.class';

    @Injectable({

      providedIn: 'root'

    })

    export class UserService {

      ...

    }

  3. We'll now inject the Greeter class using the @Inject decorator in constructor of UserService as an Angular service.

    Notice that we'll be using typeof Greeter instead of just Greeter because we need to use the constructor later on:

    ...

    export class UserService {

      ...

      constructor(@Inject(GREETER) public greeter: typeof    Greeter) { }

      ...

    }

  4. Finally, we can replace the usage of new Greeter(user) inside the getUser method by using the injected service, as follows:

    ...

    export class UserService {

      ...

      getUser() {

        const user = this.users[Math.floor(Math.random()     * this.users.length)]

        return new this.greeter(user);

      }

    }

Now that we know the recipe, let's take a closer look at how it works.

How it works

Angular doesn't recognize regular TypeScript classes as injectables in services. However, we can create our own injection tokens and use the @Inject decorator to inject them whenever possible. Angular recognizes our token behind the scenes and finds its corresponding definition, which is usually in the form of a factory function. Notice that we're using providedIn: 'root' within the token definition. This means that there will be only one instance of the class in the entire application.

See also

Optional dependencies

Optional dependencies in Angular are really powerful when you use or configure a dependency that may or may not exist or that has been provided within an Angular application. In this recipe, we'll learn how to use the @Optional decorator to configure optional dependencies in our components/services. We'll work with LoggerService and ensure our components do not break if it has not already been provided.

Getting ready

The project for this recipe resides in chapter03/start_here/ng-optional-dependencies. Perform the following steps:

  1. Open the project in Visual Studio Code.
  2. Open the Terminal, and run npm install to install the dependencies of the project.
  3. Once done, run ng serve -o.

    This should open the app in a new browser tab. You should see something similar to the following screenshot:

Figure 3.2 – The ng-optional-dependencies app running on http://localhost:4200

Figure 3.2 – The ng-optional-dependencies app running on http://localhost:4200

Now that we have the app running, we can move on to the steps for the recipe.

How to do it

We'll start with an app that has a LoggerService with providedIn: 'root' set to its injectable configuration. We'll see what happens when we don't provide this service anywhere. Then, we'll identify and fix the issues using the @Optional decorator. Follow these steps:

  1. First, let's run the app and change the version in the input.

    This will result in the logs being saved in localStorage via LoggerService. Open Chrome Dev Tools, navigate to Application, select Local Storage, and then click on localhost:4200. You will see the key log_log with log values, as follows:

    Figure 3.3 – The logs are saved in localStorage for http://localhost:4200

    Figure 3.3 – The logs are saved in localStorage for http://localhost:4200

  2. Now, let's try to remove the configuration provided in the @Injectable decorator for LoggerService, which is highlighted in the following code:

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

    import { Logger } from '../interfaces/logger';

    @Injectable({

      providedIn: 'root' ← Remove

    })

    export class LoggerService implements Logger {

      ...

    }

    This will result in Angular not being able to recognize it and throwing an error to VcLogsComponent:

    Figure 3.4 – An error detailing that Angular doesn't recognize LoggerService

    Figure 3.4 – An error detailing that Angular doesn't recognize LoggerService

  3. We can now use the @Optional decorator to mark the dependency as optional. Let's import it from the @angular/core package and use the decorator in the constructor of VcLogsComponent in the vc-logs.component.ts file, as follows:

    import { Component, OnInit, Input, OnChanges, SimpleChanges, Optional } from '@angular/core';

    ...

    export class VcLogsComponent implements OnInit {

      ...

      constructor(@Optional() private loggerService:   LoggerService) {

        this.logger = this.loggerService;

      }

      ...

    }

    Great! Now if you refresh the app and view the console, there shouldn't be any errors. However, if you change the version and hit the Submit button, you'll see that it throws the following error because the component is unable to retrieve LoggerService as a dependency:

    Figure 3.5 – An error detailing that this.logger is essentially null at the moment

    Figure 3.5 – An error detailing that this.logger is essentially null at the moment

  4. To fix this issue, we can either decide not to log anything at all, or we can fall back to the console.* methods if LoggerService is not provided. The code to fall back to the console.* methods should appear as follows:

    ...

    export class VcLogsComponent implements OnInit {

      ...

      constructor(@Optional() private loggerService:   LoggerService) {

        if (!this.loggerService) {

          this.logger = console;

        } else {

          this.logger = this.loggerService;

        }

      }

      ...

    Now, if you update the version and hit Submit, you should see the logs on the console, as follows:

Figure 3.6 – The logs being printed on the console as a fallback to LoggerService not being provided

Figure 3.6 – The logs being printed on the console as a fallback to LoggerService not being provided

Great! We've finished the recipe and everything looks great. Please refer to the next section to understand how it works.

How it works

The @Optional decorator is a special parameter from the @angular/core package, which allows you to mark a parameter for a dependency as optional. Behind the scenes, Angular will provide the value as null when the dependency doesn't exist or is not provided to the app.

See also

Creating a singleton service using providedIn

In this recipe, you'll learn several tips on how to ensure your Angular service is being used as a singleton. This means that there will only be one instance of your service in the entire application. Here, we'll use a couple of techniques, including the providedIn: 'root' statement and making sure we only provide the service once in the entire app by using the @Optional() and @SkipSelf() decorators.

Getting ready

The project for this recipe resides in the chapter03/start_here/ng-singleton-service path. Perform the following steps:

  1. Open the project in Visual Studio Code.
  2. Open the Terminal, and run npm install to install the dependencies of the project.
  3. Once done, run ng serve -o.

    This should open the app in a new browser tab. You should see something similar to the following screenshot:

Figure 3.7 – The ng-singleton-service app running on http://localhost:4200

Figure 3.7 – The ng-singleton-service app running on http://localhost:4200

Now that you have your app running, let's see move ahead and look at the steps of this recipe.

How to do it

The problem with the app is that if you add or remove any notifications, the count on the bell icon in the header does not change. That's due to us having multiple instances of NotificationsService. Please refer to the following steps to ensure we only have a single instance of the service in the app:

  1. Firstly, as Angular developers, we already know that we can use providedIn: 'root' for a service to tell Angular that it is only provided in the root module, and it should only have one instance in the entire app. So, let's go to notifications.service.ts and pass providedIn: 'root' in the @Injectable decorator parameters, as follows:

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

    import { BehaviorSubject, Observable } from 'rxjs';

    @Injectable({

      providedIn: 'root'

    })

    export class NotificationsService {

      ...

    }

    Great! Now even if you refresh and try adding or removing notifications, you'll still see that the count in the header doesn't change. "But why is this, Ahsan?" Well, I'm glad you asked. That's because we're still providing the service in AppModule as well as in VersioningModule.

  2. First, let's remove NotificationsService from the providers array in app.module.ts, as highlighted in the following code block:

    ...

    import { NotificationsButtonComponent } from './components/notifications-button/notifications-button.component';

    import { NotificationsService } from './services/notifications.service'; ← Remove this

    @NgModule({

      declarations: [... ],

      imports: [...],

      providers: [

        NotificationsService ← Remove this

      ],

      bootstrap: [AppComponent]

    })

    export class AppModule { }

  3. Now, we'll remove NotificationsService from versioning.module.ts, as highlighted in the following code block:

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

    import { CommonModule } from '@angular/common';

    import { VersioningRoutingModule } from './versioning-routing.module';

    import { VersioningComponent } from './versioning.component';

    import { NotificationsManagerComponent } from './components/notifications-manager/notifications-manager.component';

    import { NotificationsService } from '../services/notifications.service'; ← Remove this

    @NgModule({

      declarations: [VersioningComponent,   NotificationsManagerComponent],

      imports: [

        CommonModule,

        VersioningRoutingModule,

      ],

      providers: [

        NotificationsService  ← Remove this

      ]

    })

    export class VersioningModule { }

    Awesome! Now you should be able to see the count in the header change according to whether you add/remove notifications. However, what happens if someone still provides it in another lazily loaded module by mistake?

  4. Let's put NotificationsService back in the versioning.module.ts file:

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

    import { CommonModule } from '@angular/common';

    import { VersioningRoutingModule } from './versioning-routing.module';

    import { VersioningComponent } from './versioning.component';

    import { NotificationsManagerComponent } from './components/notifications-manager/notifications-manager.component';

    import { NotificationsService } from '../services/notifications.service';

    @NgModule({

      declarations: [VersioningComponent,   NotificationsManagerComponent],

      imports: [

        CommonModule,

        VersioningRoutingModule,

      ],

      providers: [

        NotificationsService

      ]

    })

    export class VersioningModule { }

    Boom! We don't have any errors on the console or during compile time. However, we do have the issue of the count not updating in the header. So, how do we alert the developers if they make such a mistake? Please refer to the next step.

  5. In order to alert the developer about potential duplicate providers, use the @SkipSelf decorator from the @angular/core package in our NotificationsService, and throw an error to notify and modify NotificationsService, as follows:

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

    ...

    export class NotificationsService {

      ...

      constructor(@SkipSelf() existingService:   NotificationsService) {

        if (existingService) {

          throw Error ('The service has already been provided       in the app. Avoid providing it again in child       modules');

        }

      }

      ...

    }

    With the previous step now complete, you'll notice that we have a problem. That is we have failed to provide NotificationsService to our app at all. You should see this in the console:

    Figure 3.8 – An error detailing that NotificationsService can't be injected into NotificationsService

    Figure 3.8 – An error detailing that NotificationsService can't be injected into NotificationsService

    The reason for this is that NotificationsService is now a dependency of NotificationsService itself. This can't work as it has not already been resolved by Angular. To fix this, we'll also use the @Optional() decorator in the next step.

  6. All right, now we'll use the @Optional() decorator in notifications.service.ts, which is in the constructor for the dependency alongside the @SkipSelf decorator. The code should appear as follows:

    import { Injectable, Optional, SkipSelf } from '@angular/core';

    ...

    export class NotificationsService {

      ...

      constructor(@Optional() @SkipSelf() existingService:   NotificationsService) {

        if (existingService) {

          throw Error ('The service has already been provided       in the app. Avoid providing it again in child       modules');

        }

      }

      ...

    }

    We have now fixed the NotificationsService -> NotificationsService dependency issue. You should see the proper error for the NotificationsService being provided multiple times in the console, as follows:

    Figure 3.9 – An error detailing that NotificationsService is already provided in the app

    Figure 3.9 – An error detailing that NotificationsService is already provided in the app

  7. Now, we'll safely remove the provided NotificationsService from the providers array in the versioning.module.ts file and check whether the app is working correctly:

    ...

    import { NotificationsManagerComponent } from './components/notifications-manager/notifications-manager.component';

    import { NotificationsService } from '../services/notifications.service'; ← Remove this

    @NgModule({

      declarations: [...],

      imports: [...],

      providers: [

        NotificationsService ← Remove this

      ]

    })

    export class VersioningModule { }

Bam! We now have a singleton service using the providedIn strategy. In the next section, let's discuss how it works.

How it works

Whenever we try to inject a service somewhere, by default, it tries to find a service inside the associated module of where you're injecting the service. When we use providedIn: 'root' to declare a service, whenever the service is injected anywhere in the app, Angular knows that it simply has to find the service definition in the root module and not in the feature modules or anywhere else.

However, you have to make sure that the service is only provided once in the entire application. If you provide it in multiple modules, then even with providedIn: 'root', you'll have multiple instances of the service. To avoid providing a service in multiple modules or at multiple places in the app, we can use the @SkipSelf() decorator with the @Optional() decorator in the services' constructor to check whether the service has already been provided in the app.

See also

Creating a singleton service using forRoot()

In this recipe, you'll learn how to use ModuleWithProviders and the forRoot() statement to ensure your Angular service is being used as a singleton in the entire app. We'll start with an app that has multiple instances of NotificationsService, and we'll implement the necessary code to make sure we end up with a single instance of the app.

Getting ready

The project for this recipe resides in the chapter03/start_here/ng-singleton-service-forroot path. Perform the following steps:

  1. Open the project in Visual Studio Code.
  2. Open the Terminal, and run npm install to install the dependencies of the project.
  3. Once done, run ng serve -o.

    This should open the app in a new browser tab. The app should appear as follows:

Figure 3.10 – The ng-singleton-service-forroot app running on http://localhost:4200

Figure 3.10 – The ng-singleton-service-forroot app running on http://localhost:4200

Now that we have the app running, in the next section, we can move on to the steps for the recipe.

How to do it

In order to make sure we only have a singleton service in the app with the forRoot() method, you need to understand how ModuleWithProviders and the static forRoot() method are created and implemented. Perform the following steps:

  1. First, we'll make sure that the service has its own module. In many Angular applications, you'll probably see CoreModule where the services are provided (given we're not using the providedIn: 'root' syntax for some reason). To begin, we'll create a module, named ServicesModule, using the following command:

    ng g m services

  2. Now that we have created the module, let's create a static method inside the services.module.ts file. We'll name the method forRoot and return a ModuleWithProviders object that contains the NotificationsService provided in the providers array, as follows:

    import { ModuleWithProviders, NgModule } from '@angular/core';

    import { CommonModule } from '@angular/common';

    import { NotificationsService } from '../services/notifications.service';

    @NgModule({

        ...

    })

    export class ServicesModule {

      static forRoot(): ModuleWithProviders<ServicesModule> {

        return {

          ngModule: ServicesModule,

          providers: [

            NotificationsService

          ]

        };

      }

    }

  3. Now we'll remove the NotificationsService from the app.module.ts file's imports array and include ServicesModule in the app.module.ts file; in particular, we'll add in the imports array using the forRoot() method, as highlighted in the following code block.

    This is because it injects ServicesModule with the providers in AppModule, for instance, with the NotificationsService being provided as follows:

    import { BrowserModule } from '@angular/platform-browser';

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

    import { AppRoutingModule } from './app-routing.module';

    import { AppComponent } from './app.component';

    import { NotificationsButtonComponent } from './components/notifications-button/notifications-button.component';

    import { NotificationsService } from './services/notifications.service'; ← Remove this

    import { ServicesModule } from './services/services.module';

    @NgModule({

      declarations: [

        AppComponent,

        NotificationsButtonComponent

      ],

      imports: [

        BrowserModule,

        AppRoutingModule,

        ServicesModule.forRoot()

      ],

      providers: [

        NotificationsService ← Remove this

      ],

      bootstrap: [AppComponent]

    })

    export class AppModule { }

    You'll notice that when adding/removing notifications, the count in the header still doesn't change. This is because we're still providing the NotificationsService in the versioning.module.ts file.

  4. We'll remove the NotificationsService from the providers array in the versioning.module.ts file, as follows:

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

    import { CommonModule } from '@angular/common';

    import { VersioningRoutingModule } from './versioning-routing.module';

    import { VersioningComponent } from './versioning.component';

    import { NotificationsManagerComponent } from './components/notifications-manager/notifications-manager.component';

    import { NotificationsService } from '../services/notifications.service'; Remove

    @NgModule({

      declarations: [VersioningComponent,   NotificationsManagerComponent],

      imports: [

        CommonModule,

        VersioningRoutingModule,

      ],

      providers: [

        NotificationsService ← Remove

      ]

    })

    export class VersioningModule { }

All right, so far, you've done a great job. Now that we have finished the recipe, in the next section, let's discuss how it works.

How it works

ModuleWithProviders is a wrapper around NgModule, which is associated with the providers array that is used in NgModule. It allows you to declare NgModule with providers, so the module where it is being imported gets the providers as well. We created a forRoot() method in our ServicesModule class that returns ModuleWithProviders containing our provided NotificationsService. This allows us to provide NotificationsService only once in the entire app, which results in only one instance of the service in the app.

See also

Providing different services to the app with the same Aliased class provider

In this recipe, you'll learn how to provide two different services to the app using Aliased class providers. This is extremely helpful in complex applications where you need to narrow down the implementation of the base class for some components/modules. Additionally, aliasing is used in component/service unit tests to mock the dependent service's actual implementation so that we don't rely on it.

Getting ready

The project that we are going to work with resides in the chapter03/start_here/ng-aliased-class-providers path, which is inside the cloned repository. Perform the following steps:

  1. Open the project in Visual Studio Code.
  2. Open the Terminal and run npm install to install the dependencies of the project.
  3. Once done, run ng serve -o.

    This should open the app in a new browser tab.

  4. Click on the Login as Admin button. You should see something similar to the following screenshot:
Figure 3.11 – The ng-aliased-class-providers app running on http://localhost:4200

Figure 3.11 – The ng-aliased-class-providers app running on http://localhost:4200

Now that we have the app running, let's move to the next section to follow the steps for the recipe.

How to do it

We have a shared component named BucketComponent, which is being used in both the admin and employee modules. BucketComponent uses BucketService behind the scenes to add/remove items from and to a bucket. For the employee, we'll restrict the the ability to remove an item by providing an aliased class provider and a different EmployeeBucketService. This is so that we can override the remove item functionality. Perform the following steps:

  1. We'll start by creating EmployeeBucketService within the employee folder, as follows:

    ng g service employee/services/employee-bucket

  2. Next, we'll extend EmployeeBucketService from BucketService so that we get all the goodness of BucketService. Let's modify the code as follows:

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

    import { BucketService } from 'src/app/services/bucket.service';

    @Injectable({

      providedIn: 'root'

    })

    export class EmployeeBucketService extends BucketService {

      constructor() {

        super();

      }

    }

  3. We will now override the removeItem() method to simply display a simple alert() mentioning that the employees can't remove items from the bucket. Your code should appear as follows:

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

    import { BucketService } from 'src/app/services/bucket.service';

    @Injectable({

      providedIn: 'root'

    })

    export class EmployeeBucketService extends BucketService {

      constructor() {

        super();

      }

      removeItem() {

        alert('Employees can not delete items');

      }

    }

  4. As a final step, we need to provide the aliased class provider to the employee.module.ts file, as follows:

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

    ...

    import { BucketService } from '../services/bucket.service';

    import { EmployeeBucketService } from './services/employee-bucket.service';

    @NgModule({

      declarations: [...],

      imports: [

       ...

      ],

      providers: [{

        provide: BucketService,

        useClass: EmployeeBucketService

      }]

    })

    export class EmployeeModule { }

If you now log in as an employee in the app and try to remove an item, you'll see an alert pop up, which says Employees cannot delete items.

How it works

When we inject a service into a component, Angular tries to find that component from the injected place by moving up the hierarchy of components and modules. Our BucketService is provided in 'root' using the providedIn: 'root' syntax. Therefore, it resides at the top of the hierarchy. However, since, in this recipe, we use an aliased class provider in EmployeeModule, when Angular searches for BucketService, it quickly finds it inside EmployeeModule and stops there before it even reaches 'root' to get the actual BucketService.

See also

Value providers in Angular

In this recipe, you'll learn how to use value providers in Angular to provide constants and config values to your app. We'll start with the same example from the previous recipe, that is, EmployeeModule and AdminModule using the shared component named BucketComponent. We will restrict the employee from deleting items from the bucket by using a value provider, so the employees won't even see the delete button.

Getting ready

The project that we are going to work with resides in the chapter03/start_here/ng-value-providers path, which is inside the cloned repository. Perform the following steps:

  1. Open the project in Visual Studio Code.
  2. Open the Terminal, and run npm install to install the dependencies of the project.
  3. Once done, run ng serve -o .

    This should open the app in a new browser tab.

  4. Click on the Login as Admin button. You should see something similar to the following screenshot:
Figure 3.12 – The ng-value-providers app running on http://localhost:4200

Figure 3.12 – The ng-value-providers app running on http://localhost:4200

We have a shared component, named BucketComponent, that is being used in both the admin and employee modules. For the employee, we'll restrict the ability to remove an item by providing a value provider in EmployeeModule. This is so that we can hide the delete button based on its value.

How to do it

  1. First, we'll start by creating the value provider with InjectionToken within a new file, named app-config.ts, inside the app/constants folder. The code should appear as follows:

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

    export interface IAppConfig {

      canDeleteItems: boolean;

    }

    export const APP_CONFIG = new InjectionToken<IAppConfig>('APP_CONFIG');

    export const AppConfig: IAppConfig = {

      canDeleteItems: true

    }

    Before we can actually use this AppConfig constant in our BucketComponent, we need to register it to the AppModule so that when we inject this in the BucketComponent, the value of the provider is resolved.

  2. Let's add the provider to the app.module.ts file, as follows:

    ...

    import { AppConfig, APP_CONFIG } from './constants/app-config';

    @NgModule({

      declarations: [

        AppComponent

      ],

      imports: [

        ...

      ],

      providers: [{

        provide: APP_CONFIG,

        useValue: AppConfig

      }],

      bootstrap: [AppComponent]

    })

    export class AppModule { }

    Now the app knows about the AppConfig constants. The next step is to use this constant in BucketComponent.

  3. We'll use the @Inject() decorator to inject it inside the BucketComponent class, in the shared/components/bucket/bucket.component.ts file, as follows:

    import { Component, Inject, OnInit } from '@angular/core';

    ...

    import { IAppConfig, APP_CONFIG } from '../../../constants/app-config';

    ...

    export class BucketComponent implements OnInit {

      ...

      constructor(private bucketService: BucketService,   @Inject(APP_CONFIG) private config: IAppConfig) { }

      ...

    }

    Great! The constant has been injected. Now, if you refresh the app, you shouldn't get any errors. The next step is to use the canDeleteItems property from config in BucketComponent to show/hide the delete button.

  4. We'll first add the property to the shared/components/bucket/bucket.component.ts file and assign it to the ngOnInit() method, as follows:

    ...

    export class BucketComponent implements OnInit {

      $bucket: Observable<IFruit[]>;

      selectedFruit: Fruit = '' as null;

      fruits: string[] = Object.values(Fruit);

      canDeleteItems: boolean;

      constructor(private bucketService: BucketService,   @Inject(APP_CONFIG) private config: IAppConfig) { }

      ngOnInit(): void {

        this.$bucket = this.bucketService.$bucket;

        this.bucketService.loadItems();

        this.canDeleteItems = this.config.canDeleteItems;

      }

      ...

    }

  5. Now, we'll add an *ngIf directive in the shared/components/bucket/ bucket.component.html file to only show the delete button if the value of canDeleteItems is true:

    <div class="buckets" *ngIf="$bucket | async as bucket">

      <h4>Bucket <i class="material-icons">shopping_cart   </i></h4>

      <div class="add-section">

        ...

      </div>

      <div class="fruits">

        <ng-container *ngIf="bucket.length > 0; else     bucketEmptyMessage">

          <div class="fruits__item" *ngFor="let item of       bucket;">

            <div class="fruits__item__title">{{item.name}}        </div>

            <div *ngIf="canDeleteItems" class="fruits__        item__delete-icon"         (click)="deleteFromBucket(item)">

              <div class="material-icons">delete</div>

            </div>

          </div>

        </ng-container>

      </div>

    </div>

    <ng-template #bucketEmptyMessage>

      ...

    </ng-template>

    You can test whether everything works by setting the AppConfig constant's canDeleteItems property to false. Note that the delete button is now hidden for both the admin and employee. Once tested, set the value of canDeleteItems back to true again.

    Now we have everything set up. Let's add a new constant so that we can hide the delete button for the employee only.

  6. We'll create a folder, named constants, inside the employee folder. Then, we'll create a new file underneath the employee/constants path, called employee-config.ts, and we will add the following code to it:

    import { IAppConfig } from '../../constants/app-config';

    export const EmployeeConfig: IAppConfig = {

      canDeleteItems: false

    }

  7. Now, we'll provide this EmployeeConfig constant to the EmployeeModule for the same APP_CONFIG injection token. The code in the employee.module.ts file should appear as follows:

    ...

    import { EmployeeComponent } from './employee.component';

    import { APP_CONFIG } from '../constants/app-config';

    import { EmployeeConfig } from './constants/employee-config';

    @NgModule({

      declarations: [EmployeeComponent],

      imports: [

        ...

      ],

      providers: [{

        provide: APP_CONFIG,

        useValue: EmployeeConfig

      }]

    })

    export class EmployeeModule { }

And we're done! The recipe is now complete. You can see that the delete button is visible to the admin but hidden for the employee. It's all thanks to the magic of value providers.

How it works

When we inject a token into a component, Angular tries to find the resolved value of the token from the injected place by moving up the hierarchy of components and modules. We provided EmployeeConfig as APP_CONFIG in EmployeeModule. When Angular tries to resolve its value for BucketComponent, it finds it early at EmployeeModule as EmployeeConfig. Therefore, Angular stops right there and doesn't reach AppComponent. Notice that the value for APP_CONFIG in AppComponent is the AppConfig constant.

See also

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
44.200.39.110