diff --git a/integration/_payload-limits.json b/integration/_payload-limits.json
index 6866a62790..0a7c71295a 100644
--- a/integration/_payload-limits.json
+++ b/integration/_payload-limits.json
@@ -3,7 +3,7 @@
"master": {
"uncompressed": {
"inline": 1447,
- "main": 154185,
+ "main": 159944,
"polyfills": 59179
}
}
@@ -11,7 +11,7 @@
"hello_world__closure": {
"master": {
"uncompressed": {
- "bundle": 101744
+ "bundle": 105779
}
}
},
diff --git a/integration/injectable-def/package.json b/integration/injectable-def/package.json
new file mode 100644
index 0000000000..6b48359758
--- /dev/null
+++ b/integration/injectable-def/package.json
@@ -0,0 +1,29 @@
+{
+ "name": "angular-integration",
+ "version": "0.0.0",
+ "license": "MIT",
+ "dependencies": {
+ "@angular/animations": "file:../../dist/packages-dist/animations",
+ "@angular/common": "file:../../dist/packages-dist/common",
+ "@angular/compiler": "file:../../dist/packages-dist/compiler",
+ "@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli",
+ "@angular/core": "file:../../dist/packages-dist/core",
+ "@angular/http": "file:../../dist/packages-dist/http",
+ "@angular/platform-browser": "file:../../dist/packages-dist/platform-browser",
+ "@angular/platform-browser-dynamic": "file:../../dist/packages-dist/platform-browser-dynamic",
+ "@angular/platform-server": "file:../../dist/packages-dist/platform-server",
+ "@types/node": "^9.4.0",
+ "rxjs": "file:../../node_modules/rxjs",
+ "typescript": "file:../../node_modules/typescript",
+ "zone.js": "file:../../node_modules/zone.js"
+ },
+ "devDependencies": {
+ "@types/jasmine": "2.5.41",
+ "concurrently": "3.4.0",
+ "lite-server": "2.2.2",
+ "protractor": "file:../../node_modules/protractor"
+ },
+ "scripts": {
+ "test": "./test.sh"
+ }
+}
diff --git a/integration/injectable-def/src/app.ts b/integration/injectable-def/src/app.ts
new file mode 100644
index 0000000000..bc93d3432e
--- /dev/null
+++ b/integration/injectable-def/src/app.ts
@@ -0,0 +1,21 @@
+import {Component, NgModule} from '@angular/core';
+import {BrowserModule} from '@angular/platform-browser';
+import {ServerModule} from '@angular/platform-server';
+import {Lib2Module} from 'lib2_built';
+
+@Component({
+ selector: 'test-app',
+ template: '',
+})
+export class TestApp {}
+
+@NgModule({
+ declarations: [TestApp],
+ bootstrap: [TestApp],
+ imports: [
+ Lib2Module,
+ BrowserModule.withServerTransition({appId: 'appId'}),
+ ServerModule,
+ ],
+})
+export class AppModule {}
diff --git a/integration/injectable-def/src/lib1.ts b/integration/injectable-def/src/lib1.ts
new file mode 100644
index 0000000000..093339129f
--- /dev/null
+++ b/integration/injectable-def/src/lib1.ts
@@ -0,0 +1,10 @@
+import {Injectable, NgModule} from '@angular/core';
+
+@NgModule({})
+export class Lib1Module {}
+
+@Injectable({scope: Lib1Module})
+export class Service {
+ static instance = 0;
+ readonly instance = Service.instance++;
+}
diff --git a/integration/injectable-def/src/lib2.ts b/integration/injectable-def/src/lib2.ts
new file mode 100644
index 0000000000..ebf7ce9bdd
--- /dev/null
+++ b/integration/injectable-def/src/lib2.ts
@@ -0,0 +1,23 @@
+import {Component, Injector, NgModule} from '@angular/core';
+import {Lib1Module, Service} from 'lib1_built';
+
+@Component({
+ selector: 'test-cmp',
+ template: '{{instance1}}:{{instance2}}',
+})
+export class TestCmp {
+ instance1: number;
+ instance2: number;
+
+ constructor(service: Service, injector: Injector) {
+ this.instance1 = service.instance;
+ this.instance2 = injector.get(Service).instance;
+ }
+}
+
+@NgModule({
+ declarations: [TestCmp],
+ exports: [TestCmp],
+ imports: [Lib1Module],
+})
+export class Lib2Module {}
diff --git a/integration/injectable-def/src/main.ts b/integration/injectable-def/src/main.ts
new file mode 100644
index 0000000000..85cb135d51
--- /dev/null
+++ b/integration/injectable-def/src/main.ts
@@ -0,0 +1,21 @@
+import 'zone.js/dist/zone-node';
+
+import {enableProdMode} from '@angular/core';
+import {renderModuleFactory} from '@angular/platform-server';
+import {AppModuleNgFactory} from './app.ngfactory';
+
+enableProdMode();
+renderModuleFactory(AppModuleNgFactory, {
+ document: '',
+ url: '/',
+}).then(html => {
+ if (/>0:0 {
+ console.error(err);
+ process.exit(2);
+})
\ No newline at end of file
diff --git a/integration/injectable-def/src/package-lib1.json b/integration/injectable-def/src/package-lib1.json
new file mode 100644
index 0000000000..bbed601241
--- /dev/null
+++ b/integration/injectable-def/src/package-lib1.json
@@ -0,0 +1,7 @@
+{
+ "name": "lib1_built",
+ "version": "0.0.0",
+ "license": "MIT",
+ "main": "./src/index.js",
+ "typings": "./src/index.d.ts"
+}
\ No newline at end of file
diff --git a/integration/injectable-def/src/package-lib2.json b/integration/injectable-def/src/package-lib2.json
new file mode 100644
index 0000000000..e6d2dbaa09
--- /dev/null
+++ b/integration/injectable-def/src/package-lib2.json
@@ -0,0 +1,7 @@
+{
+ "name": "lib2_built",
+ "version": "0.0.0",
+ "license": "MIT",
+ "main": "./src/index.js",
+ "typings": "./src/index.d.ts"
+}
\ No newline at end of file
diff --git a/integration/injectable-def/test.sh b/integration/injectable-def/test.sh
new file mode 100755
index 0000000000..e0bceaeb07
--- /dev/null
+++ b/integration/injectable-def/test.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+set -e -x
+
+NPM_BIN=$(npm bin)
+PATH="$PATH:${NPM_BIN}"
+
+rm -rf node_modules/lib1_built node_modules/lib2_built dist/
+
+ngc -p tsconfig-lib1.json
+cp src/package-lib1.json node_modules/lib1_built/package.json
+
+ngc -p tsconfig-lib2.json
+cp src/package-lib2.json node_modules/lib2_built/package.json
+
+ngc -p tsconfig-app.json
+
+node ./dist/src/main.js
diff --git a/integration/injectable-def/tsconfig-app.json b/integration/injectable-def/tsconfig-app.json
new file mode 100644
index 0000000000..bb53b78664
--- /dev/null
+++ b/integration/injectable-def/tsconfig-app.json
@@ -0,0 +1,17 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "module": "commonjs",
+ "moduleResolution": "node",
+ "lib": ["es2015", "dom"],
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true,
+ "outDir": "dist",
+ "types": ["node"],
+ "rootDir": "."
+ },
+ "files": [
+ "src/app.ts",
+ "src/main.ts"
+ ]
+}
\ No newline at end of file
diff --git a/integration/injectable-def/tsconfig-lib1.json b/integration/injectable-def/tsconfig-lib1.json
new file mode 100644
index 0000000000..f51660e935
--- /dev/null
+++ b/integration/injectable-def/tsconfig-lib1.json
@@ -0,0 +1,22 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "module": "commonjs",
+ "declaration": true,
+ "moduleResolution": "node",
+ "lib": ["es2015", "dom"],
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true,
+ "outDir": "node_modules/lib1_built",
+ "types": [],
+ "rootDir": "."
+ },
+ "files": [
+ "src/lib1.ts"
+ ],
+ "angularCompilerOptions": {
+ "skipTemplateCodegen": true,
+ "flatModuleOutFile": "index.js",
+ "flatModuleId": "lib1_built"
+ }
+}
\ No newline at end of file
diff --git a/integration/injectable-def/tsconfig-lib2.json b/integration/injectable-def/tsconfig-lib2.json
new file mode 100644
index 0000000000..c0bf416395
--- /dev/null
+++ b/integration/injectable-def/tsconfig-lib2.json
@@ -0,0 +1,22 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "module": "commonjs",
+ "declaration": true,
+ "moduleResolution": "node",
+ "lib": ["es2015", "dom"],
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true,
+ "outDir": "node_modules/lib2_built",
+ "types": [],
+ "rootDir": "."
+ },
+ "files": [
+ "src/lib2.ts"
+ ],
+ "angularCompilerOptions": {
+ "skipTemplateCodegen": true,
+ "flatModuleId": "lib2_built",
+ "flatModuleOutFile": "index.js"
+ }
+}
\ No newline at end of file
diff --git a/packages/compiler-cli/integrationtest/bazel/injectable_def/app/BUILD.bazel b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/BUILD.bazel
new file mode 100644
index 0000000000..daf7755a35
--- /dev/null
+++ b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/BUILD.bazel
@@ -0,0 +1,21 @@
+package(default_visibility = ["//visibility:public"])
+
+load("//tools:defaults.bzl", "ng_module", "ts_library")
+load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test")
+
+ng_module(
+ name = "app",
+ srcs = glob(
+ [
+ "src/**/*.ts",
+ ],
+ ),
+ module_name = "app_built",
+ deps = [
+ "//packages/compiler-cli/integrationtest/bazel/injectable_def/lib2",
+ "//packages/core",
+ "//packages/platform-browser",
+ "//packages/platform-server",
+ "@rxjs",
+ ],
+)
diff --git a/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/basic.ts b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/basic.ts
new file mode 100644
index 0000000000..899eccc89d
--- /dev/null
+++ b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/basic.ts
@@ -0,0 +1,31 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import {Component, NgModule} from '@angular/core';
+import {BrowserModule} from '@angular/platform-browser';
+import {ServerModule} from '@angular/platform-server';
+import {Lib2Module} from 'lib2_built/module';
+
+@Component({
+ selector: 'id-app',
+ template: '',
+})
+export class AppComponent {
+}
+
+@NgModule({
+ imports: [
+ Lib2Module,
+ BrowserModule.withServerTransition({appId: 'id-app'}),
+ ServerModule,
+ ],
+ declarations: [AppComponent],
+ bootstrap: [AppComponent],
+})
+export class BasicAppModule {
+}
diff --git a/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/hierarchy.ts b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/hierarchy.ts
new file mode 100644
index 0000000000..86daf88798
--- /dev/null
+++ b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/hierarchy.ts
@@ -0,0 +1,44 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import {Component, Injectable, NgModule, Optional, Self} from '@angular/core';
+import {BrowserModule} from '@angular/platform-browser';
+import {ServerModule} from '@angular/platform-server';
+
+@Injectable()
+export class Service {
+}
+
+@Component({
+ selector: 'hierarchy-app',
+ template: '',
+ providers: [Service],
+})
+export class AppComponent {
+}
+
+@Component({
+ selector: 'child-cmp',
+ template: '{{found}}',
+})
+export class ChildComponent {
+ found: boolean;
+
+ constructor(@Optional() @Self() service: Service|null) { this.found = !!service; }
+}
+
+@NgModule({
+ imports: [
+ BrowserModule.withServerTransition({appId: 'hierarchy-app'}),
+ ServerModule,
+ ],
+ declarations: [AppComponent, ChildComponent],
+ bootstrap: [AppComponent],
+})
+export class HierarchyAppModule {
+}
diff --git a/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/self.ts b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/self.ts
new file mode 100644
index 0000000000..9a211bc030
--- /dev/null
+++ b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/self.ts
@@ -0,0 +1,41 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import {Component, Injectable, NgModule, Optional, Self} from '@angular/core';
+import {BrowserModule} from '@angular/platform-browser';
+import {ServerModule} from '@angular/platform-server';
+
+@Injectable()
+export class NormalService {
+ constructor(@Optional() @Self() readonly shakeable: ShakeableService|null) {}
+}
+
+@Component({
+ selector: 'self-app',
+ template: '{{found}}',
+})
+export class AppComponent {
+ found: boolean;
+ constructor(service: NormalService) { this.found = !!service.shakeable; }
+}
+
+@NgModule({
+ imports: [
+ BrowserModule.withServerTransition({appId: 'id-app'}),
+ ServerModule,
+ ],
+ declarations: [AppComponent],
+ bootstrap: [AppComponent],
+ providers: [NormalService],
+})
+export class SelfAppModule {
+}
+
+@Injectable({scope: SelfAppModule})
+export class ShakeableService {
+}
\ No newline at end of file
diff --git a/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/token.ts b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/token.ts
new file mode 100644
index 0000000000..ae07315cba
--- /dev/null
+++ b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/token.ts
@@ -0,0 +1,42 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import {Component, Inject, InjectionToken, NgModule, forwardRef} from '@angular/core';
+import {BrowserModule} from '@angular/platform-browser';
+import {ServerModule} from '@angular/platform-server';
+
+export interface IService { readonly data: string; }
+
+@Component({
+ selector: 'token-app',
+ template: '{{data}}',
+})
+export class AppComponent {
+ data: string;
+ constructor(@Inject(TOKEN) service: IService) { this.data = service.data; }
+}
+
+@NgModule({
+ imports: [
+ BrowserModule.withServerTransition({appId: 'id-app'}),
+ ServerModule,
+ ],
+ declarations: [AppComponent],
+ bootstrap: [AppComponent],
+ providers: [{provide: forwardRef(() => TOKEN), useClass: forwardRef(() => Service)}]
+})
+export class TokenAppModule {
+}
+
+export class Service { readonly data = 'fromToken'; }
+
+export const TOKEN = new InjectionToken('test', {
+ scope: TokenAppModule,
+ useClass: Service,
+ deps: [],
+});
diff --git a/packages/compiler-cli/integrationtest/bazel/injectable_def/app/test/BUILD.bazel b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/test/BUILD.bazel
new file mode 100644
index 0000000000..570c6fa0e9
--- /dev/null
+++ b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/test/BUILD.bazel
@@ -0,0 +1,30 @@
+package(default_visibility = ["//visibility:public"])
+
+load("//tools:defaults.bzl", "ts_library")
+load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test")
+
+ts_library(
+ name = "test_lib",
+ testonly = 1,
+ srcs = glob(
+ [
+ "**/*.ts",
+ ],
+ ),
+ deps = [
+ "//packages/compiler-cli/integrationtest/bazel/injectable_def/app",
+ "//packages/core",
+ "//packages/platform-server",
+ ],
+)
+
+jasmine_node_test(
+ name = "test",
+ bootstrap = ["angular/tools/testing/init_node_spec.js"],
+ deps = [
+ ":test_lib",
+ "//packages/platform-server",
+ "//packages/platform-server/testing",
+ "//tools/testing:node",
+ ],
+)
diff --git a/packages/compiler-cli/integrationtest/bazel/injectable_def/app/test/app_spec.ts b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/test/app_spec.ts
new file mode 100644
index 0000000000..3cf74df8f9
--- /dev/null
+++ b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/test/app_spec.ts
@@ -0,0 +1,58 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import {enableProdMode} from '@angular/core';
+import {renderModuleFactory} from '@angular/platform-server';
+import {BasicAppModuleNgFactory} from 'app_built/src/basic.ngfactory';
+import {HierarchyAppModuleNgFactory} from 'app_built/src/hierarchy.ngfactory';
+import {SelfAppModuleNgFactory} from 'app_built/src/self.ngfactory';
+import {TokenAppModuleNgFactory} from 'app_built/src/token.ngfactory';
+
+enableProdMode();
+
+describe('ngInjectableDef Bazel Integration', () => {
+ it('works in AOT', done => {
+ renderModuleFactory(BasicAppModuleNgFactory, {
+ document: '',
+ url: '/',
+ }).then(html => {
+ expect(html).toMatch(/>0:0<\//);
+ done();
+ });
+ });
+
+ it('@Self() works in component hierarchies', done => {
+ renderModuleFactory(HierarchyAppModuleNgFactory, {
+ document: '',
+ url: '/',
+ }).then(html => {
+ expect(html).toMatch(/>false<\//);
+ done();
+ });
+ });
+
+ it('@Optional() Self() resolves to @Injectable() scoped service', done => {
+ renderModuleFactory(SelfAppModuleNgFactory, {
+ document: '',
+ url: '/',
+ }).then(html => {
+ expect(html).toMatch(/>true<\//);
+ done();
+ });
+ });
+
+ it('InjectionToken ngInjectableDef works', done => {
+ renderModuleFactory(TokenAppModuleNgFactory, {
+ document: '',
+ url: '/',
+ }).then(html => {
+ expect(html).toMatch(/>fromToken<\//);
+ done();
+ });
+ });
+});
diff --git a/packages/compiler-cli/integrationtest/bazel/injectable_def/lib1/BUILD.bazel b/packages/compiler-cli/integrationtest/bazel/injectable_def/lib1/BUILD.bazel
new file mode 100644
index 0000000000..4caedba714
--- /dev/null
+++ b/packages/compiler-cli/integrationtest/bazel/injectable_def/lib1/BUILD.bazel
@@ -0,0 +1,17 @@
+package(default_visibility = ["//visibility:public"])
+
+load("//tools:defaults.bzl", "ng_module")
+
+ng_module(
+ name = "lib1",
+ srcs = glob(
+ [
+ "**/*.ts",
+ ],
+ ),
+ module_name = "lib1_built",
+ deps = [
+ "//packages/core",
+ "@rxjs",
+ ],
+)
diff --git a/packages/compiler-cli/integrationtest/bazel/injectable_def/lib1/module.ts b/packages/compiler-cli/integrationtest/bazel/injectable_def/lib1/module.ts
new file mode 100644
index 0000000000..297dc07804
--- /dev/null
+++ b/packages/compiler-cli/integrationtest/bazel/injectable_def/lib1/module.ts
@@ -0,0 +1,21 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import {Injectable, NgModule} from '@angular/core';
+
+@NgModule({})
+export class Lib1Module {
+}
+
+@Injectable({
+ scope: Lib1Module,
+})
+export class Service {
+ static instanceCount = 0;
+ instance = Service.instanceCount++;
+}
diff --git a/packages/compiler-cli/integrationtest/bazel/injectable_def/lib2/BUILD.bazel b/packages/compiler-cli/integrationtest/bazel/injectable_def/lib2/BUILD.bazel
new file mode 100644
index 0000000000..1c08147d30
--- /dev/null
+++ b/packages/compiler-cli/integrationtest/bazel/injectable_def/lib2/BUILD.bazel
@@ -0,0 +1,18 @@
+package(default_visibility = ["//visibility:public"])
+
+load("//tools:defaults.bzl", "ng_module")
+
+ng_module(
+ name = "lib2",
+ srcs = glob(
+ [
+ "**/*.ts",
+ ],
+ ),
+ module_name = "lib2_built",
+ deps = [
+ "//packages/compiler-cli/integrationtest/bazel/injectable_def/lib1",
+ "//packages/core",
+ "@rxjs",
+ ],
+)
diff --git a/packages/compiler-cli/integrationtest/bazel/injectable_def/lib2/module.ts b/packages/compiler-cli/integrationtest/bazel/injectable_def/lib2/module.ts
new file mode 100644
index 0000000000..ebeccb0a81
--- /dev/null
+++ b/packages/compiler-cli/integrationtest/bazel/injectable_def/lib2/module.ts
@@ -0,0 +1,32 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import {Component, Injector, NgModule} from '@angular/core';
+import {Lib1Module, Service} from 'lib1_built/module';
+
+@Component({
+ selector: 'lib2-cmp',
+ template: '{{instance1}}:{{instance2}}',
+})
+export class Lib2Cmp {
+ instance1: number = -1;
+ instance2: number = -1;
+
+ constructor(service: Service, injector: Injector) {
+ this.instance1 = service.instance;
+ this.instance2 = injector.get(Service).instance;
+ }
+}
+
+@NgModule({
+ declarations: [Lib2Cmp],
+ exports: [Lib2Cmp],
+ imports: [Lib1Module],
+})
+export class Lib2Module {
+}
diff --git a/packages/compiler-cli/src/transformers/program.ts b/packages/compiler-cli/src/transformers/program.ts
index ebe789dbd4..1e1864773c 100644
--- a/packages/compiler-cli/src/transformers/program.ts
+++ b/packages/compiler-cli/src/transformers/program.ts
@@ -7,7 +7,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {AotCompiler, AotCompilerHost, AotCompilerOptions, EmitterVisitorContext, FormattedMessageChain, GeneratedFile, MessageBundle, NgAnalyzedFile, NgAnalyzedModules, ParseSourceSpan, PartialModule, Position, Serializer, TypeScriptEmitter, Xliff, Xliff2, Xmb, core, createAotCompiler, getParseErrors, isFormattedError, isSyntaxError} from '@angular/compiler';
+import {AotCompiler, AotCompilerHost, AotCompilerOptions, EmitterVisitorContext, FormattedMessageChain, GeneratedFile, MessageBundle, NgAnalyzedFile, NgAnalyzedFileWithInjectables, NgAnalyzedModules, ParseSourceSpan, PartialModule, Position, Serializer, TypeScriptEmitter, Xliff, Xliff2, Xmb, core, createAotCompiler, getParseErrors, isFormattedError, isSyntaxError} from '@angular/compiler';
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
@@ -22,7 +22,7 @@ import {MetadataCache, MetadataTransformer} from './metadata_cache';
import {getAngularEmitterTransformFactory} from './node_emitter_transform';
import {PartialModuleMetadataTransformer} from './r3_metadata_transform';
import {getAngularClassTransformerFactory} from './r3_transform';
-import {GENERATED_FILES, StructureIsReused, createMessageDiagnostic, isInRootDir, ngToTsDiagnostic, tsStructureIsReused, userError} from './util';
+import {DTS, GENERATED_FILES, StructureIsReused, TS, createMessageDiagnostic, isInRootDir, ngToTsDiagnostic, tsStructureIsReused, userError} from './util';
@@ -62,6 +62,7 @@ class AngularCompilerProgram implements Program {
private _hostAdapter: TsCompilerAotCompilerTypeCheckHostAdapter;
private _tsProgram: ts.Program;
private _analyzedModules: NgAnalyzedModules|undefined;
+ private _analyzedInjectables: NgAnalyzedFileWithInjectables[]|undefined;
private _structuralDiagnostics: Diagnostic[]|undefined;
private _programWithStubs: ts.Program|undefined;
private _optionsDiagnostics: Diagnostic[] = [];
@@ -191,13 +192,15 @@ class AngularCompilerProgram implements Program {
}
return Promise.resolve()
.then(() => {
- const {tmpProgram, sourceFiles, rootNames} = this._createProgramWithBasicStubs();
- return this.compiler.loadFilesAsync(sourceFiles).then(analyzedModules => {
- if (this._analyzedModules) {
- throw new Error('Angular structure loaded both synchronously and asynchronously');
- }
- this._updateProgramWithTypeCheckStubs(tmpProgram, analyzedModules, rootNames);
- });
+ const {tmpProgram, sourceFiles, tsFiles, rootNames} = this._createProgramWithBasicStubs();
+ return this.compiler.loadFilesAsync(sourceFiles, tsFiles)
+ .then(({analyzedModules, analyzedInjectables}) => {
+ if (this._analyzedModules) {
+ throw new Error('Angular structure loaded both synchronously and asynchronously');
+ }
+ this._updateProgramWithTypeCheckStubs(
+ tmpProgram, analyzedModules, analyzedInjectables, rootNames);
+ });
})
.catch(e => this._createProgramOnError(e));
}
@@ -304,8 +307,12 @@ class AngularCompilerProgram implements Program {
}
this.writeFile(outFileName, outData, writeByteOrderMark, onError, genFile, sourceFiles);
};
- const tsCustomTransformers = this.calculateTransforms(
- genFileByFileName, /* partialModules */ undefined, customTransformers);
+
+ const modules = this._analyzedInjectables &&
+ this.compiler.emitAllPartialModules2(this._analyzedInjectables);
+
+ const tsCustomTransformers =
+ this.calculateTransforms(genFileByFileName, modules, customTransformers);
const emitOnlyDtsFiles = (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS;
// Restore the original references before we emit so TypeScript doesn't emit
// a reference to the .d.ts file.
@@ -491,9 +498,11 @@ class AngularCompilerProgram implements Program {
return;
}
try {
- const {tmpProgram, sourceFiles, rootNames} = this._createProgramWithBasicStubs();
- const analyzedModules = this.compiler.loadFilesSync(sourceFiles);
- this._updateProgramWithTypeCheckStubs(tmpProgram, analyzedModules, rootNames);
+ const {tmpProgram, sourceFiles, tsFiles, rootNames} = this._createProgramWithBasicStubs();
+ const {analyzedModules, analyzedInjectables} =
+ this.compiler.loadFilesSync(sourceFiles, tsFiles);
+ this._updateProgramWithTypeCheckStubs(
+ tmpProgram, analyzedModules, analyzedInjectables, rootNames);
} catch (e) {
this._createProgramOnError(e);
}
@@ -520,6 +529,7 @@ class AngularCompilerProgram implements Program {
tmpProgram: ts.Program,
rootNames: string[],
sourceFiles: string[],
+ tsFiles: string[],
} {
if (this._analyzedModules) {
throw new Error(`Internal Error: already initialized!`);
@@ -553,17 +563,23 @@ class AngularCompilerProgram implements Program {
const tmpProgram = ts.createProgram(rootNames, this.options, this.hostAdapter, oldTsProgram);
const sourceFiles: string[] = [];
+ const tsFiles: string[] = [];
tmpProgram.getSourceFiles().forEach(sf => {
if (this.hostAdapter.isSourceFile(sf.fileName)) {
sourceFiles.push(sf.fileName);
}
+ if (TS.test(sf.fileName) && !DTS.test(sf.fileName)) {
+ tsFiles.push(sf.fileName);
+ }
});
- return {tmpProgram, sourceFiles, rootNames};
+ return {tmpProgram, sourceFiles, tsFiles, rootNames};
}
private _updateProgramWithTypeCheckStubs(
- tmpProgram: ts.Program, analyzedModules: NgAnalyzedModules, rootNames: string[]) {
+ tmpProgram: ts.Program, analyzedModules: NgAnalyzedModules,
+ analyzedInjectables: NgAnalyzedFileWithInjectables[], rootNames: string[]) {
this._analyzedModules = analyzedModules;
+ this._analyzedInjectables = analyzedInjectables;
tmpProgram.getSourceFiles().forEach(sf => {
if (sf.fileName.endsWith('.ngfactory.ts')) {
const {generate, baseFileName} = this.hostAdapter.shouldGenerateFile(sf.fileName);
diff --git a/packages/compiler-cli/src/transformers/r3_transform.ts b/packages/compiler-cli/src/transformers/r3_transform.ts
index c162c62c7c..5c21b8bc82 100644
--- a/packages/compiler-cli/src/transformers/r3_transform.ts
+++ b/packages/compiler-cli/src/transformers/r3_transform.ts
@@ -26,7 +26,7 @@ export function getAngularClassTransformerFactory(modules: PartialModule[]): Tra
return function(context: ts.TransformationContext) {
return function(sourceFile: ts.SourceFile): ts.SourceFile {
const module = moduleMap.get(sourceFile.fileName);
- if (module) {
+ if (module && module.statements.length > 0) {
const [newSourceFile] = updateSourceFile(sourceFile, module, context);
return newSourceFile;
}
diff --git a/packages/compiler-cli/src/transformers/util.ts b/packages/compiler-cli/src/transformers/util.ts
index 858be64594..8d35326e27 100644
--- a/packages/compiler-cli/src/transformers/util.ts
+++ b/packages/compiler-cli/src/transformers/util.ts
@@ -14,6 +14,7 @@ import {CompilerOptions, DEFAULT_ERROR_CODE, Diagnostic, SOURCE} from './api';
export const GENERATED_FILES = /(.*?)\.(ngfactory|shim\.ngstyle|ngstyle|ngsummary)\.(js|d\.ts|ts)$/;
export const DTS = /\.d\.ts$/;
+export const TS = /^(?!.*\.d\.ts$).*\.ts$/;
export const enum StructureIsReused {Not = 0, SafeModules = 1, Completely = 2}
diff --git a/packages/compiler-cli/test/ngc_spec.ts b/packages/compiler-cli/test/ngc_spec.ts
index 834e87d6d3..de60f5630a 100644
--- a/packages/compiler-cli/test/ngc_spec.ts
+++ b/packages/compiler-cli/test/ngc_spec.ts
@@ -1942,4 +1942,157 @@ describe('ngc transformer command-line', () => {
expect(emittedFile('hello-world.js')).toContain('ngComponentDef');
});
});
+
+ describe('tree shakeable services', () => {
+
+ function compileService(source: string): string {
+ write('service.ts', source);
+
+ const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
+ expect(exitCode).toEqual(0);
+
+ const servicePath = path.resolve(outDir, 'service.js');
+ return fs.readFileSync(servicePath, 'utf8');
+ }
+
+ beforeEach(() => {
+ writeConfig(`{
+ "extends": "./tsconfig-base.json",
+ "files": ["service.ts"]
+ }`);
+ write('module.ts', `
+ import {NgModule} from '@angular/core';
+
+ @NgModule({})
+ export class Module {}
+ `);
+ });
+
+ describe(`doesn't break existing injectables`, () => {
+ it('on simple services', () => {
+ const source = compileService(`
+ import {Injectable, NgModule} from '@angular/core';
+
+ @Injectable()
+ export class Service {
+ constructor(public param: string) {}
+ }
+
+ @NgModule({
+ providers: [{provide: Service, useValue: new Service('test')}],
+ })
+ export class ServiceModule {}
+ `);
+ expect(source).not.toMatch(/ngInjectableDef/);
+ });
+ it('on a service with a base class service', () => {
+ const source = compileService(`
+ import {Injectable, NgModule} from '@angular/core';
+
+ @Injectable()
+ export class Dep {}
+
+ export class Base {
+ constructor(private dep: Dep) {}
+ }
+ @Injectable()
+ export class Service extends Base {}
+
+ @NgModule({
+ providers: [Service],
+ })
+ export class ServiceModule {}
+ `);
+ expect(source).not.toMatch(/ngInjectableDef/);
+ });
+ });
+
+ it('compiles a basic InjectableDef', () => {
+ const source = compileService(`
+ import {Injectable} from '@angular/core';
+ import {Module} from './module';
+
+ @Injectable({
+ scope: Module,
+ })
+ export class Service {}
+ `);
+ expect(source).toMatch(/ngInjectableDef = .+\.defineInjectable\(/);
+ expect(source).toMatch(/ngInjectableDef.*token: Service/);
+ expect(source).toMatch(/ngInjectableDef.*scope: .+\.Module/);
+ });
+
+ it('compiles a useValue InjectableDef', () => {
+ const source = compileService(`
+ import {Injectable} from '@angular/core';
+ import {Module} from './module';
+
+ export const CONST_SERVICE: Service = null;
+
+ @Injectable({
+ scope: Module,
+ useValue: CONST_SERVICE
+ })
+ export class Service {}
+ `);
+ expect(source).toMatch(/ngInjectableDef.*return CONST_SERVICE/);
+ });
+
+ it('compiles a useExisting InjectableDef', () => {
+ const source = compileService(`
+ import {Injectable} from '@angular/core';
+ import {Module} from './module';
+
+ @Injectable()
+ export class Existing {}
+
+ @Injectable({
+ scope: Module,
+ useExisting: Existing,
+ })
+ export class Service {}
+ `);
+ expect(source).toMatch(/ngInjectableDef.*return ..\.inject\(Existing\)/);
+ });
+
+ it('compiles a useFactory InjectableDef with optional dep', () => {
+ const source = compileService(`
+ import {Injectable, Optional} from '@angular/core';
+ import {Module} from './module';
+
+ @Injectable()
+ export class Existing {}
+
+ @Injectable({
+ scope: Module,
+ useFactory: (existing: Existing|null) => new Service(existing),
+ deps: [[new Optional(), Existing]],
+ })
+ export class Service {
+ constructor(e: Existing|null) {}
+ }
+ `);
+ expect(source).toMatch(/ngInjectableDef.*return ..\(..\.inject\(Existing, null, 0\)/);
+ });
+
+ it('compiles a useFactory InjectableDef with skip-self dep', () => {
+ const source = compileService(`
+ import {Injectable, SkipSelf} from '@angular/core';
+ import {Module} from './module';
+
+ @Injectable()
+ export class Existing {}
+
+ @Injectable({
+ scope: Module,
+ useFactory: (existing: Existing) => new Service(existing),
+ deps: [[new SkipSelf(), Existing]],
+ })
+ export class Service {
+ constructor(e: Existing) {}
+ }
+ `);
+ expect(source).toMatch(/ngInjectableDef.*return ..\(..\.inject\(Existing, undefined, 1\)/);
+ });
+ });
});
diff --git a/packages/compiler/src/aot/compiler.ts b/packages/compiler/src/aot/compiler.ts
index c8f1787c99..98814d35eb 100644
--- a/packages/compiler/src/aot/compiler.ts
+++ b/packages/compiler/src/aot/compiler.ts
@@ -6,12 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileNgModuleSummary, CompilePipeMetadata, CompilePipeSummary, CompileProviderMetadata, CompileStylesheetMetadata, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, componentFactoryName, flatten, identifierName, templateSourceUrl, tokenReference} from '../compile_metadata';
+import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileInjectableMetadata, CompileNgModuleMetadata, CompileNgModuleSummary, CompilePipeMetadata, CompilePipeSummary, CompileProviderMetadata, CompileStylesheetMetadata, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, componentFactoryName, flatten, identifierName, templateSourceUrl, tokenReference} from '../compile_metadata';
import {CompilerConfig} from '../config';
import {ConstantPool} from '../constant_pool';
import {ViewEncapsulation} from '../core';
import {MessageBundle} from '../i18n/message_bundle';
import {Identifiers, createTokenForExternalReference} from '../identifiers';
+import {InjectableCompiler} from '../injectable_compiler';
import {CompileMetadataResolver} from '../metadata_resolver';
import {HtmlParser} from '../ml_parser/html_parser';
import {InterpolationConfig} from '../ml_parser/interpolation_config';
@@ -49,6 +50,7 @@ export class AotCompiler {
private _templateAstCache =
new Map();
private _analyzedFiles = new Map();
+ private _analyzedFilesForInjectables = new Map();
constructor(
private _config: CompilerConfig, private _options: AotCompilerOptions,
@@ -56,7 +58,7 @@ export class AotCompiler {
private _metadataResolver: CompileMetadataResolver, private _templateParser: TemplateParser,
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
private _typeCheckCompiler: TypeCheckCompiler, private _ngModuleCompiler: NgModuleCompiler,
- private _outputEmitter: OutputEmitter,
+ private _injectableCompiler: InjectableCompiler, private _outputEmitter: OutputEmitter,
private _summaryResolver: SummaryResolver,
private _symbolResolver: StaticSymbolResolver) {}
@@ -91,6 +93,16 @@ export class AotCompiler {
return analyzedFile;
}
+ private _analyzeFileForInjectables(fileName: string): NgAnalyzedFileWithInjectables {
+ let analyzedFile = this._analyzedFilesForInjectables.get(fileName);
+ if (!analyzedFile) {
+ analyzedFile = analyzeFileForInjectables(
+ this._host, this._symbolResolver, this._metadataResolver, fileName);
+ this._analyzedFilesForInjectables.set(fileName, analyzedFile);
+ }
+ return analyzedFile;
+ }
+
findGeneratedFileNames(fileName: string): string[] {
const genFileNames: string[] = [];
const file = this._analyzeFile(fileName);
@@ -174,7 +186,8 @@ export class AotCompiler {
null;
}
- loadFilesAsync(fileNames: string[]): Promise {
+ loadFilesAsync(fileNames: string[], tsFiles: string[]): Promise<
+ {analyzedModules: NgAnalyzedModules, analyzedInjectables: NgAnalyzedFileWithInjectables[]}> {
const files = fileNames.map(fileName => this._analyzeFile(fileName));
const loadingPromises: Promise[] = [];
files.forEach(
@@ -182,16 +195,25 @@ export class AotCompiler {
ngModule =>
loadingPromises.push(this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
ngModule.type.reference, false))));
- return Promise.all(loadingPromises).then(_ => mergeAndValidateNgFiles(files));
+ const analyzedInjectables = tsFiles.map(tsFile => this._analyzeFileForInjectables(tsFile));
+ return Promise.all(loadingPromises).then(_ => ({
+ analyzedModules: mergeAndValidateNgFiles(files),
+ analyzedInjectables: analyzedInjectables,
+ }));
}
- loadFilesSync(fileNames: string[]): NgAnalyzedModules {
+ loadFilesSync(fileNames: string[], tsFiles: string[]):
+ {analyzedModules: NgAnalyzedModules, analyzedInjectables: NgAnalyzedFileWithInjectables[]} {
const files = fileNames.map(fileName => this._analyzeFile(fileName));
files.forEach(
file => file.ngModules.forEach(
ngModule => this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
ngModule.type.reference, true)));
- return mergeAndValidateNgFiles(files);
+ const analyzedInjectables = tsFiles.map(tsFile => this._analyzeFileForInjectables(tsFile));
+ return {
+ analyzedModules: mergeAndValidateNgFiles(files),
+ analyzedInjectables: analyzedInjectables,
+ };
}
private _createNgFactoryStub(
@@ -320,7 +342,7 @@ export class AotCompiler {
private _emitPartialModule(
fileName: string, ngModuleByPipeOrDirective: Map,
directives: StaticSymbol[], pipes: StaticSymbol[], ngModules: CompileNgModuleMetadata[],
- injectables: StaticSymbol[]): PartialModule[] {
+ injectables: CompileInjectableMetadata[]): PartialModule[] {
const classes: o.ClassStmt[] = [];
const context = this._createOutputContext(fileName);
@@ -342,7 +364,29 @@ export class AotCompiler {
}
});
- if (context.statements) {
+ injectables.forEach(injectable => this._injectableCompiler.compile(injectable, context));
+
+ if (context.statements && context.statements.length > 0) {
+ return [{fileName, statements: [...context.constantPool.statements, ...context.statements]}];
+ }
+ return [];
+ }
+
+ emitAllPartialModules2(files: NgAnalyzedFileWithInjectables[]): PartialModule[] {
+ // Using reduce like this is a select many pattern (where map is a select pattern)
+ return files.reduce((r, file) => {
+ r.push(...this._emitPartialModule2(file.fileName, file.injectables));
+ return r;
+ }, []);
+ }
+
+ private _emitPartialModule2(fileName: string, injectables: CompileInjectableMetadata[]):
+ PartialModule[] {
+ const context = this._createOutputContext(fileName);
+
+ injectables.forEach(injectable => this._injectableCompiler.compile(injectable, context));
+
+ if (context.statements && context.statements.length > 0) {
return [{fileName, statements: [...context.constantPool.statements, ...context.statements]}];
}
return [];
@@ -360,7 +404,7 @@ export class AotCompiler {
private _compileImplFile(
srcFileUrl: string, ngModuleByPipeOrDirective: Map,
directives: StaticSymbol[], pipes: StaticSymbol[], ngModules: CompileNgModuleMetadata[],
- injectables: StaticSymbol[]): GeneratedFile[] {
+ injectables: CompileInjectableMetadata[]): GeneratedFile[] {
const fileSuffix = normalizeGenFileSuffix(splitTypescriptSuffix(srcFileUrl, true)[1]);
const generatedFiles: GeneratedFile[] = [];
@@ -414,7 +458,7 @@ export class AotCompiler {
private _createSummary(
srcFileName: string, directives: StaticSymbol[], pipes: StaticSymbol[],
- ngModules: CompileNgModuleMetadata[], injectables: StaticSymbol[],
+ ngModules: CompileNgModuleMetadata[], injectables: CompileInjectableMetadata[],
ngFactoryCtx: OutputContext): GeneratedFile[] {
const symbolSummaries = this._symbolResolver.getSymbolsOf(srcFileName)
.map(symbol => this._symbolResolver.resolveSymbol(symbol));
@@ -437,10 +481,11 @@ export class AotCompiler {
summary: this._metadataResolver.getPipeSummary(ref) !,
metadata: this._metadataResolver.getPipeMetadata(ref) !
})),
- ...injectables.map(ref => ({
- summary: this._metadataResolver.getInjectableSummary(ref) !,
- metadata: this._metadataResolver.getInjectableSummary(ref) !.type
- }))
+ ...injectables.map(
+ ref => ({
+ summary: this._metadataResolver.getInjectableSummary(ref.symbol) !,
+ metadata: this._metadataResolver.getInjectableSummary(ref.symbol) !.type
+ }))
];
const forJitOutputCtx = this._options.enableSummariesForJit ?
this._createOutputContext(summaryForJitFileName(srcFileName, true)) :
@@ -682,12 +727,17 @@ export interface NgAnalyzedModules {
symbolsMissingModule?: StaticSymbol[];
}
+export interface NgAnalyzedFileWithInjectables {
+ fileName: string;
+ injectables: CompileInjectableMetadata[];
+}
+
export interface NgAnalyzedFile {
fileName: string;
directives: StaticSymbol[];
pipes: StaticSymbol[];
ngModules: CompileNgModuleMetadata[];
- injectables: StaticSymbol[];
+ injectables: CompileInjectableMetadata[];
exportsNonSourceFiles: boolean;
}
@@ -747,7 +797,7 @@ export function analyzeFile(
metadataResolver: CompileMetadataResolver, fileName: string): NgAnalyzedFile {
const directives: StaticSymbol[] = [];
const pipes: StaticSymbol[] = [];
- const injectables: StaticSymbol[] = [];
+ const injectables: CompileInjectableMetadata[] = [];
const ngModules: CompileNgModuleMetadata[] = [];
const hasDecorators = staticSymbolResolver.hasDecorators(fileName);
let exportsNonSourceFiles = false;
@@ -779,7 +829,10 @@ export function analyzeFile(
}
} else if (metadataResolver.isInjectable(symbol)) {
isNgSymbol = true;
- injectables.push(symbol);
+ const injectable = metadataResolver.getInjectableMetadata(symbol, null, false);
+ if (injectable) {
+ injectables.push(injectable);
+ }
}
}
if (!isNgSymbol) {
@@ -793,6 +846,32 @@ export function analyzeFile(
};
}
+export function analyzeFileForInjectables(
+ host: NgAnalyzeModulesHost, staticSymbolResolver: StaticSymbolResolver,
+ metadataResolver: CompileMetadataResolver, fileName: string): NgAnalyzedFileWithInjectables {
+ const injectables: CompileInjectableMetadata[] = [];
+ if (staticSymbolResolver.hasDecorators(fileName)) {
+ staticSymbolResolver.getSymbolsOf(fileName).forEach((symbol) => {
+ const resolvedSymbol = staticSymbolResolver.resolveSymbol(symbol);
+ const symbolMeta = resolvedSymbol.metadata;
+ if (!symbolMeta || symbolMeta.__symbolic === 'error') {
+ return;
+ }
+ let isNgSymbol = false;
+ if (symbolMeta.__symbolic === 'class') {
+ if (metadataResolver.isInjectable(symbol)) {
+ isNgSymbol = true;
+ const injectable = metadataResolver.getInjectableMetadata(symbol, null, false);
+ if (injectable) {
+ injectables.push(injectable);
+ }
+ }
+ }
+ });
+ }
+ return {fileName, injectables};
+}
+
function isValueExportingNonSourceFile(host: NgAnalyzeModulesHost, metadata: any): boolean {
let exportsNonSourceFiles = false;
diff --git a/packages/compiler/src/aot/compiler_factory.ts b/packages/compiler/src/aot/compiler_factory.ts
index 20dd4a941b..708cef7aa2 100644
--- a/packages/compiler/src/aot/compiler_factory.ts
+++ b/packages/compiler/src/aot/compiler_factory.ts
@@ -13,6 +13,7 @@ import {DirectiveResolver} from '../directive_resolver';
import {Lexer} from '../expression_parser/lexer';
import {Parser} from '../expression_parser/parser';
import {I18NHtmlParser} from '../i18n/i18n_html_parser';
+import {InjectableCompiler} from '../injectable_compiler';
import {CompileMetadataResolver} from '../metadata_resolver';
import {HtmlParser} from '../ml_parser/html_parser';
import {NgModuleCompiler} from '../ng_module_compiler';
@@ -90,7 +91,7 @@ export function createAotCompiler(
const compiler = new AotCompiler(
config, options, compilerHost, staticReflector, resolver, tmplParser,
new StyleCompiler(urlResolver), viewCompiler, typeCheckCompiler,
- new NgModuleCompiler(staticReflector), new TypeScriptEmitter(), summaryResolver,
- symbolResolver);
+ new NgModuleCompiler(staticReflector), new InjectableCompiler(staticReflector),
+ new TypeScriptEmitter(), summaryResolver, symbolResolver);
return {compiler, reflector: staticReflector};
}
diff --git a/packages/compiler/src/aot/static_reflector.ts b/packages/compiler/src/aot/static_reflector.ts
index c8349e517d..bc4380d6f8 100644
--- a/packages/compiler/src/aot/static_reflector.ts
+++ b/packages/compiler/src/aot/static_reflector.ts
@@ -124,6 +124,16 @@ export class StaticReflector implements CompileReflector {
return symbol;
}
+ public tryAnnotations(type: StaticSymbol): any[] {
+ const originalRecorder = this.errorRecorder;
+ this.errorRecorder = (error: any, fileName: string) => {};
+ try {
+ return this.annotations(type);
+ } finally {
+ this.errorRecorder = originalRecorder;
+ }
+ }
+
public annotations(type: StaticSymbol): any[] {
let annotations = this.annotationCache.get(type);
if (!annotations) {
@@ -331,6 +341,8 @@ export class StaticReflector implements CompileReflector {
}
private initializeConversionMap(): void {
+ this._registerDecoratorOrConstructor(
+ this.findDeclaration(ANGULAR_CORE, 'Injectable'), createInjectable);
this.injectionToken = this.findDeclaration(ANGULAR_CORE, 'InjectionToken');
this.opaqueToken = this.findDeclaration(ANGULAR_CORE, 'OpaqueToken');
this.ROUTES = this.tryFindDeclaration(ANGULAR_ROUTER, 'ROUTES');
@@ -338,8 +350,6 @@ export class StaticReflector implements CompileReflector {
this.findDeclaration(ANGULAR_CORE, 'ANALYZE_FOR_ENTRY_COMPONENTS');
this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Host'), createHost);
- this._registerDecoratorOrConstructor(
- this.findDeclaration(ANGULAR_CORE, 'Injectable'), createInjectable);
this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Self'), createSelf);
this._registerDecoratorOrConstructor(
this.findDeclaration(ANGULAR_CORE, 'SkipSelf'), createSkipSelf);
diff --git a/packages/compiler/src/aot/static_symbol_resolver.ts b/packages/compiler/src/aot/static_symbol_resolver.ts
index 0cafa222cd..641a96e4c2 100644
--- a/packages/compiler/src/aot/static_symbol_resolver.ts
+++ b/packages/compiler/src/aot/static_symbol_resolver.ts
@@ -12,6 +12,9 @@ import {ValueTransformer, visitValue} from '../util';
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
import {isGeneratedFile, stripSummaryForJitFileSuffix, stripSummaryForJitNameSuffix, summaryForJitFileName, summaryForJitName} from './util';
+const DTS = /\.d\.ts$/;
+const TS = /^(?!.*\.d\.ts$).*\.ts$/;
+
export class ResolvedStaticSymbol {
constructor(public symbol: StaticSymbol, public metadata: any) {}
}
@@ -374,7 +377,8 @@ export class StaticSymbolResolver {
// (e.g. their constructor parameters).
// We do this to prevent introducing deep imports
// as we didn't generate .ngfactory.ts files with proper reexports.
- if (this.summaryResolver.isLibraryFile(sourceSymbol.filePath) && metadata &&
+ const isTsFile = TS.test(sourceSymbol.filePath);
+ if (this.summaryResolver.isLibraryFile(sourceSymbol.filePath) && !isTsFile && metadata &&
metadata['__symbolic'] === 'class') {
const transformedMeta = {__symbolic: 'class', arity: metadata.arity};
return new ResolvedStaticSymbol(sourceSymbol, transformedMeta);
diff --git a/packages/compiler/src/compile_metadata.ts b/packages/compiler/src/compile_metadata.ts
index 33c930ea01..816494c8c7 100644
--- a/packages/compiler/src/compile_metadata.ts
+++ b/packages/compiler/src/compile_metadata.ts
@@ -136,6 +136,19 @@ export interface CompileTokenMetadata {
identifier?: CompileIdentifierMetadata|CompileTypeMetadata;
}
+export interface CompileInjectableMetadata {
+ symbol: StaticSymbol;
+ type: CompileTypeMetadata;
+
+ module?: StaticSymbol;
+
+ useValue?: any;
+ useClass?: StaticSymbol;
+ useExisting?: StaticSymbol;
+ useFactory?: StaticSymbol;
+ deps?: any[];
+}
+
/**
* Metadata regarding compilation of a type.
*/
diff --git a/packages/compiler/src/compile_reflector.ts b/packages/compiler/src/compile_reflector.ts
index 44970091a2..9700634fc5 100644
--- a/packages/compiler/src/compile_reflector.ts
+++ b/packages/compiler/src/compile_reflector.ts
@@ -15,6 +15,7 @@ import * as o from './output/output_ast';
export abstract class CompileReflector {
abstract parameters(typeOrFunc: /*Type*/ any): any[][];
abstract annotations(typeOrFunc: /*Type*/ any): any[];
+ abstract tryAnnotations(typeOrFunc: /*Type*/ any): any[];
abstract propMetadata(typeOrFunc: /*Type*/ any): {[key: string]: any[]};
abstract hasLifecycleHook(type: any, lcProperty: string): boolean;
abstract guards(typeOrFunc: /* Type */ any): {[key: string]: any};
diff --git a/packages/compiler/src/core.ts b/packages/compiler/src/core.ts
index 4f0161dcaf..264043cfeb 100644
--- a/packages/compiler/src/core.ts
+++ b/packages/compiler/src/core.ts
@@ -14,8 +14,8 @@
export interface Inject { token: any; }
export const createInject = makeMetadataFactory('Inject', (token: any) => ({token}));
-export const createInjectionToken =
- makeMetadataFactory