Angular ~ Dependency Injection & Providers - rohit120582sharma/Documentation GitHub Wiki

Table of contents

  • Introduction
    • Common functionality, Centerlize data/state management, and cross component's communication
  • Create a service
    • Simple class with @Injectable decorator
    • Apply @Injectable decorator if this service needs another service via Dependency Injection system
  • Register a service (Provide a service as dependency into an Injector)
    • Config a service with providers (an object mapping token with a dependency) into an injector (ngModule, Component or Lazy loaded module).
    • Hierarchical Injector
  • Use a service
    • Push notification for cross component's communication
    • Use a service into another service

Introduction

It is a method of decoupling a piece of code from the dependencies it needs in order to run.

It uses Inversion of Control so the responsibility of creating dependencies and passing them into dependant pieces of code is handled by something else.

The specific design pattern for implementing IoC above is called Dependency Injection.

Dependency Injection is a way of architecting an application so the code is easier to re-use, easier to test and easier to maintain.

Angular comes with a Dependency Injection (DI) framework of it’s own and it’s used throughout Angulars code.

The DI framework in Angular consists of 4 concepts working together:

  • Token - This uniquely identifies something that we want injected. A dependancy of our code.
  • Dependancy - The actual code we want injected.
  • Provider - This is a map between a token and a list of dependancies.
  • Injector - This is a function which when passed a token returns a dependancy (or a list of dependencies)

Injectors

At the core of the DI framework is an injector.

An injector is passed a token and returns a dependency (or list of). We say that an injector resolves a token into a dependency.

Normally we never need to implement an injector. Angular handles low level injectable implementation details for us and typically we just configure it to give us to behaviour we want.

We configure injectors with providers, Angular then uses these injectors to resolve dependencies using tokens and injecting them into constructors as arguments.

We create child injectors from parent injectors. A child injector will forward a request to their parent if it can’t resolve the token itself.

The injector doesn’t return the class, but an instance of the class instantiated with new.

The dependencies returned from injectors are cached. So multiple calls to the same injector for the same token will return the same instance.


Provider

We configure injectors with providers and a provider links a token to a dependency.

The real configuration for a provider is an object which describes a token and configuration for how to create the associated dependency.

There are 4 different kinds of dependencies providers can provide in Angular.

useClass

We can have a provider which maps a token to a class.

{ provide: Car, useClass: Car }

useExisting

We can make two tokens map to the same thing via aliases

{ provide: Car, useClass: Car }, 
{ provide: BigBus, useExisting: Car }

useValue

We can also provide a simple value. By using Object.freeze that objects values can’t be changed, in effect it’s read-only.

{
	provide: 'Config',
	useValue: Object.freeze({
		'APIKey': 'XYZ1234ABC',
		'APISecret': '555-123-111'
	})
}

useFactory

We can also configure a provider to call a function every-time a token is requested, leaving it to the provider to figure out what to return.

When the injector resolves to this provider, it calls the useFactory function and returns whatever is returned by this function as the dependency. Just like other providers, the result of the call is cached which means it returns the same instance if calling again.

{
	provide: "EmailService",
	useFactory: ()=>{ 
		if(isProd){
			return new MandrillService();
		}else{
			return new SendGridService();
		}
	}
}

Tokens

A token can be either a string, a class or an instance of InjectionToken.

String tokens can cause name clashes so we prefer to use InjectionTokens instead.

{ provide: "EmailService", useClass: MandrillService }

Or

{ provide: EmailService, useClass: SendGridService }

Configure Injectors

Understanding where to configure your provider is a key piece of understanding how to architect your application.

We can configure injectors in Angular by:

NgModule.providers

If we want an instance of a dependency to be shared globally and share state across the application we configure it on the NgModule.

Since we only have one injector which is resolving the dependency, every-time we request an instance of a dependency to be injected into one of our components it’s always going to inject the same instance.

@NgModule({
	providers: [ SimpleService ]
})
class AppModule{
}

Component.providers

If we want to have one instance of a service per component, and shared with all the components children, we configure it on the providers property on our component decorator. So state is not shared globally but only between a component and it’s child components.

Remember when we request the same token from different injectors we get the different instances.

@Component({
	selector: 'parent',
	template: `...`,
	providers: [ SimpleService ]
})
class ParentComponent{
	constructor(private service: SimpleService){
	}
}

Component.viewProviders

If we want to have one instance of a service per component, and shared with only the components view children and not the components content children, we configure it on the viewProviders property on our component decorator.

When using viewProviders the component creates an child injector which is only used by the current component and any view children.

@Component({
	selector: 'parent',
	template: `...`,
	viewProviders: [ SimpleService ]
})
class ParentComponent{
	constructor(private service: SimpleService){
	}
}

Hierarchical Injector & Inject Dependency

There is one top level injector created for each NgModule and then for each component in our app, from the root component down, there is a tree of injectors created which map to the component tree.

App Module - the same instance of service is available Application-wide.

App Component - the same instance of service is available for all components (but not for other services).

Any Other Component - the same instance of service is available for the component and all its child components.

We configure these injectors with providers by adding the configuration to either the providers property on the NgModule, Component and Directive decorators or to the viewProviders property on the Component decorator.

We use the @Inject parameter decorator to instruct Angular we want to resolve a token and inject a dependency into a constructor.

We use the @Injectable class decorators to automatically resolve and inject all the parameters of class constructor. This only works if each parameter has a TypeScript type associated with it, which the DI framework uses as the token.

We don’t need to use the @Injectable class decorator on classes which are already decorated with such as @NgModule, @Component, @Directive, or @Pipe.