简体   繁体   中英

Difference between Reflective Injector and Injector in Angular

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);
     }

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.

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);
 }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM