© Adam Freeman 2018
Adam FreemanPro Angular 6https://doi.org/10.1007/978-1-4842-3649-9_20

20. Using Service Providers

Adam Freeman1 
(1)
London, UK
 

In the previous chapter, I introduced services and explained how they are distributed using dependency injection. When using dependency injection, the objects that are used to resolve dependencies are created by service providers, known more commonly as providers. In this chapter, I explain how providers work, describe the different types of provider available, and demonstrate how providers can be created in different parts of the application to change the way that services behave. Table 20-1 puts providers in context.

Why You Should Consider Skipping This Chapter

Dependency injection provokes strong reactions in developers and polarizes opinion. If you are new to dependency injection and have yet to form your own opinion, then you might want to skip this chapter and just use the features that I described in Chapter 19. That’s because features like the ones I describe in this chapter are exactly why many developers dread using dependency injection and form a strong preference against its use.

The basic Angular dependency injection features are easy to understand and have an immediate and obvious benefit in making applications easier to write and maintain. The features described in this chapter provide fine-grained control over how dependency injection works, but they also make it possible to sharply increase the complexity of an Angular application and, ultimately, undermine many of the benefits that the basic features offer.

If you decide that you want all of the gritty detail, then read on. But if you are new to the world of dependency injection, you may prefer to skip this chapter until you find that the basic features from Chapter 19 don’t deliver the functionality you require.

Table 20-1

Putting Service Providers in Context

Question

Answer

What are they?

Providers are classes that create service objects the first time that Angular needs to resolve a dependency.

Why are they useful?

Providers allow the creation of service objects to be tailored to the needs of the application. The simplest provider just creates an instance of a specified class, but there are other providers that can be used to tailor the way that service objects are created and configured.

How are they used?

Providers are defined in the providers property of the Angular module’s decorator. They can also be defined by components and directives to provide services to their children, as described in the “Using Local Providers” section.

Are there any pitfalls or limitations?

It is easy to create unexpected behavior, especially when working with local providers. If you encounter problems, check the scope of the local providers you have created and make sure that your dependencies and providers are using the same tokens.

Are there any alternatives?

Many applications will require only the basic dependency injection features described in Chapter 19. You should use the features in this chapter only if you cannot build your application using the basic features and only if you have a solid understanding of dependency injection.

Table 20-2 summarizes the chapter.
Table 20-2

Chapter Summary

Problem

Solution

Listing

Change the way that services are created

Use a service provider

1–3

Specify a service using a class

Use the class provider

4–6, 10–13

Define arbitrary tokens for services

Use the InjectionToken class

7–9

Specify a service using an object

Use the value provider

14–15

Specify a service using a function

Use the factory provider

16–18

Specify one service using another

Use the existing service provider

19

Change the scope of a service

Use a local service provider

20–28

Control the resolution of dependencies

Use the @Host, @Optional, or @SkipSelf decorator

29–30

Preparing the Example Project

As with the other chapters in this part of the book, I am going to continue working with the project created in Chapter 11 and most recently modified in Chapter 19. To prepare for this chapter, I added a file called log.service.ts to the src/app folder and used it to define the service shown in Listing 20-1.

Tip

You can download the example project for this chapter—and for all the other chapters in this book—from https://github.com/Apress/pro-angular-6 .

import { Injectable } from "@angular/core";
export enum LogLevel {
    DEBUG, INFO, ERROR
}
@Injectable()
export class LogService {
    minimumLevel: LogLevel = LogLevel.INFO;
    logInfoMessage(message: string) {
        this.logMessage(LogLevel.INFO, message);
    }
    logDebugMessage(message: string) {
        this.logMessage(LogLevel.DEBUG, message);
    }
    logErrorMessage(message: string) {
        this.logMessage(LogLevel.ERROR, message);
    }
    logMessage(level: LogLevel, message: string) {
        if (level >= this.minimumLevel) {
            console.log(`Message (${LogLevel[level]}): ${message}`);
        }
    }
}
Listing 20-1

The Contents of the log.service.ts File in the src/app Folder

This service writes out log messages, with differing levels of severity, to the browser’s JavaScript console. I will register and use this service later in the chapter.

When you have created the service and saved the changes, run the following command in the example folder to start the Angular development tools:
ng serve
Open a new browser window and navigate to http://localhost:4200 to see the application, as shown in Figure 20-1.
../images/421542_3_En_20_Chapter/421542_3_En_20_Fig1_HTML.jpg
Figure 20-1

Running the example application

Using Service Providers

As I explained in the previous chapters, classes declare dependencies on services using their constructor arguments. When Angular needs to create a new instance of the class, it inspects the constructor and uses a combination of built-in and custom services to resolve each argument. Listing 20-2 updates the DiscountService class so that it depends on the LogService class created in the previous section.
import { Injectable } from "@angular/core";
import { LogService } from "./log.service";
@Injectable()
export class DiscountService {
    private discountValue: number = 10;
    constructor(private logger: LogService) { }
    public get discount(): number {
        return this.discountValue;
    }
    public set discount(newValue: number) {
        this.discountValue = newValue || 0;
    }
    public applyDiscount(price: number) {
        this.logger.logInfoMessage(`Discount ${this.discount}`
            + ` applied to price: ${price}`);
        return Math.max(price - this.discountValue, 5);
    }
}
Listing 20-2

Creating a Dependency in the discount.service.ts File in the src/app Folder

The changes in Listing 20-2 prevent the application from running. Angular processes the HTML document and starts creating the hierarchy of components, each with their templates that require directives and data bindings, and it encounters the classes that depend on the DiscountService class. But it can’t create an instance of DiscountService because its constructor requires a LogService object and it doesn’t know how to handle this class.

When you save the changes in Listing 20-2, you will see an error like this one in the browser’s JavaScript console:
NullInjectorError: No provider for LogService!

Angular delegates responsibility for creating the objects needed for dependency injection to providers, each of which managed a single type of dependency. When it needs to create an instance of the DiscountService class, it looks for a suitable provider to resolve the LogService dependency. Since there is no such provider, Angular can’t create the objects it needs to start the application and reports the error.

The simplest way to create a provider is to add the service class to the array assigned to the Angular module’s providers property, as shown in Listing 20-3. (I have taken the opportunity to remove some of the statements that are no longer required in the module.)
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { ProductComponent } from "./component";
import { FormsModule, ReactiveFormsModule  } from "@angular/forms";
import { PaAttrDirective } from "./attr.directive";
import { PaModel } from "./twoway.directive";
import { PaStructureDirective } from "./structure.directive";
import { PaIteratorDirective } from "./iterator.directive";
import { PaCellColor } from "./cellColor.directive";
import { PaCellColorSwitcher } from "./cellColorSwitcher.directive";
import { ProductTableComponent } from "./productTable.component";
import { ProductFormComponent } from "./productForm.component";
import { PaAddTaxPipe } from "./addTax.pipe";
import { PaCategoryFilterPipe } from "./categoryFilter.pipe";
import { PaDiscountDisplayComponent } from "./discountDisplay.component";
import { PaDiscountEditorComponent } from "./discountEditor.component";
import { DiscountService } from "./discount.service";
import { PaDiscountPipe } from "./discount.pipe";
import { PaDiscountAmountDirective } from "./discountAmount.directive";
import { SimpleDataSource } from "./datasource.model";
import { Model } from "./repository.model";
import { LogService } from "./log.service";
@NgModule({
    imports: [BrowserModule, FormsModule, ReactiveFormsModule],
    declarations: [ProductComponent, PaAttrDirective, PaModel,
        PaStructureDirective, PaIteratorDirective,
        PaCellColor, PaCellColorSwitcher, ProductTableComponent,
        ProductFormComponent, PaAddTaxPipe, PaCategoryFilterPipe,
        PaDiscountDisplayComponent, PaDiscountEditorComponent,
        PaDiscountPipe, PaDiscountAmountDirective],
    providers: [DiscountService, SimpleDataSource, Model, LogService],
    bootstrap: [ProductComponent]
})
export class AppModule { }
Listing 20-3

Creating a Provider in the app.module.ts File in the src/app Folder

When you save the changes, you will have defined the provider that Angular requires to handle the LogService dependency, and you will see messages like this one shown in the browser’s JavaScript console:
Message (INFO): Discount 10 applied to price: 16

You might wonder why the configuration step in Listing 20-3 is required. After all, Angular could just assume that it should create a new LogService object the first time it needs one.

In fact, Angular provides a range of different providers, each of which creates objects in a different way to let you take control of the service creation process. Table 20-3 describes the set of providers that are available, which are described in the sections that follow.
Table 20-3

The Angular Providers

Name

Description

Class provider

This provider is configured using a class. Dependencies on the service are resolved by an instance of the class, which Angular creates.

Value provider

This provider is configured using an object, which is used to resolve dependencies on the service.

Factory provider

This provider is configured using a function. Dependencies on the service are resolved using an object that is created by invoking the function.

Existing service provider

This provider is configured using the name of another service and allows aliases for services to be created.

Using the Class Provider

This provider is the most commonly used and is the one I applied by adding the class names to the module’s providers property in Listing 20-3. This listing shows the shorthand syntax, and there is also a literal syntax that achieves the same result, as shown in Listing 20-4.
...
@NgModule({
    imports: [BrowserModule, FormsModule, ReactiveFormsModule],
    declarations: [ProductComponent, PaAttrDirective, PaModel,
        PaStructureDirective, PaIteratorDirective,
        PaCellColor, PaCellColorSwitcher, ProductTableComponent,
        ProductFormComponent, PaAddTaxPipe, PaCategoryFilterPipe,
        PaDiscountDisplayComponent, PaDiscountEditorComponent,
        PaDiscountPipe, PaDiscountAmountDirective],
    providers: [DiscountService, SimpleDataSource, Model,
               { provide: LogService, useClass: LogService }],
    bootstrap: [ProductComponent]
})
...
Listing 20-4

Using the Class Provider Literal Syntax in the app.module.ts File in the src/app Folder

Providers are defined as classes, but they can be specified and configured using the JavaScript object literal format, like this:
...
{
    provide: LogService,
    useClass: LogService
}
...
The class provider supports three properties, which are described in Table 20-4 and explained in the sections that follow.
Table 20-4

The Class Provider’s Properties

Name

Description

provide

This property is used to specify the token, which is used to identify the provider and the dependency that will be resolved. See the “Understanding the Token” section.

useClass

This property is used to specify the class that will be instantiated to resolve the dependency by the provider. See the “Understanding the useClass Property” section.

multi

This property can be used to deliver an array of service objects to resolve dependencies. See the “Resolving a Dependency with Multiple Objects” section.

Understanding the Token

All providers rely on a token, which Angular uses to identify the dependency that the provider can resolve. The simplest approach is to use a class as the token, which is what I did in Listing 20-4. However, you can use any object as the token, which allows the dependency and the type of the object to be separated. This has the effect of increasing the flexibility of the dependency injection configuration because it allows a provider to supply objects of different types, which can be useful with some of the more advanced providers described later in the chapter. As a simple example, Listing 20-5 uses the class provider to register the log service created at the start of the chapter using a string as a token, rather than a class.
...
@NgModule({
    imports: [BrowserModule, FormsModule, ReactiveFormsModule],
    declarations: [ProductComponent, PaAttrDirective, PaModel,
        PaStructureDirective, PaIteratorDirective,
        PaCellColor, PaCellColorSwitcher, ProductTableComponent,
        ProductFormComponent, PaAddTaxPipe, PaCategoryFilterPipe,
        PaDiscountDisplayComponent, PaDiscountEditorComponent,
        PaDiscountPipe, PaDiscountAmountDirective],
    providers: [DiscountService, SimpleDataSource, Model,
                { provide: "logger", useClass: LogService }],
    bootstrap: [ProductComponent]
})
...
Listing 20-5

Registering a Service with a Token in the app.module.ts File in the src/app Folder

In the listing the provide property of the new provider is set to logger. Angular will automatically match providers whose token is a class but needs some additional help for other token types. Listing 20-6 shows the DiscountService class updated with a dependency on the logging service, accessed using the logger token.
import { Injectable, Inject } from "@angular/core";
import { LogService } from "./log.service";
@Injectable()
export class DiscountService {
    private discountValue: number = 10;
    constructor(@Inject("logger") private logger: LogService) { }
    public get discount(): number {
        return this.discountValue;
    }
    public set discount(newValue: number) {
        this.discountValue = newValue || 0;
    }
    public applyDiscount(price: number) {
        this.logger.logInfoMessage(`Discount ${this.discount}`
            + ` applied to price: ${price}`);
        return Math.max(price - this.discountValue, 5);
    }
}
Listing 20-6

Using a String Provider Token in the discount.service.ts File in the src/app Folder

The @Inject decorator is applied to the constructor argument and used to specify the token that should be used to resolve the dependency. When Angular needs to create an instance of the DiscountService class, it will inspect the constructor and use the @Inject decorator argument to select the provider that will be used to resolve the dependency, resolving the dependency on the LogService class.

Using Opaque Tokens

When using simple types as provider tokens, there is a chance that two different parts of the application will try to use the same token to identify different services, which means that the wrong type of object may be used to resolve dependencies and cause errors.

To help work around this, Angular provides the InjectionToken class, which provides an object wrapper around a string value and can be used to create unique token values. In Listing 20-7, I have used the InjectionToken class to create a token that will be used to identify dependencies on the LogService class.
import { Injectable, InjectionToken } from "@angular/core";
export const LOG_SERVICE = new InjectionToken("logger");
export enum LogLevel {
    DEBUG, INFO, ERROR
}
@Injectable()
export class LogService {
    minimumLevel: LogLevel = LogLevel.INFO;
    // ...methods omitted for brevity...
}
Listing 20-7

Using the InjectionToken Class in the log.service.ts File in the src/app Folder

The constructor for the InjectionToken class accepts a string value that describes the service, but it is the InjectionToken object that will be the token. Dependencies must be declared on the same InjectionToken that is used to create the provider in the module, which is why the token has been created using the const keyword, which prevents the object from being modified. Listing 20-8 shows the provider configuration using the new token.
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { ProductComponent } from "./component";
import { FormsModule, ReactiveFormsModule  } from "@angular/forms";
import { PaAttrDirective } from "./attr.directive";
import { PaModel } from "./twoway.directive";
import { PaStructureDirective } from "./structure.directive";
import { PaIteratorDirective } from "./iterator.directive";
import { PaCellColor } from "./cellColor.directive";
import { PaCellColorSwitcher } from "./cellColorSwitcher.directive";
import { ProductTableComponent } from "./productTable.component";
import { ProductFormComponent } from "./productForm.component";
import { PaAddTaxPipe } from "./addTax.pipe";
import { PaCategoryFilterPipe } from "./categoryFilter.pipe";
import { PaDiscountDisplayComponent } from "./discountDisplay.component";
import { PaDiscountEditorComponent } from "./discountEditor.component";
import { DiscountService } from "./discount.service";
import { PaDiscountPipe } from "./discount.pipe";
import { PaDiscountAmountDirective } from "./discountAmount.directive";
import { SimpleDataSource } from "./datasource.model";
import { Model } from "./repository.model";
import { LogService, LOG_SERVICE } from "./log.service";
@NgModule({
    imports: [BrowserModule, FormsModule, ReactiveFormsModule],
    declarations: [ProductComponent, PaAttrDirective, PaModel,
        PaStructureDirective, PaIteratorDirective,
        PaCellColor, PaCellColorSwitcher, ProductTableComponent,
        ProductFormComponent, PaAddTaxPipe, PaCategoryFilterPipe,
        PaDiscountDisplayComponent, PaDiscountEditorComponent,
        PaDiscountPipe, PaDiscountAmountDirective],
    providers: [DiscountService, SimpleDataSource, Model,
        { provide: LOG_SERVICE, useClass: LogService }],
    bootstrap: [ProductComponent]
})
export class AppModule { }
Listing 20-8

Creating a Provider Using an InjectionToken in the app.module.ts File in the src/app Folder

Finally, Listing 20-9 shows the DiscountService class updated to declare a dependency using the InjectionToken instead of a string.
import { Injectable, Inject } from "@angular/core";
import { LogService, LOG_SERVICE } from "./log.service";
@Injectable()
export class DiscountService {
    private discountValue: number = 10;
    constructor( @Inject(LOG_SERVICE) private logger: LogService) { }
    public get discount(): number {
        return this.discountValue;
    }
    public set discount(newValue: number) {
        this.discountValue = newValue || 0;
    }
    public applyDiscount(price: number) {
        this.logger.logInfoMessage(`Discount ${this.discount}`
            + ` applied to price: ${price}`);
        return Math.max(price - this.discountValue, 5);
    }
}
Listing 20-9

Declaring a Dependency in the discount.service.ts File in the src/app Folder

There is no difference in the functionality offered by the application, but using the InjectionToken means that there will be no confusion between services.

Understanding the useClass Property

The class provider’s useClass property specifies the class that will be instantiated to resolve dependencies. The provider can be configured with any class, which means you can change the implementation of a service by changing the provider configuration. This feature should be used with caution because the recipients of the service object will be expecting a specific type and a mismatch won’t result in an error until the application is running in the browser. (TypeScript type enforcement has no effect on dependency injection because it occurs at runtime after the type annotations have been processed by the TypeScript compiler.)

The most common way to change classes is to use different subclasses. In Listing 20-10, I extended the LogService class to create a service that writes a different format of message in the browser’s JavaScript console.
import { Injectable, InjectionToken } from "@angular/core";
export const LOG_SERVICE = new InjectionToken("logger");
export enum LogLevel {
    DEBUG, INFO, ERROR
}
@Injectable()
export class LogService {
    minimumLevel: LogLevel = LogLevel.INFO;
    logInfoMessage(message: string) {
        this.logMessage(LogLevel.INFO, message);
    }
    logDebugMessage(message: string) {
        this.logMessage(LogLevel.DEBUG, message);
    }
    logErrorMessage(message: string) {
        this.logMessage(LogLevel.ERROR, message);
    }
    logMessage(level: LogLevel, message: string) {
        if (level >= this.minimumLevel) {
            console.log(`Message (${LogLevel[level]}): ${message}`);
        }
    }
}
@Injectable()
export class SpecialLogService extends LogService {
    constructor() {
        super()
        this.minimumLevel = LogLevel.DEBUG;
    }
    logMessage(level: LogLevel, message: string) {
        if (level >= this.minimumLevel) {
            console.log(`Special Message (${LogLevel[level]}): ${message}`);
        }
    }
}
Listing 20-10

Creating a Subclassed Service in the log.service.ts File in the src/app Folder

The SpecialLogService class extends LogService and provides its own implementation of the logMessage method. Listing 20-11 updates the provider configuration so that the useClass property specifies the new service.
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { ProductComponent } from "./component";
import { FormsModule, ReactiveFormsModule  } from "@angular/forms";
import { PaAttrDirective } from "./attr.directive";
import { PaModel } from "./twoway.directive";
import { PaStructureDirective } from "./structure.directive";
import { PaIteratorDirective } from "./iterator.directive";
import { PaCellColor } from "./cellColor.directive";
import { PaCellColorSwitcher } from "./cellColorSwitcher.directive";
import { ProductTableComponent } from "./productTable.component";
import { ProductFormComponent } from "./productForm.component";
import { PaAddTaxPipe } from "./addTax.pipe";
import { PaCategoryFilterPipe } from "./categoryFilter.pipe";
import { PaDiscountDisplayComponent } from "./discountDisplay.component";
import { PaDiscountEditorComponent } from "./discountEditor.component";
import { DiscountService } from "./discount.service";
import { PaDiscountPipe } from "./discount.pipe";
import { PaDiscountAmountDirective } from "./discountAmount.directive";
import { SimpleDataSource } from "./datasource.model";
import { Model } from "./repository.model";
import { LogService, LOG_SERVICE, SpecialLogService } from "./log.service";
@NgModule({
    imports: [BrowserModule, FormsModule, ReactiveFormsModule],
    declarations: [ProductComponent, PaAttrDirective, PaModel,
        PaStructureDirective, PaIteratorDirective,
        PaCellColor, PaCellColorSwitcher, ProductTableComponent,
        ProductFormComponent, PaAddTaxPipe, PaCategoryFilterPipe,
        PaDiscountDisplayComponent, PaDiscountEditorComponent,
        PaDiscountPipe, PaDiscountAmountDirective],
    providers: [DiscountService, SimpleDataSource, Model,
        { provide: LOG_SERVICE, useClass: SpecialLogService }],
    bootstrap: [ProductComponent]
})
export class AppModule { }
Listing 20-11

Configuring the Provider in the app.module.ts File in the src/app Folder

The combination of token and class means that dependencies on the LOG_SERVICE opaque token will be resolved using a SpecialLogService object. When you save the changes, you will see messages like this one displayed in the browser’s JavaScript console, indicating that the derived service has been used:
Special Message (INFO): Discount 10 applied to price: 275

Care must be taken when setting the useClass property to specify a type that the dependent classes are expecting. Specifying a subclass is the safest option because the functionality of the base class is guaranteed to be available.

Resolving a Dependency with Multiple Objects

The class provider can be configured to deliver an array of objects to resolve a dependency, which can be useful if you want to provide a set of related services that differ in how they are configured. To provide an array, multiple class providers are configured using the same token and with the multi property set as true, as shown in Listing 20-12.
...
@NgModule({
    imports: [BrowserModule, FormsModule, ReactiveFormsModule],
    declarations: [ProductComponent, PaAttrDirective, PaModel,
        PaStructureDirective, PaIteratorDirective,
        PaCellColor, PaCellColorSwitcher, ProductTableComponent,
        ProductFormComponent, PaAddTaxPipe, PaCategoryFilterPipe,
        PaDiscountDisplayComponent, PaDiscountEditorComponent,
        PaDiscountPipe, PaDiscountAmountDirective],
    providers: [DiscountService, SimpleDataSource, Model,
        { provide: LOG_SERVICE, useClass: LogService, multi: true },
        { provide: LOG_SERVICE, useClass: SpecialLogService, multi: true }],
    bootstrap: [ProductComponent]
})
...
Listing 20-12

Configuring Multiple Service Objects in the app.module.ts File in the src/app Folder

The Angular dependency injection system will resolve dependencies on the LOG_SERVICE token by creating LogService and SpecialLogService objects, placing them in an array, and passing them to the dependent class’s constructor. The class that receives the services must be expecting an array, as shown in Listing 20-13.
import { Injectable, Inject } from "@angular/core";
import { LogService, LOG_SERVICE, LogLevel } from "./log.service";
@Injectable()
export class DiscountService {
    private discountValue: number = 10;
    private logger: LogService;
    constructor( @Inject(LOG_SERVICE) loggers: LogService[]) {
        this.logger = loggers.find(l => l.minimumLevel == LogLevel.DEBUG);
    }
    public get discount(): number {
        return this.discountValue;
    }
    public set discount(newValue: number) {
        this.discountValue = newValue || 0;
    }
    public applyDiscount(price: number) {
        this.logger.logInfoMessage(`Discount ${this.discount}`
            + ` applied to price: ${price}`);
        return Math.max(price - this.discountValue, 5);
    }
}
Listing 20-13

Receiving Multiple Services in the discount.service.ts File in the src/app Folder

The services are received as an array by the constructor, which uses the array find method to locate the first logger whose minimumLevel property is LogLevel.Debug and assign it to the logger property. The applyDiscount method calls the service’s logDebugMessage method, which results in messages like this one displayed in the browser’s JavaScript console:
Special Message (INFO): Discount 10 applied to price: 275

Using the Value Provider

The value provider is used when you want to take responsibility for creating the service objects yourself, rather than leaving it to the class provider. This can also be useful when services are simple types, such as string or number values, which can be a useful way of providing access to common configuration settings. The value provider can be applied using a literal object and supports the properties described in Table 20-5.
Table 20-5

The Value Provider Properties

Name

Description

provide

This property defines the service token, as described in the “Understanding the Token” section earlier in the chapter.

useValue

This property specifies the object that will be used to resolve the dependency.

multi

This property is used to allow multiple providers to be combined to provide an array of objects that will be used to resolve a dependency on the token. See the “Resolving a Dependency with Multiple Objects” section earlier in the chapter for an example.

The value provider works in the same way as the class provider except that it is configured with an object rather than a type. Listing 20-14 shows the use of the value provider to create an instance of the LogService class that is configured with a specific property value.
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { ProductComponent } from "./component";
import { FormsModule, ReactiveFormsModule  } from "@angular/forms";
import { PaAttrDirective } from "./attr.directive";
import { PaModel } from "./twoway.directive";
import { PaStructureDirective } from "./structure.directive";
import { PaIteratorDirective } from "./iterator.directive";
import { PaCellColor } from "./cellColor.directive";
import { PaCellColorSwitcher } from "./cellColorSwitcher.directive";
import { ProductTableComponent } from "./productTable.component";
import { ProductFormComponent } from "./productForm.component";
import { PaAddTaxPipe } from "./addTax.pipe";
import { PaCategoryFilterPipe } from "./categoryFilter.pipe";
import { PaDiscountDisplayComponent } from "./discountDisplay.component";
import { PaDiscountEditorComponent } from "./discountEditor.component";
import { DiscountService } from "./discount.service";
import { PaDiscountPipe } from "./discount.pipe";
import { PaDiscountAmountDirective } from "./discountAmount.directive";
import { SimpleDataSource } from "./datasource.model";
import { Model } from "./repository.model";
import { LogService, LOG_SERVICE, SpecialLogService, LogLevel } from "./log.service";
let logger = new LogService();
logger.minimumLevel = LogLevel.DEBUG;
@NgModule({
    imports: [BrowserModule, FormsModule, ReactiveFormsModule],
    declarations: [ProductComponent, PaAttrDirective, PaModel,
        PaStructureDirective, PaIteratorDirective,
        PaCellColor, PaCellColorSwitcher, ProductTableComponent,
        ProductFormComponent, PaAddTaxPipe, PaCategoryFilterPipe,
        PaDiscountDisplayComponent, PaDiscountEditorComponent,
        PaDiscountPipe, PaDiscountAmountDirective],
    providers: [DiscountService, SimpleDataSource, Model,
        { provide: LogService, useValue: logger }],
    bootstrap: [ProductComponent]
})
export class AppModule { }
Listing 20-14

Using the Value Provider in the app.module.ts File in the src/app Folder

This value provider is configured to resolve dependencies on the LogService token with a specific object that has been created and configured outside of the module class.

The value provider—and, in fact, all of the providers—can use any object as the token, as described in the previous section, but I have returned to using types as tokens because it is the most commonly used technique and because it works so nicely with TypeScript constructor parameter typing. Listing 20-15 shows the corresponding change to the DiscountService, which declares a dependency using a typed constructor argument.
import { Injectable, Inject } from "@angular/core";
import { LogService, LOG_SERVICE, LogLevel } from "./log.service";
@Injectable()
export class DiscountService {
    private discountValue: number = 10;
    constructor(private logger: LogService) { }
    public get discount(): number {
        return this.discountValue;
    }
    public set discount(newValue: number) {
        this.discountValue = newValue || 0;
    }
    public applyDiscount(price: number) {
        this.logger.logInfoMessage(`Discount ${this.discount}`
            + ` applied to price: ${price}`);
        return Math.max(price - this.discountValue, 5);
    }
}
Listing 20-15

Declaring a Dependency Using a Type in the discount.service.ts File in the src/app Folder

Using the Factory Provider

The factory provider uses a function to create the object required to resolve a dependency. This provider supports the properties described in Table 20-6.
Table 20-6

The Factory Provider Properties

Name

Description

provide

This property defines the service token, as described in the “Understanding the Token” section earlier in the chapter.

deps

This property specifies an array of provider tokens that will be resolved and passed to the function specified by the useFactory property.

useFactory

This property specifies the function that will create the service object. The objects produced by resolving the tokens specified by the deps property will be passed to the function as arguments. The result returned by the function will be used as the service object.

multi

This property is used to allow multiple providers to be combined to provide an array of objects that will be used to resolve a dependency on the token. See the “Resolving a Dependency with Multiple Objects” section earlier in the chapter for an example.

This is the provider that gives the most flexibility in how service objects are created because you can define functions that are tailored to your application’s requirements. Listing 20-16 shows a factory function that creates LogService objects.
...
@NgModule({
    imports: [BrowserModule, FormsModule, ReactiveFormsModule],
    declarations: [ProductComponent, PaAttrDirective, PaModel,
        PaStructureDirective, PaIteratorDirective,
        PaCellColor, PaCellColorSwitcher, ProductTableComponent,
        ProductFormComponent, PaAddTaxPipe, PaCategoryFilterPipe,
        PaDiscountDisplayComponent, PaDiscountEditorComponent,
        PaDiscountPipe, PaDiscountAmountDirective],
    providers: [DiscountService, SimpleDataSource, Model,
        {
            provide: LogService, useFactory: () => {
                let logger = new LogService();
                logger.minimumLevel = LogLevel.DEBUG;
                return logger;
            }
        }],
    bootstrap: [ProductComponent]
})
...
Listing 20-16

Using the Factory Provider in the app.module.ts File in the src/app Folder

The function in this example is simple: it receives no arguments and just creates a new LogService object. The real flexibility of this provider comes when the deps property is used, which allows for dependencies to be created on other services. In Listing 20-17, I have defined a token that specifies a debugging level.
import { Injectable, InjectionToken } from "@angular/core";
export const LOG_SERVICE = new InjectionToken("logger");
export const LOG_LEVEL = new InjectionToken("log_level");
export enum LogLevel {
    DEBUG, INFO, ERROR
}
@Injectable()
export class LogService {
    minimumLevel: LogLevel = LogLevel.INFO;
    // ...methods omitted for brevity...
}
@Injectable()
export class SpecialLogService extends LogService {
    // ...methods omitted for brevity...
}
Listing 20-17

Defining a Logging Level Service in the log.service.ts File in the src/app Folder

In Listing 20-18, I have defined a value provider that creates a service using the LOG_LEVEL token and used that service in the factory function that creates the LogService object.
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { ProductComponent } from "./component";
import { FormsModule, ReactiveFormsModule  } from "@angular/forms";
import { PaAttrDirective } from "./attr.directive";
import { PaModel } from "./twoway.directive";
import { PaStructureDirective } from "./structure.directive";
import { PaIteratorDirective } from "./iterator.directive";
import { PaCellColor } from "./cellColor.directive";
import { PaCellColorSwitcher } from "./cellColorSwitcher.directive";
import { ProductTableComponent } from "./productTable.component";
import { ProductFormComponent } from "./productForm.component";
import { PaAddTaxPipe } from "./addTax.pipe";
import { PaCategoryFilterPipe } from "./categoryFilter.pipe";
import { PaDiscountDisplayComponent } from "./discountDisplay.component";
import { PaDiscountEditorComponent } from "./discountEditor.component";
import { DiscountService } from "./discount.service";
import { PaDiscountPipe } from "./discount.pipe";
import { PaDiscountAmountDirective } from "./discountAmount.directive";
import { SimpleDataSource } from "./datasource.model";
import { Model } from "./repository.model";
import { LogService, LOG_SERVICE, SpecialLogService,
         LogLevel, LOG_LEVEL} from "./log.service";
@NgModule({
    imports: [BrowserModule, FormsModule, ReactiveFormsModule],
    declarations: [ProductComponent, PaAttrDirective, PaModel,
        PaStructureDirective, PaIteratorDirective,
        PaCellColor, PaCellColorSwitcher, ProductTableComponent,
        ProductFormComponent, PaAddTaxPipe, PaCategoryFilterPipe,
        PaDiscountDisplayComponent, PaDiscountEditorComponent,
        PaDiscountPipe, PaDiscountAmountDirective],
    providers: [DiscountService, SimpleDataSource, Model,
        { provide: LOG_LEVEL, useValue: LogLevel.DEBUG },
        { provide: LogService,
          deps: [LOG_LEVEL],
          useFactory: (level) => {
            let logger = new LogService();
            logger.minimumLevel = level;
            return logger;
         }
        }],
    bootstrap: [ProductComponent]
})
export class AppModule { }
Listing 20-18

Using Factory Dependencies in the app.module.ts File in the src/app Folder

The LOG_LEVEL token is used by a value provider to define a simple value as a service. The factory provider specifies this token in its deps array, which the dependency injection system resolves and provides as an argument to the factory function, which uses it to set the minimumLevel property of a new LogService object.

Using the Existing Service Provider

This provider is used to create aliases for services so they can be targeted using more than one token, using the properties described in Table 20-7.
Table 20-7

The Existing Provider Properties

Name

Description

provide

This property defines the service token, as described in the “Understanding the Token” section earlier in the chapter.

useExisting

This property is used to specify the token of another provider, whose service object will be used to resolve dependencies on this service.

multi

This property is used to allow multiple providers to be combined to provide an array of objects that will be used to resolve a dependency on the token. See the “Resolving a Dependency with Multiple Objects” section earlier in the chapter for an example.

This provider can be useful when you want to refactor the set of providers but don’t want to eliminate all the obsolete tokens in order to avoid refactoring the rest of the application. Listing 20-19 shows the use of this provider.
...
@NgModule({
    imports: [BrowserModule, FormsModule, ReactiveFormsModule],
    declarations: [ProductComponent, PaAttrDirective, PaModel,
        PaStructureDirective, PaIteratorDirective,
        PaCellColor, PaCellColorSwitcher, ProductTableComponent,
        ProductFormComponent, PaAddTaxPipe, PaCategoryFilterPipe,
        PaDiscountDisplayComponent, PaDiscountEditorComponent,
        PaDiscountPipe, PaDiscountAmountDirective],
    providers: [DiscountService, SimpleDataSource, Model,
        { provide: LOG_LEVEL, useValue: LogLevel.DEBUG },
        { provide: "debugLevel", useExisting: LOG_LEVEL },
        { provide: LogService,
          deps: ["debugLevel"],
          useFactory: (level) => {
            let logger = new LogService();
            logger.minimumLevel = level;
            return logger;
         }
        }],
    bootstrap: [ProductComponent]
})
...
Listing 20-19

Creating a Service Alias in the app.module.ts File in the src/app Folder

The token for the new service is the string debugLevel, and it is aliased to the provider with the LOG_LEVEL token. Using either token will result in the dependency being resolved with the same value.

Using Local Providers

When Angular creates a new instance of a class, it resolves any dependencies using an injector. It is an injector that is responsible for inspecting the constructors of classes to determine what dependencies have been declared and resolving them using the available providers.

So far, all of the dependency injection examples have relied on providers configured in the application’s Angular module. But the Angular dependency injection system is more complex: there is a hierarchy of injectors that corresponds to the application’s tree of components and directives. Each component and directive can have its own injector, and each injector can be configured with its own set of providers, known as local providers.

When there is a dependency to resolve, Angular uses the injector for the nearest component or directive. The injector first tries to resolve the dependency using its own set of local providers. If no local providers have been set up or there are no providers that can be used to resolve this specific dependency, then the injector consults the parent component’s injector. The process is repeated—the parent component’s injector tries to resolve the dependency using its own set of local providers. If a suitable provider is available, then it is used to provide the service object required to resolve the dependency. If there is no suitable provider, then the request is passed up to the next level in the hierarchy, to the grandparent of the original injector. At the top of the hierarchy is the root Angular module, whose providers are the last resort before reporting an error.

Defining providers in the Angular module means that all dependencies for a token within the application will be resolved using the same object. As I explain in the following sections, registering providers further down the injector hierarchy can change this behavior and alter the way that services are created and used.

Understanding the Limitations of Single Service Objects

Using a single service object can be a powerful tool, allowing building blocks in different parts of the application to share data and respond to user interactions. But some services don’t lend themselves to being shared so widely. As a simple example, Listing 20-20 adds a dependency on LogService to one of the pipes created in Chapter 18.
import { Pipe, Injectable } from "@angular/core";
import { DiscountService } from "./discount.service";
import { LogService } from "./log.service";
@Pipe({
    name: "discount",
    pure: false
})
export class PaDiscountPipe {
    constructor(private discount: DiscountService,
                private logger: LogService) { }
    transform(price: number): number {
        if (price > 100) {
            this.logger.logInfoMessage(`Large price discounted: ${price}`);
        }
        return this.discount.applyDiscount(price);
    }
}
Listing 20-20

Adding a Service Dependency in the discount.pipe.ts File in the src/app Folder

The pipe’s transform method uses the LogService object, which is received as a constructor argument, to generate logging messages when the price it transforms is greater than 100.

The problem is that these log messages are drowned out by the messages generated by the DiscountService object, which creates a message every time a discount is applied. The obvious thing to do is to change the minimum level in the LogService object when it is created by the module provider’s factory function, as shown in Listing 20-21.
...
@NgModule({
    imports: [BrowserModule, FormsModule, ReactiveFormsModule],
    declarations: [ProductComponent, PaAttrDirective, PaModel,
        PaStructureDirective, PaIteratorDirective,
        PaCellColor, PaCellColorSwitcher, ProductTableComponent,
        ProductFormComponent, PaAddTaxPipe, PaCategoryFilterPipe,
        PaDiscountDisplayComponent, PaDiscountEditorComponent,
        PaDiscountPipe, PaDiscountAmountDirective],
    providers: [DiscountService, SimpleDataSource, Model,
        { provide: LOG_LEVEL, useValue: LogLevel.ERROR },
        { provide: "debugLevel", useExisting: LOG_LEVEL },
        {
            provide: LogService,
            deps: ["debugLevel"],
            useFactory: (level) => {
                let logger = new LogService();
                logger.minimumLevel = level;
                return logger;
            }
        }],
    bootstrap: [ProductComponent]
})
...
Listing 20-21

Changing the Logging Level in the app.module.ts File in the src/app Folder

Of course, this doesn’t have the desired effect because the same LogService object is used throughout the application and filtering the DiscountService messages means that the pipe messages are filtered too.

I could enhance the LogService class so there are different filters for each source of logging messages, but that gets complicated pretty quickly. Instead, I am going to solve the problem by creating a local provider so that there are multiple LogService objects in the application, each of which can then be configured separately.

Creating Local Providers in a Component

Components can define local providers, which allow separate servers to be created and used by part of the application. Components support two decorator properties for creating local providers, as described in Table 20-8.
Table 20-8

The Component Decorator Properties for Local Providers

Name

Description

providers

This property is used to create a provider used to resolve dependencies of view and content children.

viewProviders

This property is used to create a provider used to resolve dependencies of view children.

The simplest way to address my LogService issue is to use the providers property to set up a local provider, as shown in Listing 20-22.
import { Component, Input } from "@angular/core";
import { Model } from "./repository.model";
import { Product } from "./product.model";
import { DiscountService } from "./discount.service";
import { LogService } from "./log.service";
@Component({
  selector: "paProductTable",
  templateUrl: "productTable.component.html",
  providers:[LogService]
})
export class ProductTableComponent {
  constructor(private dataModel: Model) { }
  getProduct(key: number): Product {
    return this.dataModel.getProduct(key);
  }
  getProducts(): Product[] {
    return this.dataModel.getProducts();
  }
  deleteProduct(key: number) {
    this.dataModel.deleteProduct(key);
  }
  dateObject: Date = new Date(2020, 1, 20);
  dateString: string = "2020-02-20T00:00:00.000Z";
  dateNumber: number = 1582156800000;
}
Listing 20-22

Creating a Local Provider in the productTable.component.ts File in the src/app Folder

When Angular needs to create a new pipe object, it detects the dependency on LogService and starts working its way up the application hierarchy, examining each component it finds to determine whether they have a provider that can be used to resolve the dependency. The ProductTableComponent does have a LogService provider, which is used to create the service used to resolve the pipe’s dependency. This means there are now two LogService objects in the application, each of which can be configured separately, as shown in Figure 20-2.
../images/421542_3_En_20_Chapter/421542_3_En_20_Fig2_HTML.jpg
Figure 20-2

Creating a local provider

The LogService object created by the component’s provider uses the default value for its minimumLevel property and will display LogLevel.INFO messages. The LogService object created by the module, which will be used to resolve all other dependencies in the application, including the one declared by the DiscountService class, is configured so that it will display only LogLevel.ERROR messages. When you save the changes, you will see the logging messages from the pipe (which receives the service from the component) but not from DiscountService (which receives the service from the module).

Understanding the Provider Alternatives

As described in Table 20-8, there are two properties that can be used to create local providers. To demonstrate how these properties differ, I added a file called valueDisplay.directive.ts to the src/app folder and used it to define the directive shown in Listing 20-23.
import { Directive, InjectionToken, Inject, HostBinding} from "@angular/core";
export const VALUE_SERVICE = new InjectionToken("value_service");
@Directive({
    selector: "[paDisplayValue]"
})
export class PaDisplayValueDirective {
    constructor( @Inject(VALUE_SERVICE) serviceValue: string) {
        this.elementContent = serviceValue;
    }
    @HostBinding("textContent")
    elementContent: string;
}
Listing 20-23

The Contents of the valueDisplay.directive.ts File in the src/app Folder

The VALUE_SERVICE opaque token will be used to define a value-based service, on which the directive in this listing declares a dependency so that it can be displayed in the host element’s content. Listing 20-24 shows the service being defined and the directive being registered in the Angular module. I have also simplified the LogService provider in the module for brevity.
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { ProductComponent } from "./component";
import { FormsModule, ReactiveFormsModule  } from "@angular/forms";
import { PaAttrDirective } from "./attr.directive";
import { PaModel } from "./twoway.directive";
import { PaStructureDirective } from "./structure.directive";
import { PaIteratorDirective } from "./iterator.directive";
import { PaCellColor } from "./cellColor.directive";
import { PaCellColorSwitcher } from "./cellColorSwitcher.directive";
import { ProductTableComponent } from "./productTable.component";
import { ProductFormComponent } from "./productForm.component";
import { PaAddTaxPipe } from "./addTax.pipe";
import { PaCategoryFilterPipe } from "./categoryFilter.pipe";
import { PaDiscountDisplayComponent } from "./discountDisplay.component";
import { PaDiscountEditorComponent } from "./discountEditor.component";
import { DiscountService } from "./discount.service";
import { PaDiscountPipe } from "./discount.pipe";
import { PaDiscountAmountDirective } from "./discountAmount.directive";
import { SimpleDataSource } from "./datasource.model";
import { Model } from "./repository.model";
import { LogService, LOG_SERVICE, SpecialLogService,
    LogLevel, LOG_LEVEL} from "./log.service";
import { VALUE_SERVICE, PaDisplayValueDirective} from "./valueDisplay.directive";
@NgModule({
    imports: [BrowserModule, FormsModule, ReactiveFormsModule],
    declarations: [ProductComponent, PaAttrDirective, PaModel,
        PaStructureDirective, PaIteratorDirective,
        PaCellColor, PaCellColorSwitcher, ProductTableComponent,
        ProductFormComponent, PaAddTaxPipe, PaCategoryFilterPipe,
        PaDiscountDisplayComponent, PaDiscountEditorComponent,
        PaDiscountPipe, PaDiscountAmountDirective, PaDisplayValueDirective],
    providers: [DiscountService, SimpleDataSource, Model, LogService,
                { provide: VALUE_SERVICE, useValue: "Apples" }],
    bootstrap: [ProductComponent]
})
export class AppModule { }
Listing 20-24

Registering the Directive and Service in the app.module.ts File in the src/app Folder

The provider sets up a value of Apples for the VALUE_SERVICE service. The next step is to apply the new directive so there is an instance that is a view child of a component and another that is a content child. Listing 20-25 sets up the content child instance.
<div class="row m-2">
  <div class="col-4 p-2">
    <paProductForm>
      <span paDisplayValue></span>
    </paProductForm>
  </div>
  <div class="col-8 p-2">
    <paProductTable></paProductTable>
  </div>
</div>
Listing 20-25

Applying a Content Child Directive in the template.html File in the src/app Folder

Listing 20-26 projects the host element’s content and adds a view child instance of the new directive.
<form novalidate [formGroup]="form" (ngSubmit)="submitForm(form)">
    <div class="form-group" *ngFor="let control of form.productControls">
        <label>{{control.label}}</label>
        <input class="form-control"
            [(ngModel)]="newProduct[control.modelProperty]"
            name="{{control.modelProperty}}"
            formControlName="{{control.modelProperty}}" />
        <ul class="text-danger list-unstyled"
                *ngIf="(formSubmitted || control.dirty) && !control.valid">
            <li *ngFor="let error of control.getValidationMessages()">
                {{error}}
            </li>
        </ul>
    </div>
    <button class="btn btn-primary" type="submit"
        [disabled]="formSubmitted && !form.valid"
        [class.btn-secondary]="formSubmitted && !form.valid">
            Create
    </button>
</form>
<div class="bg-info text-white m-2 p-2">
    View Child Value: <span paDisplayValue></span>
</div>
<div class="bg-info text-white m-2 p-2">
    Content Child Value: <ng-content></ng-content>
</div>
Listing 20-26

Adding Directives in the productForm.component.html File in the src/app Folder

When you save the changes, you will see the new elements, as shown in Figure 20-3, both of which show the same value because the only provider for VALUE_SERVICE is defined in the module.
../images/421542_3_En_20_Chapter/421542_3_En_20_Fig3_HTML.jpg
Figure 20-3

View and content child directives

Creating a Local Provider for All Children

The @Component decorator’s providers property is used to define providers that will be used to resolve service dependencies for all children, regardless of whether they are defined in the template (view children) or projected from the host element (content children). Listing 20-27 defines a VALUE_SERVICE provider in the parent component for two new directive instances.
import { Component, Output, EventEmitter, ViewEncapsulation } from "@angular/core";
import { Product } from "./product.model";
import { ProductFormGroup } from "./form.model";
import { Model } from "./repository.model";
import { VALUE_SERVICE } from "./valueDisplay.directive";
@Component({
    selector: "paProductForm",
    templateUrl: "productForm.component.html",
    providers: [{ provide: VALUE_SERVICE, useValue: "Oranges" }]
})
export class ProductFormComponent {
    form: ProductFormGroup = new ProductFormGroup();
    newProduct: Product = new Product();
    formSubmitted: boolean = false;
    constructor(private model: Model) { }
    submitForm(form: any) {
        this.formSubmitted = true;
        if (form.valid) {
            this.model.saveProduct(this.newProduct);
            this.newProduct = new Product();
            this.form.reset();
            this.formSubmitted = false;
        }
    }
}
Listing 20-27

Defining a Provider in the productForm.component.ts File in the src/app Folder

The new provider changes the service value. When Angular comes to create the instances of the new directive, it begins its search for providers by working its way up the application hierarchy and finds the VALUE_SERVICE provider defined in Listing 20-27. The service value is used by both instances of the directive, as shown in Figure 20-4.
../images/421542_3_En_20_Chapter/421542_3_En_20_Fig4_HTML.jpg
Figure 20-4

Defining a provider for all children in a component

Creating a Provider for View Children

The viewProviders property defines providers that are used to resolve dependencies for view children but not content children. Listing 20-28 uses the viewProviders property to define a provider for VALUE_SERVICE.
import { Component, Output, EventEmitter, ViewEncapsulation } from "@angular/core";
import { Product } from "./product.model";
import { ProductFormGroup } from "./form.model";
import { Model } from "./repository.model";
import { VALUE_SERVICE } from "./valueDisplay.directive";
@Component({
    selector: "paProductForm",
    templateUrl: "productForm.component.html",
    viewProviders: [{ provide: VALUE_SERVICE, useValue: "Oranges" }]
})
export class ProductFormComponent {
    // ...methods and properties omitted for brevity...
}
Listing 20-28

Defining a View Child Provider in the productForm.component.ts File in the src/app Folder

Angular uses the provider when resolving dependencies for view children but not for content children. This means dependencies for content children are referred up the application’s hierarchy as though the component had not defined a provider. In the example, this means that the view child will receive the service created by the component’s provider, and the content child will receive the service created by the module’s provider, as shown in Figure 20-5.

Caution

Defining providers for the same service using both the providers and viewProviders properties is not supported. If you do this, the view and content children both will receive the service created by the viewProviders provider.

../images/421542_3_En_20_Chapter/421542_3_En_20_Fig5_HTML.jpg
Figure 20-5

Defining a provider for view children

Controlling Dependency Resolution

Angular provides three decorators that can be used to provide instructions about how a dependency is resolved. These decorators are described in Table 20-9 and demonstrated in the following sections.
Table 20-9

The Dependency Resolution Decorators

Name

Description

@Host

This decorator restricts the search for a provider to the nearest component.

@Optional

This decorator stops Angular from reporting an error if the dependency cannot be resolved.

@SkipSelf

This decorator excludes the providers defined by the component/directive whose dependency is being resolved.

Restricting the Provider Search

The @Host decorator restricts the search for a suitable provider so that it stops once the closest component has been reached. The decorator is typically combined with @Optional, which prevents Angular from throwing an exception if a service dependency cannot be resolved. Listing 20-29 shows the addition of both decorators to the directive in the example.
import { Directive, InjectionToken, Inject,
         HostBinding, Host, Optional} from "@angular/core";
export const VALUE_SERVICE = new InjectionToken("value_service");
@Directive({
    selector: "[paDisplayValue]"
})
export class PaDisplayValueDirective {
    constructor( @Inject(VALUE_SERVICE) @Host() @Optional() serviceValue: string) {
        this.elementContent = serviceValue || "No Value";
    }
    @HostBinding("textContent")
    elementContent: string;
}
Listing 20-29

Adding Dependency Decorators in the valueDisplay.directive.ts File in the src/app Folder

When using the @Optional decorator, you must ensure that the class is able to function if the service cannot be resolved, in which case the constructor argument for the service is undefined. The nearest component defines a service for its view children but not content children, which means that one instance of the directive will receive a service object and the other will not, as illustrated in Figure 20-6.
../images/421542_3_En_20_Chapter/421542_3_En_20_Fig6_HTML.jpg
Figure 20-6

Controlling how a dependency is resolved

Skipping Self-Defined Providers

By default, the providers defined by a component are used to resolve its dependencies. The @SkipSelf decorator can be applied to constructor arguments to tell Angular to ignore the local providers and start the search at the next level in the application hierarchy, which means that the local providers will be used only to resolve dependencies for children. In Listing 20-30, I have added a dependency on the VALUE_SERVICE provider that is decorated with @SkipSelf.
import { Component, Output, EventEmitter, ViewEncapsulation,
         Inject, SkipSelf } from "@angular/core";
import { Product } from "./product.model";
import { ProductFormGroup } from "./form.model";
import { Model } from "./repository.model";
import { VALUE_SERVICE } from "./valueDisplay.directive";
@Component({
    selector: "paProductForm",
    templateUrl: "productForm.component.html",
    viewProviders: [{ provide: VALUE_SERVICE, useValue: "Oranges" }]
})
export class ProductFormComponent {
    form: ProductFormGroup = new ProductFormGroup();
    newProduct: Product = new Product();
    formSubmitted: boolean = false;
    constructor(private model: Model,
            @Inject(VALUE_SERVICE) @SkipSelf() private serviceValue: string) {
        console.log("Service Value: " + serviceValue);
    }
    submitForm(form: any) {
        this.formSubmitted = true;
        if (form.valid) {
            this.model.saveProduct(this.newProduct);
            this.newProduct = new Product();
            this.form.reset();
            this.formSubmitted = false;
        }
    }
}
Listing 20-30

Skipping Local Providers in the productForm.component.ts File in the src/app Folder

When you save the changes and the browser reloads the page, you will see the following message in the browser’s JavaScript console, showing that the service value defined locally (Oranges) has been skipped and allowing the dependency to be resolved by the Angular module:
Service Value: Apples

Summary

In this chapter, I explained the role that providers play in dependency injection and explained how they can be used to change how services are used to resolve dependencies. I described the different types of provider that can be used to create service objects and demonstrated how directives and components can define their own providers to resolve their own dependencies and those of their children. In the next chapter, I describe modules, which are the final building block for Angular applications.

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

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