Reputation: 7931
I tried to create dependency explictly using two ways. Both are almost same but I am little bit confusing what's the advantage of using Reflective Injector over the normal Injector and which one is recommended way?
Using Injector
import { Injector } from '@angular/core';
constructor( private injector: Injector) {
this.myService = this.injector.get(MyService);
}
Using Reflective Injector
import { ReflectiveInjector } from '@angular/core';
constructor() {
var injector= ReflectiveInjector.resolveAndCreate([MyService]);
let myservice=injector.get(MyService);
}
Upvotes: 2
Views: 2376
Reputation: 54801
Angular will only create an instance once for each provider. Therefore, if you want to use the Injector
as a factory you can't. You have to create a new injector for each new instance of an object you need.
So let's say we have a service class called ReportsService
and it needs to create Record
objects at run-time, but you want Record
to be injectable.
Here's a class that we need to create multiple instances of.
@Injectable() // does not have to be defined in ngModule
export class Record {
// just an example dependency that ReportsService doesn't know about
public constructor(http: Http) {
}
}
Here's the service that will create the above class.
@Injectable() // exported via ngModule
export class RecordsService {
// we need injector as parent otherwise Record class will not find Http
public constructor(private parent: Injector) {
}
// factory method
public createRecord(): Record {
// creates a new injector that knows about Record but extends existing injector
let injector = ReflectiveInjector.resolveAndCreate([
{provide: Record, useClass: Record}
], this.parent);
// first call, object is created
let record = injector.get(Record);
// just an example
let example = injector.get(Record);
// will output true
console.log(record === example ? 'true' : 'false');
return record;
}
}
Now a new instance of Record is great, but what's the point if each object is exactly the same. So we need to inject parameters for Record
. We'll create a token for a string value.
export const RECORD_URL = new InjectionToken<string>('record-url');
@Injectable()
export class Record {
public constructor(http: Http, @Inject(RECORD_URL) url: string) {
}
}
Now update the create function
public createRecord(url: string): Record {
let injector = ReflectiveInjector.resolveAndCreate([
{provide: Record, useClass: Record},
{provide: RECORD_URL, useValue: url
], this.parent);
return injector.get(Record);
}
Upvotes: 1
Reputation: 105517
An injector is a container with providers/services. It implements one method get
and returns an instance of the service. Let's implement the most basic version of the injector in JS pseudocode:
class ReflectiveInjector {
providers = [];
static resolveAndCreate(providers) {
providers.forEach((provider)=>{
providers.push({token: provider.provide, instance: new provider.useClass})
});
)}
get(dep) {
return providers.find((provider)=>{ return provider.token === token });
}
}
Now, we need to create the instance of the injector before we can use it. And when we create it we define providers:
const existingInjector = ReflectiveInjector.resolveAndCreate([{provide: A, useClass: A }]);
const AInstance = existingInjector.get(A);
So you see in order to use an injector it has to be created first. It doesn't have any specific methods that allow adding providers so after it's created we can't add any new providers to it.
The injector you inject into the component constructor is already created by Angular. You can't add anything to it and can query only the providers already defined on it. If you need to provide B
class, you can't do that with existing injector. You need a new one. And this is where ReflectiveInjector
class comes in. It allows you to add new providers by creating a new instance of the injector and registering new providers. And the good part is that it can also setup an injector chain.
Let's a bit modify resolveAndCreate
and get
methods to allow chaining injectors:
class ReflectiveInjector {
providers = [];
parent;
static resolveAndCreate(providers, parent) {
this.parent = parent;
...
}
get(dep) {
let found = providers.find((provider)=>{ return provider.token === token });
if (!found && parent) {
found = parent.get(dep);
}
return found;
}
So now the only that left is to use it:
// passing existingInjector as a parent
const childInjector = ReflectiveInjector.resolveAndCreate([{provide: B, useClass: B }], i);
const AInstance = childInjector.get(A);
const BInstance = childInjector.get(B);
Now, suppose someone might want to get access to our existingInjector
. We need a token to use to get this existing injector. Let's define this token like this:
abstract class Injector {}
and let's write a function that will get the existing injector:
function resolveDependency(token) {
if (token === Injector) {
return existingInjector;
}
}
And now suppose that Angular when executes the constructor of the component, uses the tokens you specified to get dependencies and passes them to the resolveDependency
function. So you write like this:
// the Injector here is a reference to our abstract Injector class and simply used as a token
MyComp {
constructor(private injector: Injector) { ... }
}
And the token here is Injector
which as I said is passed into the resolveDependency
function and the existing injector is returned.
Upvotes: 6