X Tutup
Skip to content

Codegen App Injector / remove ReflectiveInjector and Reflector for offline compile #8997

@tbosch

Description

@tbosch

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 ReflectiveInjector nor Reflector / ReflectionCapabilities
      • this drops about 30kb minified / 6.49k minified gzipped from angular core
    • we don't need the reflect-metadata polyfill 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.
  • 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/browser as this uses the RuntimeCompiler already, 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 CompilerConfig and 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).

Proposed changes to Angular besides the compiler changes

  • [BREAKING] move ReflectiveInjector and move it to @angular/common or 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 Reflector and ReflectionCapabilities from @angular/core to @angular/compiler as the
    compiler is the only place where these are used.
  • make ComponentResolver (an RuntimeCompiler) part of PlatformRef and don't use DI to create the providers of PlatformRef. Still expose an injector in PlatformRef which is just based on a Map.
  • [BREAKING] make platform pipes / platform directives part of CompilerConfig and define them as arrays of CompileIdentifierMetadata so they can be used for offline and runtime compile.
  • [BREAKING] pass CompilerConfig to browserPlatform() as argument instead of getting it via DI.
    • we might consider using a special BrowserCompileConfig class as some parts of CompilerConfig are 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 of CompileIdentifierMetadata)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      X Tutup