Angular: How StaticInjector replaces ReflectiveInjector

New StaticInjector has been merged to the master branch of angular project. It means that it will be released soon in angular@5.x.x and should give us a small increase in performance and smaller bundles but we will came across with new breaking changes.

Let’s see what we should expect from a new injector.

ReflectiveInjector will be deprecated soon

If you rely on ReflectiveInjector.resolveAndCreate method to create your own injector then you should use Injector.create instead so the following code:

import { ReflectiveInjector } from ‘@angular/core’;
ReflectiveInjector.resolveAndCreate([SomeClass], parentInjector);

becames

import { Injector } from ‘@angular/core’;
Injector.create([{provide: SomeClass, deps: []], parentInjector);

But that is not all. We have already known that we can provide token several ways:

1) TypeProvider:

[ SomeClass ] 

2) ClassProvider:

[ { provide: SomeClass, useClass: SomeOtherClass } ]

3) ValueProvider:

[ { provide: URL_TOKEN, useValue: 'someString' } ]

4) ExistingProvider:

[ { provide: Foo, useExisting: Bar } ]

5) FactoryProvider:

[ 
{
provide: CustomHttp,
useFactory: httpFactory,
deps: [Http, SomeClass]
}
]

What has changed?

Note: This only applies to platform creation and providers for the JIT compiler. Fortunately it does not apply to Compotent or NgModule declarations so we do not need to rewrite all our providers.

New Injector doesn’t rely on Reflect polyfill anymore as it did ReflectiveInjector:

function _dependenciesFor(typeOrFunc: any): ReflectiveDependency[] {
const params = reflector.parameters(typeOrFunc);

StaticInjector looks at deps property instead.

function computeDeps(provider: StaticProvider): DependencyRecord[] {
let deps: DependencyRecord[] = EMPTY;
const providerDeps: any[] =
(provider as ExistingProvider & StaticClassProvider & ConstructorProvider).deps;

So we have to specify dependencies for TypeProvider and ClassProvider in deps property. This way to provide some token we should:

  • 1) use ConstructorProvider type instead of TypeProvider:
export interface ConstructorProvider {
provide: Type<any>;

deps: any[];

multi?: boolean;
}

Before:

@Injectable()
class SomeClass {
constructor(
private foo: Foo,
@Optional() @Inject(BAR_TOKEN) private bar: string) {}
}
[ SomeClass ]

@Injectable decorator does all work by keeping parameters for class:

SomeClass = __decorate([
core_1.Injectable(),
__param(1, core_1.Optional()), __param(1, core_1.Inject(BAR_TOKEN)),
__metadata("design:paramtypes", [Foo, String])
], SomeClass);

Now we can omit Injectable decorator and specify parameters explicity.

After:

[ 
{
provide: SomeClass,
deps: [Foo, [Optional, BAR_TOKEN]]
}
]
  • 2) use StaticClassProvider type instead of ClassProvider:
export interface StaticClassProvider {
provide: any;

useClass: Type<any>;

deps: any[];

multi?: boolean;
}

Before:

[ 
{
provide: SomeClass,
useClass: SomeOtherClass
}
]

After:

[ 
{
provide: SomeClass,
useClass: SomeOtherClass,
deps: [Dep1, Dep2]
}
]

As I said early this only applies to Platform Injector e.g.

const platform = platformBrowserDynamic(
/*extra providers*/
[
{ provide: Foo, deps: [] }
]
);

and to Compiler Injector e.g:

platformBrowserDynamic().bootstrapModule(AppModule, 
{
providers: [
{
provide: ElementSchemaRegistry,
useClass: CustomDomElementSchemaRegistry,
deps: []
}
]
}
);

That’s all. Thanks for reading..