Repository navigation
Closed
Description
Angular is already generating Injectors on elements for components. We would like to have the same approach for the injector used for bootstrapping and the injector used for dynamic component loading.
With this, we no longer need any metadata about classes / functions in offline compile mode, as all of the metadata (decorators) have been read during offline compile and converted into appropriate code. This means:
- Download size impact:
- We are no longer using
ReflectiveInjectornorReflector/ReflectionCapabilities- this drops about 30kb minified / 6.49k minified gzipped from angular core
- we don't need the
reflect-metadatapolyfill anymore when during offline compile (2.1kb min gzipped) - we don't need to keep the decorators anymore during runtime.
- we don't need to store the constructor arguments any more
- we are generting code for the injectors instead, which needs to be measured how much.
- We are no longer using
- Bootstrap speed
- analyzing the dependencies of classes, normalizing them and creating factories for them during bootstrap of an application can be costly depending on the number of services used. This especially affects bootstrap as the JavaScript vm most probably did not have time to optimize the code.
- this was proven by internal benchmarks for the last bigger change to DI for elements (see #8b000a7f0e9fade5e6a9354532597018c5ad9a55).
- Better global static analyzability of injection of angular apps. In the future, this would allow to check which providers are actually used in an application and report missing providers that services / directives that are used depend upon.
Impact for users:
- This change does not affect runtime compile, only offline compilation.
- This change only affects how users add additional providers to bootstrap / when components are dynamically loaded. Bootrapping / dynamically loading components without additional providers does not change.
- This change does not change how components use providers
Part of #6270
Input and output of the compilation
Declare injectors via a config class like this:
@InjectorConfig({
providers: [SomeService]
})
class MyAppConfig {
constructor(public @Provides(SomeToken) someProp: string) {
}
}
Via runtime or offline compile, we create a InjectorFactory that is usable like this:
var injectorFactory: InjectorFactory<MyAppConfig>;
var parentInjector: Injector;
var injector: Injector = injectorFactory.create(parentInjector, new MyAppConfig('hello'));
expect(injector.get(SomeService)).toBeAndInstanceOf(SomeService);
expect(injector.get(SomeToken)).toBe('hello');
Usage with offline compilation
// Name: drop suffix `Config` if existing and add `InjectorFactory`
import {MyAppInjectorFactory} from 'my_config.ngfactory';
MyAppInjectorFactory.create(...);
Usage with runtime compilation
var componentResolver: ComponentResolver;
// This would also jit the generated code, similar to our component compiler.
componentResolver.resolveInjector(MyAppConfig).then(
(injectorFactory: InjectorFactory<MyAppConfig>) => {
injectorFactory.create(...);
}
);
Consequences
- We can still support the
bootstrap(type, providers)call in@angular/browseras this uses theRuntimeCompileralready, so it can also create a injector config class on the fly with the given providers. -> plunkers stay simpler. - To create the ComponentResolver, we can't use DI any more, as it provides the factory for injectors.
- As the compiler creates the injector factories, the compiler can't be configured via DI, but we rather need factory methods that take the
CompilerConfigand maybe some other parameters.- e.g. platform directives / platform pipes need to become part of the
CompilerConfig,
and not provided via DI (this also unifies their configuration between offline and runtime compile).
- e.g. platform directives / platform pipes need to become part of the
Proposed changes to Angular besides the compiler changes
- [BREAKING] move
ReflectiveInjectorand move it to@angular/commonor into a
separate module as Angular itself does no more use it.- It is still used by e.g. benchpress so we need to keep it.
- Instead, users have to create injector config classes as shown above for both, runtime and offline compile. For runtime compile, we could create these injector config classes on the fly, but to make it easier for users to switch to offline compile they should already use them.
- or maybe just deprecate it?
- move
ReflectorandReflectionCapabilitiesfrom@angular/coreto@angular/compileras the
compiler is the only place where these are used. - make
ComponentResolver(anRuntimeCompiler) part ofPlatformRefand don't use DI to create the providers ofPlatformRef. Still expose an injector inPlatformRefwhich is just based on aMap. - [BREAKING] make platform pipes / platform directives part of
CompilerConfigand define them as arrays ofCompileIdentifierMetadataso they can be used for offline and runtime compile. - [BREAKING] pass
CompilerConfigtobrowserPlatform()as argument instead of getting it via DI.- we might consider using a special
BrowserCompileConfigclass as some parts ofCompilerConfigare implied by using the browser platform (e.g. that platform directives are always defined via their runtime types, i.e. the user only needs to provide an array of types instead ofCompileIdentifierMetadata)
- we might consider using a special
Metadata
Metadata
Assignees
Labels
No labels
Activity
tbosch commentedon Jun 3, 2016
/cc @mhevery
tbosch commentedon Jun 3, 2016
Other changes agreed upon:
ComponentResolvertoFactoryResolverComponentFactorys to not includeNgin the name, e.g.MyComponent->MyComponentFactory, but keep the filename as is (i.e....ngfactory.ts).ReflectiveInjectorintobenchpressfor now until we decide what to do with it.mhevery commentedon Jun 3, 2016
Reviewed with @tbosch and agreeing with the approach.
wardbell commentedon Jun 3, 2016
Please provide an illustration of how this affects the way I bootstrap my app and how I provide to components (especially
AppComponent).Imagine this (partial) application (semi-pseudo-coded):
mhevery commentedon Jun 3, 2016
@wardbell
bootstrapforces in browser compilation, so the above would not change. This will only come into play with offline-compilation.wardbell commentedon Jun 4, 2016
That's what we all wanted to hear :-)
tbosch commentedon Jun 6, 2016
The pattern of a config that combines a set of statically known providers with a set of unknown providers is also usable for defining the configuration of a library.
E.g.
This corresponds to the pattern of creating provider functions like this
However, in this case, the config is only part of the total config of an application. The encapsulation into a
Configclass is used because:These benefits show more the more input tokens a library has
The following proposes a way to compose configurations:
I.e. we support method injection to create Config objects and automatically include the static providers of these configs in the main injector as well.
In components, configs can also be used as follows:
Configs can be included in other configs or providers of components / directives via
@Configures(to include into configs) or{configures: ...}for components / directives. Syntax:@Configures(<ConfigClass>, {useValue: ..., useFactory: ...});tbosch commentedon Jun 6, 2016
Note from Igor: Right now we only have
@Injectas a verb for a decorator. However, we can't use@Provideror@Injectoras Angular already exports the classesInjectorandProviderwhich would collide.tbosch commentedon Jun 6, 2016
Maybe we can determine the argument to
@Configuresand@Providesusing the result of the method by default, so the user has less to write.__metadata('design:returntype', ...)wardbell commentedon Jun 6, 2016
This is jibberish to me. I have no idea what problem we're solving or what the consequences are.
I'm getting feedback from community confirming that this mystifies ... and frightens ... that community.
This proposal adds new terms and complexities that we were previously unaware of.
Where do we go to find out whether this is important and what it means?
shlomiassaf commentedon Jun 6, 2016
The proposal provides better encapsulation and better static analysis for libraries/modules, the tradeoff is adding more depth into libraries making it harder to understand them which has an impact since their usually OS projects.
The result might be simple library integration for the end user.
I admit it looks complex and a lot of new terms for DI.
[-]Codegen App Injector / remove ReflectiveInjector and Reflector[/-][+]Codegen App Injector / remove ReflectiveInjector and Reflector for offline compile[/+]21 remaining items
feat(compiler): introduce generated injectors
feat(compiler): introduce `@InjectorModule` and generated injectors
tbosch commentedon Jul 7, 2016
We have
AppModules now, which generate the bootstrap injector. See #9726.angular-automatic-lock-bot commentedon Sep 9, 2019
This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.
Read more about our automatic conversation locking policy.
This action has been performed automatically by a bot.