diff --git a/packages/compiler-cli/src/ngcc/test/rendering/renderer_spec.ts b/packages/compiler-cli/src/ngcc/test/rendering/renderer_spec.ts
index 0a8834371a..b3a5ee0bf7 100644
--- a/packages/compiler-cli/src/ngcc/test/rendering/renderer_spec.ts
+++ b/packages/compiler-cli/src/ngcc/test/rendering/renderer_spec.ts
@@ -128,7 +128,7 @@ describe('Renderer', () => {
}));
expect(addDefinitionsSpy.calls.first().args[2])
.toEqual(
- `A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""]], factory: function A_Factory(t) { return new (t || A)(); }, features: [ɵngcc0.ɵPublicFeature] });`);
+ `A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""]], factory: function A_Factory(t) { return new (t || A)(); } });`);
});
it('should call removeDecorators with the source code, a map of class decorators that have been analyzed',
diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/component.ts b/packages/compiler-cli/src/ngtsc/annotations/src/component.ts
index 293b72fba4..e68bdfd6ef 100644
--- a/packages/compiler-cli/src/ngtsc/annotations/src/component.ts
+++ b/packages/compiler-cli/src/ngtsc/annotations/src/component.ts
@@ -118,6 +118,10 @@ export class ComponentDecoratorHandler implements
preserveWhitespaces = value;
}
+ const viewProviders: Expression|null = component.has('viewProviders') ?
+ new WrappedNodeExpr(component.get('viewProviders') !) :
+ null;
+
// Go through the root directories for this project, and select the one with the smallest
// relative path representation.
const filePath = node.getSourceFile().fileName;
@@ -202,6 +206,7 @@ export class ComponentDecoratorHandler implements
directives: EMPTY_MAP,
wrapDirectivesInClosure: false, //
animations,
+ viewProviders
},
parsedTemplate: template.nodes,
},
diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts b/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts
index b47568b7f1..8bd57318f3 100644
--- a/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts
+++ b/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts
@@ -146,6 +146,9 @@ export function extractDirectiveMetadata(
const host = extractHostBindings(directive, decoratedElements, reflector, checker, coreModule);
+ const providers: Expression|null =
+ directive.has('providers') ? new WrappedNodeExpr(directive.get('providers') !) : null;
+
// Determine if `ngOnChanges` is a lifecycle hook defined on the component.
const usesOnChanges = members.some(
member => !member.isStatic && member.kind === ClassMemberKind.Method &&
@@ -176,7 +179,7 @@ export function extractDirectiveMetadata(
outputs: {...outputsFromMeta, ...outputsFromFields}, queries, selector,
type: new WrappedNodeExpr(clazz.name !),
typeArgumentCount: reflector.getGenericArityOfClass(clazz) || 0,
- typeSourceSpan: null !, usesInheritance, exportAs,
+ typeSourceSpan: null !, usesInheritance, exportAs, providers
};
return {decoratedElements, decorator: directive, metadata};
}
diff --git a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts
index fdaf6cd089..cc480b8a1d 100644
--- a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts
+++ b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts
@@ -415,7 +415,6 @@ describe('compiler compliance', () => {
factory: function MyComponent_Factory(t){
return new (t || MyComponent)();
},
- features: [$r3$.ɵPublicFeature],
consts: 1,
vars: 0,
template: function MyComponent_Template(rf,ctx){
@@ -470,7 +469,6 @@ describe('compiler compliance', () => {
type: ChildComponent,
selectors: [["child"]],
factory: function ChildComponent_Factory(t) { return new (t || ChildComponent)(); },
- features: [$r3$.ɵPublicFeature],
consts: 1,
vars: 0,
template: function ChildComponent_Template(rf, ctx) {
@@ -485,8 +483,7 @@ describe('compiler compliance', () => {
SomeDirective.ngDirectiveDef = $r3$.ɵdefineDirective({
type: SomeDirective,
selectors: [["", "some-directive", ""]],
- factory: function SomeDirective_Factory(t) {return new (t || SomeDirective)(); },
- features: [$r3$.ɵPublicFeature]
+ factory: function SomeDirective_Factory(t) {return new (t || SomeDirective)(); }
});
`;
@@ -498,7 +495,6 @@ describe('compiler compliance', () => {
type: MyComponent,
selectors: [["my-component"]],
factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); },
- features: [$r3$.ɵPublicFeature],
consts: 2,
vars: 0,
template: function MyComponent_Template(rf, ctx) {
@@ -543,8 +539,7 @@ describe('compiler compliance', () => {
SomeDirective.ngDirectiveDef = $r3$.ɵdefineDirective({
type: SomeDirective,
selectors: [["div", "some-directive", "", 8, "foo", 3, "title", "", 9, "baz"]],
- factory: function SomeDirective_Factory(t) {return new (t || SomeDirective)(); },
- features: [$r3$.ɵPublicFeature]
+ factory: function SomeDirective_Factory(t) {return new (t || SomeDirective)(); }
});
`;
@@ -553,8 +548,7 @@ describe('compiler compliance', () => {
OtherDirective.ngDirectiveDef = $r3$.ɵdefineDirective({
type: OtherDirective,
selectors: [["", 5, "span", "title", "", 9, "baz"]],
- factory: function OtherDirective_Factory(t) {return new (t || OtherDirective)(); },
- features: [$r3$.ɵPublicFeature]
+ factory: function OtherDirective_Factory(t) {return new (t || OtherDirective)(); }
});
`;
@@ -590,8 +584,7 @@ describe('compiler compliance', () => {
hostBindings: function HostBindingDir_HostBindings(dirIndex, elIndex) {
$r3$.ɵelementProperty(elIndex, "id", $r3$.ɵbind($r3$.ɵload(dirIndex).dirId));
},
- hostVars: 1,
- features: [$r3$.ɵPublicFeature]
+ hostVars: 1
});
`;
@@ -635,7 +628,6 @@ describe('compiler compliance', () => {
$r3$.ɵelementProperty(elIndex, "id", $r3$.ɵbind($r3$.ɵpureFunction1(1, $ff$, $r3$.ɵload(dirIndex).id)));
},
hostVars: 3,
- features: [$r3$.ɵPublicFeature],
consts: 0,
vars: 0,
template: function HostBindingComp_Template(rf, ctx) {}
@@ -679,7 +671,6 @@ describe('compiler compliance', () => {
$r3$.ɵdirectiveInject(ElementRef), $r3$.ɵdirectiveInject(ViewContainerRef),
$r3$.ɵdirectiveInject(ChangeDetectorRef));
},
- features: [$r3$.ɵPublicFeature],
consts: 0,
vars: 0,
template: function MyComponent_Template(rf, ctx) {}
@@ -720,8 +711,7 @@ describe('compiler compliance', () => {
IfDirective.ngDirectiveDef = $r3$.ɵdefineDirective({
type: IfDirective,
selectors: [["", "if", ""]],
- factory: function IfDirective_Factory(t) { return new (t || IfDirective)($r3$.ɵdirectiveInject(TemplateRef)); },
- features: [$r3$.ɵPublicFeature]
+ factory: function IfDirective_Factory(t) { return new (t || IfDirective)($r3$.ɵdirectiveInject(TemplateRef)); }
});`;
const MyComponentDefinition = `
const $c1$ = ["foo", ""];
@@ -743,7 +733,6 @@ describe('compiler compliance', () => {
type: MyComponent,
selectors: [["my-component"]],
factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); },
- features: [$r3$.ɵPublicFeature],
consts: 3,
vars: 0,
template: function MyComponent_Template(rf, ctx) {
@@ -806,7 +795,6 @@ describe('compiler compliance', () => {
type: MyApp,
selectors: [["my-app"]],
factory: function MyApp_Factory(t) { return new (t || MyApp)(); },
- features: [$r3$.ɵPublicFeature],
consts: 1,
vars: 3,
template: function MyApp_Template(rf, ctx) {
@@ -888,7 +876,6 @@ describe('compiler compliance', () => {
type: MyApp,
selectors: [["my-app"]],
factory: function MyApp_Factory(t) { return new (t || MyApp)(); },
- features: [$r3$.ɵPublicFeature],
consts: 1,
vars: 11,
template: function MyApp_Template(rf, ctx) {
@@ -952,7 +939,6 @@ describe('compiler compliance', () => {
type: MyApp,
selectors: [["my-app"]],
factory: function MyApp_Factory(t) { return new (t || MyApp)(); },
- features: [$r3$.ɵPublicFeature],
consts: 1,
vars: 3,
template: function MyApp_Template(rf, ctx) {
@@ -1020,7 +1006,6 @@ describe('compiler compliance', () => {
type: MyApp,
selectors: [["my-app"]],
factory: function MyApp_Factory(t) { return new (t || MyApp)(); },
- features: [$r3$.ɵPublicFeature],
consts: 1,
vars: 8,
template: function MyApp_Template(rf, ctx) {
@@ -1079,7 +1064,6 @@ describe('compiler compliance', () => {
type: SimpleComponent,
selectors: [["simple"]],
factory: function SimpleComponent_Factory(t) { return new (t || SimpleComponent)(); },
- features: [$r3$.ɵPublicFeature],
consts: 2,
vars: 0,
template: function SimpleComponent_Template(rf, ctx) {
@@ -1102,7 +1086,6 @@ describe('compiler compliance', () => {
type: ComplexComponent,
selectors: [["complex"]],
factory: function ComplexComponent_Factory(t) { return new (t || ComplexComponent)(); },
- features: [$r3$.ɵPublicFeature],
consts: 4,
vars: 0,
template: function ComplexComponent_Template(rf, ctx) {
@@ -1171,7 +1154,6 @@ describe('compiler compliance', () => {
type: ViewQueryComponent,
selectors: [["view-query-component"]],
factory: function ViewQueryComponent_Factory(t) { return new (t || ViewQueryComponent)(); },
- features: [$r3$.ɵPublicFeature],
viewQuery: function ViewQueryComponent_Query(rf, ctx) {
if (rf & 1) {
$r3$.ɵquery(0, SomeDirective, true);
@@ -1348,9 +1330,9 @@ describe('compiler compliance', () => {
factory: function ContentQueryComponent_Factory(t) {
return new (t || ContentQueryComponent)();
},
- contentQueries: function ContentQueryComponent_ContentQueries() {
- $r3$.ɵregisterContentQuery($r3$.ɵquery(null, SomeDirective, true));
- $r3$.ɵregisterContentQuery($r3$.ɵquery(null, SomeDirective, false));
+ contentQueries: function ContentQueryComponent_ContentQueries(dirIndex) {
+ $r3$.ɵregisterContentQuery($r3$.ɵquery(null, SomeDirective, true), dirIndex);
+ $r3$.ɵregisterContentQuery($r3$.ɵquery(null, SomeDirective, false), dirIndex);
},
contentQueriesRefresh: function ContentQueryComponent_ContentQueriesRefresh(dirIndex, queryStartIndex) {
const instance = $r3$.ɵload(dirIndex);
@@ -1358,7 +1340,6 @@ describe('compiler compliance', () => {
($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵloadQueryList(queryStartIndex))) && ($instance$.someDir = $tmp$.first));
($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵloadQueryList((queryStartIndex + 1)))) && ($instance$.someDirList = $tmp$));
},
- features: [$r3$.ɵPublicFeature],
consts: 2,
vars: 0,
template: function ContentQueryComponent_Template(rf, ctx) {
@@ -1406,9 +1387,9 @@ describe('compiler compliance', () => {
…
ContentQueryComponent.ngComponentDef = $r3$.ɵdefineComponent({
…
- contentQueries: function ContentQueryComponent_ContentQueries() {
- $r3$.ɵregisterContentQuery($r3$.ɵquery(null, $e0_attrs$, true));
- $r3$.ɵregisterContentQuery($r3$.ɵquery(null, $e1_attrs$, false));
+ contentQueries: function ContentQueryComponent_ContentQueries(dirIndex) {
+ $r3$.ɵregisterContentQuery($r3$.ɵquery(null, $e0_attrs$, true), dirIndex);
+ $r3$.ɵregisterContentQuery($r3$.ɵquery(null, $e1_attrs$, false), dirIndex);
},
contentQueriesRefresh: function ContentQueryComponent_ContentQueriesRefresh(dirIndex, queryStartIndex) {
const instance = $r3$.ɵload(dirIndex);
@@ -1459,11 +1440,11 @@ describe('compiler compliance', () => {
…
ContentQueryComponent.ngComponentDef = $r3$.ɵdefineComponent({
…
- contentQueries: function ContentQueryComponent_ContentQueries() {
- $r3$.ɵregisterContentQuery($r3$.ɵquery(null, $e0_attrs$ , true, TemplateRef));
- $r3$.ɵregisterContentQuery($r3$.ɵquery(null, SomeDirective, true, ElementRef));
- $r3$.ɵregisterContentQuery($r3$.ɵquery(null, $e1_attrs$, false, ElementRef));
- $r3$.ɵregisterContentQuery($r3$.ɵquery(null, SomeDirective, false, TemplateRef));
+ contentQueries: function ContentQueryComponent_ContentQueries(dirIndex) {
+ $r3$.ɵregisterContentQuery($r3$.ɵquery(null, $e0_attrs$ , true, TemplateRef), dirIndex);
+ $r3$.ɵregisterContentQuery($r3$.ɵquery(null, SomeDirective, true, ElementRef), dirIndex);
+ $r3$.ɵregisterContentQuery($r3$.ɵquery(null, $e1_attrs$, false, ElementRef), dirIndex);
+ $r3$.ɵregisterContentQuery($r3$.ɵquery(null, SomeDirective, false, TemplateRef), dirIndex);
},
contentQueriesRefresh: function ContentQueryComponent_ContentQueriesRefresh(dirIndex, queryStartIndex) {
const instance = $r3$.ɵload(dirIndex);
@@ -1552,7 +1533,6 @@ describe('compiler compliance', () => {
type: MyApp,
selectors: [["my-app"]],
factory: function MyApp_Factory(t) { return new (t || MyApp)(); },
- features: [$r3$.ɵPublicFeature],
consts: 6,
vars: 17,
template: function MyApp_Template(rf, ctx) {
@@ -1616,7 +1596,6 @@ describe('compiler compliance', () => {
type: MyApp,
selectors: [["my-app"]],
factory: function MyApp_Factory(t) { return new (t || MyApp)(); },
- features: [$r3$.ɵPublicFeature],
consts: 6,
vars: 27,
template: function MyApp_Template(rf, ctx) {
@@ -1671,7 +1650,6 @@ describe('compiler compliance', () => {
type: MyComponent,
selectors: [["my-component"]],
factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); },
- features: [$r3$.ɵPublicFeature],
consts: 3,
vars: 1,
template: function MyComponent_Template(rf, ctx) {
@@ -1765,7 +1743,6 @@ describe('compiler compliance', () => {
type: MyComponent,
selectors: [["my-component"]],
factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); },
- features: [$r3$.ɵPublicFeature],
consts: 6,
vars: 1,
template: function MyComponent_Template(rf, ctx) {
@@ -1911,7 +1888,7 @@ describe('compiler compliance', () => {
selectors: [["lifecycle-comp"]],
factory: function LifecycleComp_Factory(t) { return new (t || LifecycleComp)(); },
inputs: {nameMin: "name"},
- features: [$r3$.ɵPublicFeature, $r3$.ɵNgOnChangesFeature],
+ features: [$r3$.ɵNgOnChangesFeature],
consts: 0,
vars: 0,
template: function LifecycleComp_Template(rf, ctx) {}
@@ -1922,7 +1899,6 @@ describe('compiler compliance', () => {
type: SimpleLayout,
selectors: [["simple-layout"]],
factory: function SimpleLayout_Factory(t) { return new (t || SimpleLayout)(); },
- features: [$r3$.ɵPublicFeature],
consts: 2,
vars: 2,
template: function SimpleLayout_Template(rf, ctx) {
@@ -2032,7 +2008,7 @@ describe('compiler compliance', () => {
factory: function ForOfDirective_Factory(t) {
return new (t || ForOfDirective)($r3$.ɵdirectiveInject(ViewContainerRef), $r3$.ɵdirectiveInject(TemplateRef));
},
- features: [$r3$.ɵPublicFeature, $r3$.ɵNgOnChangesFeature],
+ features: [$r3$.ɵNgOnChangesFeature],
inputs: {forOf: "forOf"}
});
`;
@@ -2052,7 +2028,6 @@ describe('compiler compliance', () => {
type: MyComponent,
selectors: [["my-component"]],
factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); },
- features: [$r3$.ɵPublicFeature],
consts: 2,
vars: 1,
template: function MyComponent_Template(rf, ctx){
@@ -2108,7 +2083,7 @@ describe('compiler compliance', () => {
factory: function ForOfDirective_Factory(t) {
return new (t || ForOfDirective)($r3$.ɵdirectiveInject(ViewContainerRef), $r3$.ɵdirectiveInject(TemplateRef));
},
- features: [$r3$.ɵPublicFeature, $r3$.ɵNgOnChangesFeature],
+ features: [$r3$.ɵNgOnChangesFeature],
inputs: {forOf: "forOf"}
});
`;
@@ -2131,7 +2106,6 @@ describe('compiler compliance', () => {
type: MyComponent,
selectors: [["my-component"]],
factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); },
- features: [$r3$.ɵPublicFeature],
consts: 2,
vars: 1,
template: function MyComponent_Template(rf, ctx) {
@@ -2231,7 +2205,6 @@ describe('compiler compliance', () => {
type: MyComponent,
selectors: [["my-component"]],
factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); },
- features: [$r3$.ɵPublicFeature],
consts: 2,
vars: 1,
template: function MyComponent_Template(rf, ctx) {
diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_directives_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_directives_spec.ts
index 26e650aa57..fc1c021bf1 100644
--- a/packages/compiler-cli/test/compliance/r3_view_compiler_directives_spec.ts
+++ b/packages/compiler-cli/test/compliance/r3_view_compiler_directives_spec.ts
@@ -42,7 +42,6 @@ describe('compiler compliance: directives', () => {
type: MyComponent,
selectors: [["my-component"]],
factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); },
- features: [$r3$.ɵPublicFeature],
consts: 1,
vars: 0,
template: function MyComponent_Template(rf, ctx) {
@@ -88,7 +87,6 @@ describe('compiler compliance: directives', () => {
type: MyComponent,
selectors: [["my-component"]],
factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); },
- features: [$r3$.ɵPublicFeature],
consts: 1,
vars: 0,
template: function MyComponent_Template(rf, ctx) {
diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_listener_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_listener_spec.ts
index 5248ea4381..c07f8f8271 100644
--- a/packages/compiler-cli/test/compliance/r3_view_compiler_listener_spec.ts
+++ b/packages/compiler-cli/test/compliance/r3_view_compiler_listener_spec.ts
@@ -202,7 +202,6 @@ describe('compiler compliance: listen()', () => {
type: MyComponent,
selectors: [["my-component"]],
factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); },
- features: [$r3$.ɵPublicFeature],
consts: 4,
vars: 0,
template: function MyComponent_Template(rf, ctx) {
diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_providers_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_providers_spec.ts
new file mode 100644
index 0000000000..8e1138d1ea
--- /dev/null
+++ b/packages/compiler-cli/test/compliance/r3_view_compiler_providers_spec.ts
@@ -0,0 +1,152 @@
+/**
+ * @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 {setup} from '@angular/compiler/test/aot/test_util';
+import {compile, expectEmit} from './mock_compile';
+
+describe('compiler compliance: providers', () => {
+ const angularFiles = setup({
+ compileAngular: false,
+ compileFakeCore: true,
+ compileAnimations: false,
+ });
+
+ it('should emit the ProvidersFeature feature when providers and viewProviders', () => {
+ const files = {
+ app: {
+ 'spec.ts': `
+ import {Component, NgModule} from '@angular/core';
+
+ abstract class Greeter { abstract greet(): string; }
+
+ class GreeterEN implements Greeter {
+ greet() { return 'Hi'; }
+ }
+
+ @Component({
+ selector: 'my-component',
+ template: '
',
+ providers: [GreeterEN, {provide: Greeter, useClass: GreeterEN}],
+ viewProviders: [GreeterEN]
+ })
+ export class MyComponent {
+ }
+
+ @NgModule({declarations: [MyComponent]})
+ export class MyModule {}
+ `
+ }
+ };
+
+ const result = compile(files, angularFiles);
+ expectEmit(
+ result.source,
+ 'features: [i0.ɵProvidersFeature([GreeterEN, {provide: Greeter, useClass: GreeterEN}], [GreeterEN])],',
+ 'Incorrect features');
+ });
+
+ it('should emit the ProvidersFeature feature when providers only', () => {
+ const files = {
+ app: {
+ 'spec.ts': `
+ import {Component, NgModule} from '@angular/core';
+
+ abstract class Greeter { abstract greet(): string; }
+
+ class GreeterEN implements Greeter {
+ greet() { return 'Hi'; }
+ }
+
+ @Component({
+ selector: 'my-component',
+ template: '',
+ providers: [GreeterEN, {provide: Greeter, useClass: GreeterEN}]
+ })
+ export class MyComponent {
+ }
+
+ @NgModule({declarations: [MyComponent]})
+ export class MyModule {}
+ `
+ }
+ };
+
+ const result = compile(files, angularFiles);
+ expectEmit(
+ result.source,
+ 'features: [i0.ɵProvidersFeature([GreeterEN, {provide: Greeter, useClass: GreeterEN}])],',
+ 'Incorrect features');
+ });
+
+ it('should emit the ProvidersFeature feature when viewProviders only', () => {
+ const files = {
+ app: {
+ 'spec.ts': `
+ import {Component, NgModule} from '@angular/core';
+
+ abstract class Greeter { abstract greet(): string; }
+
+ class GreeterEN implements Greeter {
+ greet() { return 'Hi'; }
+ }
+
+ @Component({
+ selector: 'my-component',
+ template: '',
+ viewProviders: [GreeterEN]
+ })
+ export class MyComponent {
+ }
+
+ @NgModule({declarations: [MyComponent]})
+ export class MyModule {}
+ `
+ }
+ };
+
+ const result = compile(files, angularFiles);
+ expectEmit(
+ result.source, 'features: [i0.ɵProvidersFeature([], [GreeterEN])],', 'Incorrect features');
+ });
+
+ it('should not emit the ProvidersFeature feature when no providers', () => {
+ const files = {
+ app: {
+ 'spec.ts': `
+ import {Component, NgModule} from '@angular/core';
+
+ abstract class Greeter { abstract greet(): string; }
+
+ class GreeterEN implements Greeter {
+ greet() { return 'Hi'; }
+ }
+
+ @Component({
+ selector: 'my-component',
+ template: ''
+ })
+ export class MyComponent {
+ }
+
+ @NgModule({declarations: [MyComponent]})
+ export class MyModule {}
+ `
+ }
+ };
+
+ const result = compile(files, angularFiles);
+ expectEmit(
+ result.source, `
+ export class MyComponent {
+ }
+ MyComponent.ngComponentDef = i0.ɵdefineComponent({ type: MyComponent, selectors: [["my-component"]], factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }, consts: 1, vars: 0, template: function MyComponent_Template(rf, ctx) { if (rf & 1) {
+ i0.ɵelement(0, "div");
+ } } });`,
+ 'Incorrect features');
+ });
+});
diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts
index e7e0be7ab0..aa7fe1b0d0 100644
--- a/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts
+++ b/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts
@@ -128,7 +128,6 @@ describe('compiler compliance: styling', () => {
factory:function MyComponent_Factory(t){
return new (t || MyComponent)();
},
- features: [$r3$.ɵPublicFeature],
consts: 0,
vars: 0,
template: function MyComponent_Template(rf, $ctx$) {
@@ -170,7 +169,6 @@ describe('compiler compliance: styling', () => {
factory:function MyComponent_Factory(t){
return new (t || MyComponent)();
},
- features: [$r3$.ɵPublicFeature],
consts: 0,
vars: 0,
template: function MyComponent_Template(rf, $ctx$) {
@@ -308,7 +306,6 @@ describe('compiler compliance: styling', () => {
factory:function MyComponent_Factory(t){
return new (t || MyComponent)();
},
- features: [$r3$.ɵPublicFeature],
consts: 1,
vars: 1,
template: function MyComponent_Template(rf, $ctx$) {
@@ -367,7 +364,6 @@ describe('compiler compliance: styling', () => {
factory: function MyComponent_Factory(t) {
return new (t || MyComponent)();
},
- features: [$r3$.ɵPublicFeature],
consts: 1,
vars: 0,
template: function MyComponent_Template(rf, ctx) {
@@ -506,7 +502,6 @@ describe('compiler compliance: styling', () => {
factory:function MyComponent_Factory(t){
return new (t || MyComponent)();
},
- features: [$r3$.ɵPublicFeature],
consts: 1,
vars: 1,
template: function MyComponent_Template(rf, $ctx$) {
@@ -563,7 +558,6 @@ describe('compiler compliance: styling', () => {
factory:function MyComponent_Factory(t){
return new (t || MyComponent)();
},
- features: [$r3$.ɵPublicFeature],
consts: 1,
vars: 2,
template: function MyComponent_Template(rf, $ctx$) {
diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts
index 09ffd5fe10..4337f4fefb 100644
--- a/packages/compiler/src/render3/r3_identifiers.ts
+++ b/packages/compiler/src/render3/r3_identifiers.ts
@@ -175,7 +175,7 @@ export class Identifiers {
static InheritDefinitionFeature:
o.ExternalReference = {name: 'ɵInheritDefinitionFeature', moduleName: CORE};
- static PublicFeature: o.ExternalReference = {name: 'ɵPublicFeature', moduleName: CORE};
+ static ProvidersFeature: o.ExternalReference = {name: 'ɵProvidersFeature', moduleName: CORE};
static listener: o.ExternalReference = {name: 'ɵlistener', moduleName: CORE};
diff --git a/packages/compiler/src/render3/view/api.ts b/packages/compiler/src/render3/view/api.ts
index 44265e2b2d..1782f7d64f 100644
--- a/packages/compiler/src/render3/view/api.ts
+++ b/packages/compiler/src/render3/view/api.ts
@@ -103,6 +103,11 @@ export interface R3DirectiveMetadata {
* if any.
*/
exportAs: string|null;
+
+ /**
+ * The list of providers defined in the directive.
+ */
+ providers: o.Expression|null;
}
/**
@@ -180,6 +185,11 @@ export interface R3ComponentMetadata extends R3DirectiveMetadata {
* A collection of animation triggers that will be used in the component template.
*/
animations: o.Expression|null;
+
+ /**
+ * The list of view providers defined in the component.
+ */
+ viewProviders: o.Expression|null;
}
/**
diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts
index 0833790599..fde52edc29 100644
--- a/packages/compiler/src/render3/view/compiler.ts
+++ b/packages/compiler/src/render3/view/compiler.ts
@@ -82,11 +82,30 @@ function baseDirectiveFields(
// e.g 'outputs: {a: 'a'}`
definitionMap.set('outputs', conditionallyCreateMapObjectLiteral(meta.outputs));
+ if (meta.exportAs !== null) {
+ definitionMap.set('exportAs', o.literal(meta.exportAs));
+ }
+
+ return {definitionMap, statements: result.statements};
+}
+
+/**
+ * Add features to the definition map.
+ */
+function addFeatures(
+ definitionMap: DefinitionMap, meta: R3DirectiveMetadata | R3ComponentMetadata) {
// e.g. `features: [NgOnChangesFeature]`
const features: o.Expression[] = [];
- // TODO: add `PublicFeature` so that directives get registered to the DI - make this configurable
- features.push(o.importExpr(R3.PublicFeature));
+ const providers = meta.providers;
+ const viewProviders = (meta as R3ComponentMetadata).viewProviders;
+ if (providers || viewProviders) {
+ const args = [providers || new o.LiteralArrayExpr([])];
+ if (viewProviders) {
+ args.push(viewProviders);
+ }
+ features.push(o.importExpr(R3.ProvidersFeature).callFn(args));
+ }
if (meta.usesInheritance) {
features.push(o.importExpr(R3.InheritDefinitionFeature));
@@ -97,11 +116,6 @@ function baseDirectiveFields(
if (features.length) {
definitionMap.set('features', o.literalArr(features));
}
- if (meta.exportAs !== null) {
- definitionMap.set('exportAs', o.literal(meta.exportAs));
- }
-
- return {definitionMap, statements: result.statements};
}
/**
@@ -111,6 +125,7 @@ export function compileDirectiveFromMetadata(
meta: R3DirectiveMetadata, constantPool: ConstantPool,
bindingParser: BindingParser): R3DirectiveDef {
const {definitionMap, statements} = baseDirectiveFields(meta, constantPool, bindingParser);
+ addFeatures(definitionMap, meta);
const expression = o.importExpr(R3.defineDirective).callFn([definitionMap.toLiteralMap()]);
// On the type side, remove newlines from the selector as it will need to fit into a TypeScript
@@ -164,6 +179,7 @@ export function compileComponentFromMetadata(
meta: R3ComponentMetadata, constantPool: ConstantPool,
bindingParser: BindingParser): R3ComponentDef {
const {definitionMap, statements} = baseDirectiveFields(meta, constantPool, bindingParser);
+ addFeatures(definitionMap, meta);
const selector = meta.selector && CssSelector.parse(meta.selector);
const firstSelector = selector && selector[0];
@@ -320,6 +336,8 @@ export function compileComponentFromRender2(
encapsulation:
(summary.template && summary.template.encapsulation) || core.ViewEncapsulation.Emulated,
animations: null,
+ viewProviders:
+ component.viewProviders.length > 0 ? new o.WrappedNodeExpr(component.viewProviders) : null
};
const res = compileComponentFromMetadata(meta, outputCtx.constantPool, bindingParser);
@@ -362,6 +380,7 @@ function directiveMetadataFromGlobalMetadata(
outputs: directive.outputs,
usesInheritance: false,
exportAs: null,
+ providers: directive.providers.length > 0 ? new o.WrappedNodeExpr(directive.providers) : null
};
}
@@ -451,11 +470,15 @@ function createContentQueriesFunction(
if (meta.queries.length) {
const statements: o.Statement[] = meta.queries.map((query: R3QueryMetadata) => {
const queryDefinition = createQueryDefinition(query, constantPool, null);
- return o.importExpr(R3.registerContentQuery).callFn([queryDefinition]).toStmt();
+ return o.importExpr(R3.registerContentQuery)
+ .callFn([queryDefinition, o.variable('dirIndex')])
+ .toStmt();
});
const typeName = meta.name;
+ const parameters = [new o.FnParam('dirIndex', o.NUMBER_TYPE)];
return o.fn(
- [], statements, o.INFERRED_TYPE, null, typeName ? `${typeName}_ContentQueries` : null);
+ parameters, statements, o.INFERRED_TYPE, null,
+ typeName ? `${typeName}_ContentQueries` : null);
}
return null;
diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts
index cfdba19488..99d1c17ca7 100644
--- a/packages/core/src/core_render3_private_export.ts
+++ b/packages/core/src/core_render3_private_export.ts
@@ -25,7 +25,7 @@ export {
getFactoryOf as ɵgetFactoryOf,
getInheritedFactory as ɵgetInheritedFactory,
templateRefExtractor as ɵtemplateRefExtractor,
- PublicFeature as ɵPublicFeature,
+ ProvidersFeature as ɵProvidersResolver,
InheritDefinitionFeature as ɵInheritDefinitionFeature,
NgOnChangesFeature as ɵNgOnChangesFeature,
NgModuleType as ɵNgModuleType,
diff --git a/packages/core/src/di/forward_ref.ts b/packages/core/src/di/forward_ref.ts
index 64375f42dc..ad5b50908f 100644
--- a/packages/core/src/di/forward_ref.ts
+++ b/packages/core/src/di/forward_ref.ts
@@ -8,6 +8,7 @@
import {Type} from '../type';
import {stringify} from '../util';
+import {getClosureSafeProperty} from '../util/property';
@@ -22,6 +23,8 @@ import {stringify} from '../util';
*/
export interface ForwardRefFn { (): any; }
+const __forward_ref__ = getClosureSafeProperty({__forward_ref__: getClosureSafeProperty});
+
/**
* Allows to refer to references which are not yet defined.
*
@@ -53,10 +56,11 @@ export function forwardRef(forwardRefFn: ForwardRefFn): Type {
* @see `forwardRef`
* @publicApi
*/
-export function resolveForwardRef(type: any): any {
- if (typeof type === 'function' && type.hasOwnProperty('__forward_ref__') &&
- type.__forward_ref__ === forwardRef) {
- return (type)();
+export function resolveForwardRef(type: T): T {
+ const fn: any = type;
+ if (typeof fn === 'function' && fn.hasOwnProperty(__forward_ref__) &&
+ fn.__forward_ref__ === forwardRef) {
+ return fn();
} else {
return type;
}
diff --git a/packages/core/src/di/injector.ts b/packages/core/src/di/injector.ts
index 55113fa5d0..a299632fb4 100644
--- a/packages/core/src/di/injector.ts
+++ b/packages/core/src/di/injector.ts
@@ -431,6 +431,17 @@ export function setCurrentInjector(injector: Injector | null | undefined): Injec
_currentInjector = injector;
return former;
}
+/**
+* Current implementation of inject.
+*
+* By default, it is `injectInjectorOnly`, which makes it `Injector`-only aware. It can be changed
+* to `directiveInject`, which brings in the `NodeInjector` system of ivy. It is designed this
+* way for two reasons:
+* 1. `Injector` should not depend on ivy logic.
+* 2. To maintain tree shake-ability we don't want to bring in unnecessary code.
+*/
+let _injectImplementation: ((token: Type| InjectionToken, flags: InjectFlags) => T | null)|
+ undefined;
/**
* Injects a token from the currently active injector.
@@ -452,21 +463,53 @@ export function setCurrentInjector(injector: Injector | null | undefined): Injec
export function inject(token: Type| InjectionToken): T;
export function inject(token: Type| InjectionToken, flags?: InjectFlags): T|null;
export function inject(token: Type| InjectionToken, flags = InjectFlags.Default): T|null {
+ return (_injectImplementation || injectInjectorOnly)(token, flags);
+}
+
+/**
+ * Sets the current inject implementation.
+ */
+export function setInjectImplementation(
+ impl: ((token: Type| InjectionToken, flags?: InjectFlags) => T | null) | undefined):
+ ((token: Type| InjectionToken, flags?: InjectFlags) => T | null)|undefined {
+ const previous = _injectImplementation;
+ _injectImplementation = impl;
+ return previous;
+}
+
+export function injectInjectorOnly(token: Type| InjectionToken): T;
+export function injectInjectorOnly(token: Type| InjectionToken, flags?: InjectFlags): T|
+ null;
+export function injectInjectorOnly(
+ token: Type| InjectionToken, flags = InjectFlags.Default): T|null {
if (_currentInjector === undefined) {
throw new Error(`inject() must be called from an injection context`);
} else if (_currentInjector === null) {
- const injectableDef: InjectableDef|null = getInjectableDef(token);
- if (injectableDef && injectableDef.providedIn == 'root') {
- return injectableDef.value === undefined ? injectableDef.value = injectableDef.factory() :
- injectableDef.value;
- }
- if (flags & InjectFlags.Optional) return null;
- throw new Error(`Injector: NOT_FOUND [${stringify(token)}]`);
+ return injectRootLimpMode(token, undefined, flags);
} else {
return _currentInjector.get(token, flags & InjectFlags.Optional ? null : undefined, flags);
}
}
+/**
+ * Injects `root` tokens in limp mode.
+ *
+ * If no injector exists, we can still inject tree-shakable providers which have `providedIn` set to
+ * `"root"`. This is known as the limp mode injection. In such case the value is stored in the
+ * `InjectableDef`.
+ */
+export function injectRootLimpMode(
+ token: Type| InjectionToken, notFoundValue: T | undefined, flags: InjectFlags): T|null {
+ const injectableDef: InjectableDef|null = getInjectableDef(token);
+ if (injectableDef && injectableDef.providedIn == 'root') {
+ return injectableDef.value === undefined ? injectableDef.value = injectableDef.factory() :
+ injectableDef.value;
+ }
+ if (flags & InjectFlags.Optional) return null;
+ if (notFoundValue !== undefined) return notFoundValue;
+ throw new Error(`Injector: NOT_FOUND [${stringify(token)}]`);
+}
+
export function injectArgs(types: (Type| InjectionToken| any[])[]): any[] {
const args: any[] = [];
for (let i = 0; i < types.length; i++) {
diff --git a/packages/core/src/di/r3_injector.ts b/packages/core/src/di/r3_injector.ts
index 210cd746e3..0441bb811a 100644
--- a/packages/core/src/di/r3_injector.ts
+++ b/packages/core/src/di/r3_injector.ts
@@ -165,7 +165,7 @@ export class R3Injector {
if (def && this.injectableDefInScope(def)) {
// Found an ngInjectableDef and it's scoped to this injector. Pretend as if it was here
// all along.
- record = injectableDefRecord(token);
+ record = makeRecord(injectableDefFactory(token), NOT_YET);
this.records.set(token, record);
}
}
@@ -328,7 +328,7 @@ export class R3Injector {
}
}
-function injectableDefRecord(token: Type| InjectionToken): Record {
+function injectableDefFactory(token: Type| InjectionToken): () => any {
const injectableDef = getInjectableDef(token as InjectableType);
if (injectableDef === null) {
if (token instanceof InjectionToken) {
@@ -336,21 +336,34 @@ function injectableDefRecord(token: Type| InjectionToken): Record
}
// TODO(alxhub): there should probably be a strict mode which throws here instead of assuming a
// no-args constructor.
- return makeRecord(() => new (token as Type)());
+ return () => new (token as Type)();
}
- return makeRecord(injectableDef.factory);
+ return injectableDef.factory;
}
function providerToRecord(provider: SingleProvider): Record {
+ let factory: (() => any)|undefined = providerToFactory(provider);
+ if (isValueProvider(provider)) {
+ return makeRecord(undefined, provider.useValue);
+ } else {
+ return makeRecord(factory, NOT_YET);
+ }
+}
+
+/**
+ * Converts a `SingleProvider` into a factory function.
+ *
+ * @param provider provider to convert to factory
+ */
+export function providerToFactory(provider: SingleProvider): () => any {
let token = resolveForwardRef(provider);
- let value: any = NOT_YET;
let factory: (() => any)|undefined = undefined;
if (isTypeProvider(provider)) {
- return injectableDefRecord(provider);
+ return injectableDefFactory(provider);
} else {
token = resolveForwardRef(provider.provide);
if (isValueProvider(provider)) {
- value = provider.useValue;
+ factory = () => provider.useValue;
} else if (isExistingProvider(provider)) {
factory = () => inject(provider.useExisting);
} else if (isFactoryProvider(provider)) {
@@ -360,11 +373,11 @@ function providerToRecord(provider: SingleProvider): Record {
if (hasDeps(provider)) {
factory = () => new (classRef)(...injectArgs(provider.deps));
} else {
- return injectableDefRecord(classRef);
+ return injectableDefFactory(classRef);
}
}
}
- return makeRecord(factory, value);
+ return factory;
}
function makeRecord(
@@ -392,7 +405,7 @@ function isFactoryProvider(value: SingleProvider): value is FactoryProvider {
return !!(value as FactoryProvider).useFactory;
}
-function isTypeProvider(value: SingleProvider): value is TypeProvider {
+export function isTypeProvider(value: SingleProvider): value is TypeProvider {
return typeof value === 'function';
}
diff --git a/packages/core/src/render3/component.ts b/packages/core/src/render3/component.ts
index f34eae623f..0c2d671d87 100644
--- a/packages/core/src/render3/component.ts
+++ b/packages/core/src/render3/component.ts
@@ -15,14 +15,16 @@ import {Sanitizer} from '../sanitization/security';
import {assertComponentType, assertDefined} from './assert';
import {getComponentViewByInstance} from './context_discovery';
import {getComponentDef} from './definition';
+import {diPublicInInjector, getOrCreateNodeInjectorForNode} from './di';
import {queueInitHooks, queueLifecycleHooks} from './hooks';
-import {CLEAN_PROMISE, baseDirectiveCreate, createLViewData, createNodeAtIndex, createTView, detectChangesInternal, enterView, executeInitAndContentHooks, getOrCreateTView, leaveView, locateHostElement, prefillHostVars, resetComponentState, setHostBindings} from './instructions';
+import {CLEAN_PROMISE, createLViewData, createNodeAtIndex, createTView, detectChangesInternal, executeInitAndContentHooks, getOrCreateTView, initNodeFlags, instantiateRootComponent, locateHostElement, prefillHostVars, setHostBindings} from './instructions';
import {ComponentDef, ComponentType} from './interfaces/definition';
import {TElementNode, TNodeFlags, TNodeType} from './interfaces/node';
import {PlayerHandler} from './interfaces/player';
import {RElement, RNode, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
import {CONTEXT, HEADER_OFFSET, HOST, HOST_NODE, INJECTOR, LViewData, LViewFlags, RootContext, RootContextFlags, TVIEW} from './interfaces/view';
import {publishDefaultGlobalUtils} from './publish_global_util';
+import {enterView, leaveView, resetComponentState} from './state';
import {getRootView, readElementValue, readPatchedLViewData, stringify} from './util';
@@ -130,13 +132,12 @@ export function renderComponent(
let component: T;
try {
if (rendererFactory.begin) rendererFactory.begin();
-
const componentView =
createRootComponentView(hostRNode, componentDef, rootView, renderer, sanitizer);
component = createRootComponent(
hostRNode, componentView, componentDef, rootView, rootContext, opts.hostFeatures || null);
- executeInitAndContentHooks();
+ executeInitAndContentHooks(rootView);
detectChangesInternal(componentView, component);
} finally {
leaveView(oldView);
@@ -171,9 +172,9 @@ export function createRootComponentView(
if (tView.firstTemplatePass) {
tView.expandoInstructions = ROOT_EXPANDO_INSTRUCTIONS.slice();
- if (def.diPublic) def.diPublic(def);
- tNode.flags =
- rootView.length << TNodeFlags.DirectiveStartingIndexShift | TNodeFlags.isComponent;
+ diPublicInInjector(getOrCreateNodeInjectorForNode(tNode, rootView), rootView, def.type);
+ tNode.flags = TNodeFlags.isComponent;
+ initNodeFlags(tNode, rootView.length, 1);
}
// Store component view at node index, with node as the HOST
@@ -189,16 +190,17 @@ export function createRootComponentView(
export function createRootComponent(
hostRNode: RNode | null, componentView: LViewData, componentDef: ComponentDef,
rootView: LViewData, rootContext: RootContext, hostFeatures: HostFeature[] | null): any {
+ const tView = rootView[TVIEW];
// Create directive instance with factory() and store at next index in viewData
- const component =
- baseDirectiveCreate(rootView.length, componentDef.factory() as T, componentDef, hostRNode);
+ const component = instantiateRootComponent(tView, rootView, componentDef);
rootContext.components.push(component);
componentView[CONTEXT] = component;
hostFeatures && hostFeatures.forEach((feature) => feature(component, componentDef));
- if (rootView[TVIEW].firstTemplatePass) prefillHostVars(componentDef.hostVars);
- setHostBindings();
+
+ if (tView.firstTemplatePass) prefillHostVars(tView, rootView, componentDef.hostVars);
+ setHostBindings(tView, rootView);
return component;
}
diff --git a/packages/core/src/render3/component_ref.ts b/packages/core/src/render3/component_ref.ts
index 3a04643e3b..39b009f56c 100644
--- a/packages/core/src/render3/component_ref.ts
+++ b/packages/core/src/render3/component_ref.ts
@@ -19,11 +19,12 @@ import {Type} from '../type';
import {assertComponentType, assertDefined} from './assert';
import {LifecycleHooksFeature, createRootComponent, createRootComponentView, createRootContext} from './component';
import {getComponentDef} from './definition';
-import {adjustBlueprintForNewNode, createLViewData, createNodeAtIndex, createTView, createViewNode, elementCreate, enterView, locateHostElement, renderEmbeddedTemplate} from './instructions';
+import {createLViewData, createNodeAtIndex, createTView, createViewNode, elementCreate, locateHostElement, renderEmbeddedTemplate} from './instructions';
import {ComponentDef, RenderFlags} from './interfaces/definition';
import {TElementNode, TNode, TNodeType, TViewNode} from './interfaces/node';
import {RElement, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
-import {FLAGS, INJECTOR, LViewData, LViewFlags, RootContext, TVIEW} from './interfaces/view';
+import {FLAGS, HEADER_OFFSET, INJECTOR, LViewData, LViewFlags, RootContext, TVIEW} from './interfaces/view';
+import {enterView} from './state';
import {getTNode} from './util';
import {createElementRef} from './view_engine_compatibility';
import {RootViewRef, ViewRef} from './view_ref';
@@ -114,9 +115,6 @@ export class ComponentFactory extends viewEngine_ComponentFactory {
elementCreate(this.selector, rendererFactory.createRenderer(null, this.componentDef)) :
locateHostElement(rendererFactory, rootSelectorOrNode);
- // The first index of the first selector is the tag name.
- const componentTag = this.componentDef.selectors ![0] ![0] as string;
-
const rootFlags = this.componentDef.onPush ? LViewFlags.Dirty | LViewFlags.IsRoot :
LViewFlags.CheckAlways | LViewFlags.IsRoot;
const rootContext: RootContext = ngModule && !isInternalRootView ?
@@ -145,15 +143,25 @@ export class ComponentFactory extends viewEngine_ComponentFactory {
// projection instruction. This is needed to support the reprojection of these nodes.
if (projectableNodes) {
let index = 0;
+ const tView = rootView[TVIEW];
const projection: TNode[] = tElementNode.projection = [];
for (let i = 0; i < projectableNodes.length; i++) {
const nodeList = projectableNodes[i];
let firstTNode: TNode|null = null;
let previousTNode: TNode|null = null;
for (let j = 0; j < nodeList.length; j++) {
- adjustBlueprintForNewNode(rootView);
+ if (tView.firstTemplatePass) {
+ // For dynamically created components such as ComponentRef, we create a new TView for
+ // each insert. This is not ideal since we should be sharing the TViews.
+ // Also the logic here should be shared with `component.ts`'s `renderComponent`
+ // method.
+ tView.expandoStartIndex++;
+ tView.blueprint.splice(++index + HEADER_OFFSET, 0, null);
+ tView.data.splice(index + HEADER_OFFSET, 0, null);
+ rootView.splice(index + HEADER_OFFSET, 0, null);
+ }
const tNode =
- createNodeAtIndex(++index, TNodeType.Element, nodeList[j] as RElement, null, null);
+ createNodeAtIndex(index, TNodeType.Element, nodeList[j] as RElement, null, null);
previousTNode ? (previousTNode.next = tNode) : (firstTNode = tNode);
previousTNode = tNode;
}
diff --git a/packages/core/src/render3/definition.ts b/packages/core/src/render3/definition.ts
index cd48ffda9a..627a05d102 100644
--- a/packages/core/src/render3/definition.ts
+++ b/packages/core/src/render3/definition.ts
@@ -54,7 +54,7 @@ export function defineComponent(componentDefinition: {
/**
* Factory method used to create an instance of directive.
*/
- factory: () => T;
+ factory: (t: Type| null) => T;
/**
* The number of nodes, local refs, and pipes in this component template.
@@ -154,7 +154,7 @@ export function defineComponent(componentDefinition: {
/**
* Function to create instances of content queries associated with a given directive.
*/
- contentQueries?: (() => void);
+ contentQueries?: ((dirIndex: number) => void);
/** Refreshes content queries associated with directives in a given view */
contentQueriesRefresh?: ((directiveIndex: number, queryIndex: number) => void);
@@ -210,7 +210,7 @@ export function defineComponent(componentDefinition: {
/**
* A list of optional features to apply.
*
- * See: {@link NgOnChangesFeature}, {@link PublicFeature}
+ * See: {@link NgOnChangesFeature}, {@link ProvidersFeature}
*/
features?: ComponentDefFeature[];
@@ -238,17 +238,6 @@ export function defineComponent(componentDefinition: {
*/
changeDetection?: ChangeDetectionStrategy;
- /**
- * Defines the set of injectable objects that are visible to a Directive and its light DOM
- * children.
- */
- providers?: Provider[];
-
- /**
- * Defines the set of injectable objects that are visible to its view DOM children.
- */
- viewProviders?: Provider[];
-
/**
* Registry of directives and components that may be found in this component's view.
*
@@ -270,7 +259,7 @@ export function defineComponent(componentDefinition: {
const declaredInputs: {[key: string]: string} = {} as any;
const def: Mutable, keyof ComponentDef> = {
type: type,
- diPublic: null,
+ providersResolver: null,
consts: componentDefinition.consts,
vars: componentDefinition.vars,
hostVars: componentDefinition.hostVars || 0,
@@ -301,8 +290,6 @@ export function defineComponent(componentDefinition: {
// TODO(misko): convert ViewEncapsulation into const enum so that it can be used directly in the
// next line. Also `None` should be 0 not 2.
encapsulation: componentDefinition.encapsulation || ViewEncapsulation.Emulated,
- providers: EMPTY_ARRAY,
- viewProviders: EMPTY_ARRAY,
id: 'c',
styles: componentDefinition.styles || EMPTY_ARRAY,
_: null as never,
@@ -525,7 +512,7 @@ export const defineDirective = defineComponent as any as(directiveDefinition:
/**
* Factory method used to create an instance of directive.
*/
- factory: () => T;
+ factory: (t: Type| null) => T;
/**
* Static attributes to set on host element.
@@ -595,7 +582,7 @@ export const defineDirective = defineComponent as any as(directiveDefinition:
/**
* A list of optional features to apply.
*
- * See: {@link NgOnChangesFeature}, {@link PublicFeature}, {@link InheritDefinitionFeature}
+ * See: {@link NgOnChangesFeature}, {@link ProvidersFeature}, {@link InheritDefinitionFeature}
*/
features?: DirectiveDefFeature[];
@@ -615,7 +602,7 @@ export const defineDirective = defineComponent as any as(directiveDefinition:
/**
* Function to create instances of content queries associated with a given directive.
*/
- contentQueries?: (() => void);
+ contentQueries?: ((directiveIndex: number) => void);
/** Refreshes content queries associated with directives in a given view */
contentQueriesRefresh?: ((directiveIndex: number, queryIndex: number) => void);
@@ -650,7 +637,7 @@ export function definePipe(pipeDef: {
type: Type,
/** A factory for creating a pipe instance. */
- factory: () => T,
+ factory: (t: Type| null) => T,
/** Whether the pipe is pure. */
pure?: boolean
diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts
index 8f01764e66..37ec98f3b2 100644
--- a/packages/core/src/render3/di.ts
+++ b/packages/core/src/render3/di.ts
@@ -6,23 +6,67 @@
* found in the LICENSE file at https://angular.io/license
*/
-// We are temporarily importing the existing viewEngine_from core so we can be sure we are
-// correctly implementing its interfaces for backwards compatibility.
-
import {getInjectableDef, getInjectorDef} from '../di/defs';
import {InjectionToken} from '../di/injection_token';
-import {InjectFlags, Injector, inject, setCurrentInjector} from '../di/injector';
+import {InjectFlags, Injector, NullInjector, injectRootLimpMode, setInjectImplementation} from '../di/injector';
import {Type} from '../type';
-import {assertDefined} from './assert';
+import {assertDefined, assertEqual} from './assert';
import {getComponentDef, getDirectiveDef, getPipeDef} from './definition';
import {NG_ELEMENT_ID} from './fields';
-import {_getViewData, getPreviousOrParentTNode, resolveDirective, setEnvironment} from './instructions';
import {DirectiveDef} from './interfaces/definition';
-import {InjectorLocationFlags, PARENT_INJECTOR, TNODE,} from './interfaces/injector';
-import {AttributeMarker, TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node';
+import {NO_PARENT_INJECTOR, NodeInjectorFactory, PARENT_INJECTOR, RelativeInjectorLocation, RelativeInjectorLocationFlags, TNODE, isFactory} from './interfaces/injector';
+import {AttributeMarker, TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType} from './interfaces/node';
import {DECLARATION_VIEW, HOST_NODE, INJECTOR, LViewData, TData, TVIEW, TView} from './interfaces/view';
import {assertNodeOfPossibleTypes} from './node_assert';
+import {getPreviousOrParentTNode, getViewData, setTNodeAndViewData} from './state';
+import {getParentInjectorIndex, getParentInjectorView, getParentInjectorViewOffset, hasParentInjector, isComponent, stringify} from './util';
+
+
+
+/**
+ * Defines if the call to `inject` should include `viewProviders` in its resolution.
+ *
+ * This is set to true when we try to instantiate a component. This value is reset in
+ * `getNodeInjectable` to a value which matches the declaration location of the token about to be
+ * instantiated. This is done so that if we are injecting a token which was declared outside of
+ * `viewProviders` we don't accidentally pull `viewProviders` in.
+ *
+ * Example:
+ *
+ * ```
+ * @Injectable()
+ * class MyService {
+ * constructor(public value: String) {}
+ * }
+ *
+ * @Component({
+ * providers: [
+ * MyService,
+ * {provide: String, value: 'providers' }
+ * ]
+ * viewProviders: [
+ * {provide: String, value: 'viewProviders'}
+ * ]
+ * })
+ * class MyComponent {
+ * constructor(myService: MyService, value: String) {
+ * // We expect that Component can see into `viewProviders`.
+ * expect(value).toEqual('viewProviders');
+ * // `MyService` was not declared in `viewProviders` hence it can't see it.
+ * expect(myService.value).toEqual('providers');
+ * }
+ * }
+ *
+ * ```
+ */
+let includeViewProviders = false;
+
+function setIncludeViewProviders(v: boolean): boolean {
+ const oldValue = includeViewProviders;
+ includeViewProviders = v;
+ return oldValue;
+}
/**
* The number of slots in each bloom filter (used by DI). The larger this number, the fewer
@@ -43,46 +87,40 @@ let nextNgElementId = 0;
* @param tView The TView for the injector's bloom filters
* @param type The directive token to register
*/
-export function bloomAdd(injectorIndex: number, tView: TView, type: Type): void {
- if (tView.firstTemplatePass) {
- let id: number|undefined = (type as any)[NG_ELEMENT_ID];
+export function bloomAdd(
+ injectorIndex: number, tView: TView, type: Type| InjectionToken): void {
+ ngDevMode && assertEqual(tView.firstTemplatePass, true, 'expected firstTemplatePass to be true');
+ let id: number|undefined = (type as any)[NG_ELEMENT_ID];
- // Set a unique ID on the directive type, so if something tries to inject the directive,
- // we can easily retrieve the ID and hash it into the bloom bit that should be checked.
- if (id == null) {
- id = (type as any)[NG_ELEMENT_ID] = nextNgElementId++;
- }
-
- // We only have BLOOM_SIZE (256) slots in our bloom filter (8 buckets * 32 bits each),
- // so all unique IDs must be modulo-ed into a number from 0 - 255 to fit into the filter.
- const bloomBit = id & BLOOM_MASK;
-
- // Create a mask that targets the specific bit associated with the directive.
- // JS bit operations are 32 bits, so this will be a number between 2^0 and 2^31, corresponding
- // to bit positions 0 - 31 in a 32 bit integer.
- const mask = 1 << bloomBit;
-
- // Use the raw bloomBit number to determine which bloom filter bucket we should check
- // e.g: bf0 = [0 - 31], bf1 = [32 - 63], bf2 = [64 - 95], bf3 = [96 - 127], etc
- const b7 = bloomBit & 0x80;
- const b6 = bloomBit & 0x40;
- const b5 = bloomBit & 0x20;
- const tData = tView.data as number[];
-
- if (b7) {
- b6 ? (b5 ? (tData[injectorIndex + 7] |= mask) : (tData[injectorIndex + 6] |= mask)) :
- (b5 ? (tData[injectorIndex + 5] |= mask) : (tData[injectorIndex + 4] |= mask));
- } else {
- b6 ? (b5 ? (tData[injectorIndex + 3] |= mask) : (tData[injectorIndex + 2] |= mask)) :
- (b5 ? (tData[injectorIndex + 1] |= mask) : (tData[injectorIndex] |= mask));
- }
+ // Set a unique ID on the directive type, so if something tries to inject the directive,
+ // we can easily retrieve the ID and hash it into the bloom bit that should be checked.
+ if (id == null) {
+ id = (type as any)[NG_ELEMENT_ID] = nextNgElementId++;
}
-}
-export function getOrCreateNodeInjector(): number {
- return getOrCreateNodeInjectorForNode(
- getPreviousOrParentTNode() as TElementNode | TElementContainerNode | TContainerNode,
- _getViewData());
+ // We only have BLOOM_SIZE (256) slots in our bloom filter (8 buckets * 32 bits each),
+ // so all unique IDs must be modulo-ed into a number from 0 - 255 to fit into the filter.
+ const bloomBit = id & BLOOM_MASK;
+
+ // Create a mask that targets the specific bit associated with the directive.
+ // JS bit operations are 32 bits, so this will be a number between 2^0 and 2^31, corresponding
+ // to bit positions 0 - 31 in a 32 bit integer.
+ const mask = 1 << bloomBit;
+
+ // Use the raw bloomBit number to determine which bloom filter bucket we should check
+ // e.g: bf0 = [0 - 31], bf1 = [32 - 63], bf2 = [64 - 95], bf3 = [96 - 127], etc
+ const b7 = bloomBit & 0x80;
+ const b6 = bloomBit & 0x40;
+ const b5 = bloomBit & 0x20;
+ const tData = tView.data as number[];
+
+ if (b7) {
+ b6 ? (b5 ? (tData[injectorIndex + 7] |= mask) : (tData[injectorIndex + 6] |= mask)) :
+ (b5 ? (tData[injectorIndex + 5] |= mask) : (tData[injectorIndex + 4] |= mask));
+ } else {
+ b6 ? (b5 ? (tData[injectorIndex + 3] |= mask) : (tData[injectorIndex + 2] |= mask)) :
+ (b5 ? (tData[injectorIndex + 1] |= mask) : (tData[injectorIndex] |= mask));
+ }
}
/**
@@ -102,26 +140,29 @@ export function getOrCreateNodeInjectorForNode(
const tView = hostView[TVIEW];
if (tView.firstTemplatePass) {
tNode.injectorIndex = hostView.length;
- setUpBloom(tView.data, tNode); // foundation for node bloom
- setUpBloom(hostView, null); // foundation for cumulative bloom
- setUpBloom(tView.blueprint, null);
+ insertBloom(tView.data, tNode); // foundation for node bloom
+ insertBloom(hostView, null); // foundation for cumulative bloom
+ insertBloom(tView.blueprint, null);
+
+ ngDevMode && assertEqual(
+ tNode.flags === 0 || tNode.flags === TNodeFlags.isComponent, true,
+ 'expected tNode.flags to not be initialized');
}
const parentLoc = getParentInjectorLocation(tNode, hostView);
- const parentIndex = parentLoc & InjectorLocationFlags.InjectorIndexMask;
+ const parentIndex = getParentInjectorIndex(parentLoc);
const parentView: LViewData = getParentInjectorView(parentLoc, hostView);
- const parentData = parentView[TVIEW].data as any;
const injectorIndex = tNode.injectorIndex;
// If a parent injector can't be found, its location is set to -1.
// In that case, we don't need to set up a cumulative bloom
- if (parentLoc !== -1) {
- for (let i = 0; i < PARENT_INJECTOR; i++) {
- const bloomIndex = parentIndex + i;
- // Creates a cumulative bloom filter that merges the parent's bloom filter
- // and its own cumulative bloom (which contains tokens for all ancestors)
- hostView[injectorIndex + i] = parentView[bloomIndex] | parentData[bloomIndex];
+ if (hasParentInjector(parentLoc)) {
+ const parentData = parentView[TVIEW].data as any;
+ // Creates a cumulative bloom filter that merges the parent's bloom filter
+ // and its own cumulative bloom (which contains tokens for all ancestors)
+ for (let i = 0; i < 8; i++) {
+ hostView[injectorIndex + i] = parentView[parentIndex + i] | parentData[parentIndex + i];
}
}
@@ -129,10 +170,11 @@ export function getOrCreateNodeInjectorForNode(
return injectorIndex;
}
-function setUpBloom(arr: any[], footer: TNode | null) {
+function insertBloom(arr: any[], footer: TNode | null): void {
arr.push(0, 0, 0, 0, 0, 0, 0, 0, footer);
}
+
export function getInjectorIndex(tNode: TNode, hostView: LViewData): number {
if (tNode.injectorIndex === -1 ||
// If the injector index is the same as its parent's injector index, then the index has been
@@ -150,10 +192,12 @@ export function getInjectorIndex(tNode: TNode, hostView: LViewData): number {
/**
* Finds the index of the parent injector, with a view offset if applicable. Used to set the
* parent injector initially.
+ *
+ * Returns a combination of number of `ViewData` we have to go up and index in that `Viewdata`
*/
-export function getParentInjectorLocation(tNode: TNode, view: LViewData): number {
+export function getParentInjectorLocation(tNode: TNode, view: LViewData): RelativeInjectorLocation {
if (tNode.parent && tNode.parent.injectorIndex !== -1) {
- return tNode.parent.injectorIndex; // view offset is 0
+ return tNode.parent.injectorIndex as any; // view offset is 0
}
// For most cases, the parent injector index can be found on the host node (e.g. for component
@@ -167,81 +211,20 @@ export function getParentInjectorLocation(tNode: TNode, view: LViewData): number
viewOffset++;
}
return hostTNode ?
- hostTNode.injectorIndex | (viewOffset << InjectorLocationFlags.ViewOffsetShift) :
- -1;
+ hostTNode.injectorIndex | (viewOffset << RelativeInjectorLocationFlags.ViewOffsetShift) :
+ -1 as any;
}
/**
- * Unwraps a parent injector location number to find the view offset from the current injector,
- * then walks up the declaration view tree until the view is found that contains the parent
- * injector.
- *
- * @param location The location of the parent injector, which contains the view offset
- * @param startView The LViewData instance from which to start walking up the view tree
- * @returns The LViewData instance that contains the parent injector
- */
-export function getParentInjectorView(location: number, startView: LViewData): LViewData {
- let viewOffset = location >> InjectorLocationFlags.ViewOffsetShift;
- let parentView = startView;
- // For most cases, the parent injector can be found on the host node (e.g. for component
- // or container), but we must keep the loop here to support the rarer case of deeply nested
- // tags or inline views, where the parent injector might live many views
- // above the child injector.
- while (viewOffset > 0) {
- parentView = parentView[DECLARATION_VIEW] !;
- viewOffset--;
- }
- return parentView;
-}
-
-/**
- * Makes a directive public to the DI system by adding it to an injector's bloom filter.
+ * Makes a type or an injection token public to the DI system by adding it to an
+ * injector's bloom filter.
*
* @param di The node injector in which a directive will be added
- * @param def The definition of the directive to be made public
+ * @param token The type or the injection token to be made public
*/
export function diPublicInInjector(
- injectorIndex: number, view: LViewData, def: DirectiveDef): void {
- bloomAdd(injectorIndex, view[TVIEW], def.type);
-}
-
-/**
- * Makes a directive public to the DI system by adding it to an injector's bloom filter.
- *
- * @param def The definition of the directive to be made public
- */
-export function diPublic(def: DirectiveDef): void {
- diPublicInInjector(getOrCreateNodeInjector(), _getViewData(), def);
-}
-
-/**
- * Returns the value associated to the given token from the injectors.
- *
- * `directiveInject` is intended to be used for directive, component and pipe factories.
- * All other injection use `inject` which does not walk the node injector tree.
- *
- * Usage example (in factory function):
- *
- * class SomeDirective {
- * constructor(directive: DirectiveA) {}
- *
- * static ngDirectiveDef = defineDirective({
- * type: SomeDirective,
- * factory: () => new SomeDirective(directiveInject(DirectiveA))
- * });
- * }
- *
- * @param token the type or token to inject
- * @param flags Injection flags
- * @returns the value from the injector or `null` when not found
- */
-export function directiveInject(token: Type| InjectionToken): T;
-export function directiveInject(token: Type| InjectionToken, flags: InjectFlags): T;
-export function directiveInject(
- token: Type| InjectionToken, flags = InjectFlags.Default): T|null {
- const hostTNode =
- getPreviousOrParentTNode() as TElementNode | TContainerNode | TElementContainerNode;
- return getOrCreateInjectable(hostTNode, _getViewData(), token, flags);
+ injectorIndex: number, view: LViewData, token: InjectionToken| Type): void {
+ bloomAdd(injectorIndex, view[TVIEW], token);
}
/**
@@ -275,8 +258,7 @@ export function directiveInject(
*
* @publicApi
*/
-export function injectAttribute(attrNameToInject: string): string|undefined {
- const tNode = getPreviousOrParentTNode();
+export function injectAttributeImpl(tNode: TNode, attrNameToInject: string): string|undefined {
ngDevMode && assertNodeOfPossibleTypes(
tNode, TNodeType.Container, TNodeType.Element, TNodeType.ElementContainer);
ngDevMode && assertDefined(tNode, 'expecting tNode');
@@ -295,7 +277,7 @@ export function injectAttribute(attrNameToInject: string): string|undefined {
/**
- * Returns the value associated to the given token from the injectors.
+ * Returns the value associated to the given token from the NodeInjectors => ModuleInjector.
*
* Look for the injector providing the token by walking up the node injector tree and then
* the module injector tree.
@@ -306,133 +288,169 @@ export function injectAttribute(attrNameToInject: string): string|undefined {
* @returns the value from the injector or `null` when not found
*/
export function getOrCreateInjectable(
- hostTNode: TElementNode | TContainerNode | TElementContainerNode, hostView: LViewData,
- token: Type| InjectionToken, flags: InjectFlags = InjectFlags.Default): T|null {
+ tNode: TElementNode | TContainerNode | TElementContainerNode, lViewData: LViewData,
+ token: Type| InjectionToken, flags: InjectFlags = InjectFlags.Default,
+ notFoundValue?: any): T|null {
const bloomHash = bloomHashBitOrFactory(token);
// If the ID stored here is a function, this is a special object like ElementRef or TemplateRef
// so just call the factory function to create it.
- if (typeof bloomHash === 'function') return bloomHash();
+ if (typeof bloomHash === 'function') {
+ const savePreviousOrParentTNode = getPreviousOrParentTNode();
+ const saveViewData = getViewData();
+ setTNodeAndViewData(tNode, lViewData);
+ try {
+ return bloomHash();
+ } finally {
+ setTNodeAndViewData(savePreviousOrParentTNode, saveViewData);
+ }
+ } else if (typeof bloomHash == 'number') {
+ // If the token has a bloom hash, then it is a token which could be in NodeInjector.
- // If the token has a bloom hash, then it is a directive that is public to the injection system
- // (diPublic) otherwise fall back to the module injector.
- if (bloomHash != null) {
- const startInjectorIndex = getInjectorIndex(hostTNode, hostView);
+ // A reference to the previous injector TView that was found while climbing the element injector
+ // tree. This is used to know if viewProviders can be accessed on the current injector.
+ let previousTView: TView|null = null;
+ let injectorIndex = getInjectorIndex(tNode, lViewData);
+ let parentLocation: RelativeInjectorLocation = NO_PARENT_INJECTOR;
- let injectorIndex = startInjectorIndex;
- let injectorView = hostView;
- let parentLocation: number = -1;
+ // If we should skip this injector, start by searching the parent injector.
+ if (flags & InjectFlags.SkipSelf) {
+ parentLocation = injectorIndex === -1 ? getParentInjectorLocation(tNode, lViewData) :
+ lViewData[injectorIndex + PARENT_INJECTOR];
- // If we should skip this injector or if an injector doesn't exist on this node (e.g. all
- // directives on this node are private), start by searching the parent injector.
- if (flags & InjectFlags.SkipSelf || injectorIndex === -1) {
- parentLocation = injectorIndex === -1 ? getParentInjectorLocation(hostTNode, hostView) :
- injectorView[injectorIndex + PARENT_INJECTOR];
-
- if (shouldNotSearchParent(flags, parentLocation)) {
+ if (!shouldSearchParent(flags, parentLocation)) {
injectorIndex = -1;
} else {
- injectorIndex = parentLocation & InjectorLocationFlags.InjectorIndexMask;
- injectorView = getParentInjectorView(parentLocation, injectorView);
+ previousTView = lViewData[TVIEW];
+ injectorIndex = getParentInjectorIndex(parentLocation);
+ lViewData = getParentInjectorView(parentLocation, lViewData);
}
}
+ // Traverse up the injector tree until we find a potential match or until we know there
+ // *isn't* a match.
while (injectorIndex !== -1) {
- // Traverse up the injector tree until we find a potential match or until we know there
- // *isn't* a match. Outer loop is necessary in case we get a false positive injector.
- while (injectorIndex !== -1) {
- // Check the current injector. If it matches, stop searching for an injector.
- if (injectorHasToken(bloomHash, injectorIndex, injectorView[TVIEW].data)) {
- break;
- }
+ parentLocation = lViewData[injectorIndex + PARENT_INJECTOR];
- parentLocation = injectorView[injectorIndex + PARENT_INJECTOR];
- if (shouldNotSearchParent(flags, parentLocation)) {
- injectorIndex = -1;
- break;
- }
-
- // If the ancestor bloom filter value has the bit corresponding to the directive, traverse
- // up to find the specific injector. If the ancestor bloom filter does not have the bit, we
- // can abort.
- if (injectorHasToken(bloomHash, injectorIndex, injectorView)) {
- injectorIndex = parentLocation & InjectorLocationFlags.InjectorIndexMask;
- injectorView = getParentInjectorView(parentLocation, injectorView);
- } else {
- injectorIndex = -1;
- break;
+ // Check the current injector. If it matches, see if it contains token.
+ const tView = lViewData[TVIEW];
+ if (bloomHasToken(bloomHash, injectorIndex, tView.data)) {
+ // At this point, we have an injector which *may* contain the token, so we step through
+ // the providers and directives associated with the injector's corresponding node to get
+ // the instance.
+ const instance: T|null =
+ searchTokensOnInjector(injectorIndex, lViewData, token, previousTView);
+ if (instance !== NOT_FOUND) {
+ return instance;
}
}
-
- // If no injector is found, we *know* that there is no ancestor injector that contains the
- // token, so we abort.
- if (injectorIndex === -1) {
- break;
+ if (shouldSearchParent(flags, parentLocation) &&
+ bloomHasToken(bloomHash, injectorIndex, lViewData)) {
+ // The def wasn't found anywhere on this node, so it was a false positive.
+ // Traverse up the tree and continue searching.
+ previousTView = tView;
+ injectorIndex = getParentInjectorIndex(parentLocation);
+ lViewData = getParentInjectorView(parentLocation, lViewData);
+ } else {
+ // If we should not search parent OR If the ancestor bloom filter value does not have the
+ // bit corresponding to the directive we can give up on traversing up to find the specific
+ // injector.
+ injectorIndex = -1;
}
-
- // At this point, we have an injector which *may* contain the token, so we step through the
- // directives associated with the injector's corresponding node to get the directive instance.
- let instance: T|null;
- if (instance = searchDirectivesOnInjector(injectorIndex, injectorView, token)) {
- return instance;
- }
-
- // If we *didn't* find the directive for the token and we are searching the current node's
- // injector, it's possible the directive is on this node and hasn't been created yet.
- if (injectorIndex === startInjectorIndex && hostView === injectorView &&
- (instance = searchMatchesQueuedForCreation(token, injectorView[TVIEW]))) {
- return instance;
- }
-
- // The def wasn't found anywhere on this node, so it was a false positive.
- // Traverse up the tree and continue searching.
- injectorIndex = parentLocation & InjectorLocationFlags.InjectorIndexMask;
- injectorView = getParentInjectorView(parentLocation, injectorView);
}
}
- const moduleInjector = hostView[INJECTOR];
- const formerInjector = setCurrentInjector(moduleInjector);
- try {
- return inject(token, flags);
- } finally {
- setCurrentInjector(formerInjector);
- }
-}
-
-function searchMatchesQueuedForCreation(token: any, hostTView: TView): T|null {
- const matches = hostTView.currentMatches;
- if (matches) {
- for (let i = 0; i < matches.length; i += 2) {
- const def = matches[i] as DirectiveDef;
- if (def.type === token) {
- return resolveDirective(def, i + 1, matches);
- }
+ if ((flags & (InjectFlags.Self | InjectFlags.Host)) === 0) {
+ const moduleInjector = lViewData[INJECTOR];
+ if (moduleInjector) {
+ return moduleInjector.get(token, notFoundValue, flags & InjectFlags.Optional);
+ } else {
+ return injectRootLimpMode(token, notFoundValue, flags & InjectFlags.Optional);
}
}
- return null;
+ if (flags & InjectFlags.Optional) {
+ return notFoundValue;
+ } else {
+ throw new Error(`NodeInjector: NOT_FOUND [${stringify(token)}]`);
+ }
}
-function searchDirectivesOnInjector(
- injectorIndex: number, injectorView: LViewData, token: Type| InjectionToken) {
- const tNode = injectorView[TVIEW].data[injectorIndex + TNODE] as TNode;
+const NOT_FOUND = {};
+
+function searchTokensOnInjector(
+ injectorIndex: number, injectorView: LViewData, token: Type| InjectionToken,
+ previousTView: TView | null) {
+ const currentTView = injectorView[TVIEW];
+ const tNode = currentTView.data[injectorIndex + TNODE] as TNode;
const nodeFlags = tNode.flags;
- const count = nodeFlags & TNodeFlags.DirectiveCountMask;
-
- if (count !== 0) {
- const start = nodeFlags >> TNodeFlags.DirectiveStartingIndexShift;
- const end = start + count;
- const defs = injectorView[TVIEW].data;
-
- for (let i = start; i < end; i++) {
- // Get the definition for the directive at this index and, if it is injectable (diPublic),
- // and matches the given token, return the directive instance.
- const directiveDef = defs[i] as DirectiveDef;
- if (directiveDef.type === token && directiveDef.diPublic) {
- return injectorView[i];
- }
+ const nodeProviderIndexes = tNode.providerIndexes;
+ const tInjectables = currentTView.data;
+ // First, we step through providers
+ let canAccessViewProviders = false;
+ // We need to determine if view providers can be accessed by the starting element.
+ // It happens in 2 cases:
+ // 1) On the initial element injector , if we are instantiating a token which can see the
+ // viewProviders of the component of that element. Such token are:
+ // - the component itself (but not other directives)
+ // - viewProviders tokens of the component (but not providers tokens)
+ // 2) Upper in the element injector tree, if the starting element is actually in the view of
+ // the current element. To determine this, we track the transition of view during the climb,
+ // and check the host node of the current view to identify component views.
+ if (previousTView == null && isComponent(tNode) && includeViewProviders ||
+ previousTView != null && previousTView != currentTView &&
+ (currentTView.node == null || currentTView.node !.type === TNodeType.Element)) {
+ canAccessViewProviders = true;
+ }
+ const startInjectables = nodeProviderIndexes & TNodeProviderIndexes.ProvidersStartIndexMask;
+ const startDirectives = nodeFlags >> TNodeFlags.DirectiveStartingIndexShift;
+ const cptViewProvidersCount =
+ nodeProviderIndexes >> TNodeProviderIndexes.CptViewProvidersCountShift;
+ const startingIndex =
+ canAccessViewProviders ? startInjectables : startInjectables + cptViewProvidersCount;
+ const directiveCount = nodeFlags & TNodeFlags.DirectiveCountMask;
+ for (let i = startingIndex; i < startDirectives + directiveCount; i++) {
+ const providerTokenOrDef = tInjectables[i] as InjectionToken| Type| DirectiveDef;
+ if (i < startDirectives && token === providerTokenOrDef ||
+ i >= startDirectives && (providerTokenOrDef as DirectiveDef).type === token) {
+ return getNodeInjectable(tInjectables, injectorView, i, tNode as TElementNode);
}
}
- return null;
+ return NOT_FOUND;
+}
+
+/**
+* Retrieve or instantiate the injectable from the `lData` at particular `index`.
+*
+* This function checks to see if the value has already been instantiated and if so returns the
+* cached `injectable`. Otherwise if it detects that the value is still a factory it
+* instantiates the `injectable` and caches the value.
+*/
+export function getNodeInjectable(
+ tData: TData, lData: LViewData, index: number, tNode: TElementNode): any {
+ let value = lData[index];
+ if (isFactory(value)) {
+ const factory: NodeInjectorFactory = value;
+ if (factory.resolving) {
+ throw new Error(`Circular dep for ${stringify(tData[index])}`);
+ }
+ const previousIncludeViewProviders = setIncludeViewProviders(factory.canSeeViewProviders);
+ factory.resolving = true;
+ let previousInjectImplementation;
+ if (factory.injectImpl) {
+ previousInjectImplementation = setInjectImplementation(factory.injectImpl);
+ }
+ const savePreviousOrParentTNode = getPreviousOrParentTNode();
+ const saveViewData = getViewData();
+ setTNodeAndViewData(tNode, lData);
+ try {
+ value = lData[index] = factory.factory(null, tData, lData, tNode);
+ } finally {
+ if (factory.injectImpl) setInjectImplementation(previousInjectImplementation);
+ setIncludeViewProviders(previousIncludeViewProviders);
+ factory.resolving = false;
+ setTNodeAndViewData(savePreviousOrParentTNode, saveViewData);
+ }
+ }
+ return value;
}
/**
@@ -452,7 +470,7 @@ export function bloomHashBitOrFactory(token: Type| InjectionToken): nu
return typeof tokenId === 'number' ? tokenId & BLOOM_MASK : tokenId;
}
-export function injectorHasToken(
+export function bloomHasToken(
bloomHash: number, injectorIndex: number, injectorView: LViewData | TData) {
// Create a mask that targets the specific bit associated with the directive we're looking for.
// JS bit operations are 32 bits, so this will be a number between 2^0 and 2^31, corresponding
@@ -481,26 +499,14 @@ export function injectorHasToken(
}
/** Returns true if flags prevent parent injector from being searched for tokens */
-function shouldNotSearchParent(flags: InjectFlags, parentLocation: number): boolean|number {
- return flags & InjectFlags.Self ||
- (flags & InjectFlags.Host && (parentLocation >> InjectorLocationFlags.ViewOffsetShift) > 0);
+function shouldSearchParent(flags: InjectFlags, parentLocation: RelativeInjectorLocation): boolean|
+ number {
+ return !(
+ flags & InjectFlags.Self ||
+ (flags & InjectFlags.Host && getParentInjectorViewOffset(parentLocation) > 0));
}
-export class NodeInjector implements Injector {
- private _injectorIndex: number;
-
- constructor(
- private _tNode: TElementNode|TContainerNode|TElementContainerNode,
- private _hostView: LViewData) {
- this._injectorIndex = getOrCreateNodeInjectorForNode(_tNode, _hostView);
- }
-
- get(token: any): any {
- setEnvironment(this._tNode, this._hostView);
- return getOrCreateInjectable(this._tNode, this._hostView, token);
- }
-}
-export function getFactoryOf(type: Type): ((type?: Type) => T)|null {
+export function getFactoryOf(type: Type): ((type: Type| null) => T)|null {
const typeAny = type as any;
const def = getComponentDef(typeAny) || getDirectiveDef(typeAny) ||
getPipeDef(typeAny) || getInjectableDef(typeAny) || getInjectorDef(typeAny);
diff --git a/packages/core/src/render3/di_setup.ts b/packages/core/src/render3/di_setup.ts
new file mode 100644
index 0000000000..770f16ccbd
--- /dev/null
+++ b/packages/core/src/render3/di_setup.ts
@@ -0,0 +1,260 @@
+/*
+ * @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 {resolveForwardRef} from '../di/forward_ref';
+import {Provider} from '../di/provider';
+import {isTypeProvider, providerToFactory} from '../di/r3_injector';
+
+import {DirectiveDef} from '.';
+import {diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode} from './di';
+import {directiveInject} from './instructions';
+import {NodeInjectorFactory} from './interfaces/injector';
+import {TContainerNode, TElementContainerNode, TElementNode, TNodeFlags, TNodeProviderIndexes} from './interfaces/node';
+import {LViewData, TData, TVIEW, TView} from './interfaces/view';
+import {getPreviousOrParentTNode, getViewData} from './state';
+import {isComponentDef} from './util';
+
+
+
+/**
+ * Resolves the providers which are defined in the DirectiveDef.
+ *
+ * When inserting the tokens and the factories in their respective arrays, we can assume that
+ * this method is called first for the component (if any), and then for other directives on the same
+ * node.
+ * As a consequence,the providers are always processed in that order:
+ * 1) The view providers of the component
+ * 2) The providers of the component
+ * 3) The providers of the other directives
+ * This matches the structure of the injectables arrays of a view (for each node).
+ * So the tokens and the factories can be pushed at the end of the arrays, except
+ * in one case for multi providers.
+ *
+ * @param def the directive definition
+ * @param providers: Array of `providers`.
+ * @param viewProviders: Array of `viewProviders`.
+ */
+export function providersResolver(
+ def: DirectiveDef, providers: Provider[], viewProviders: Provider[]): void {
+ const viewData = getViewData();
+ const tView: TView = viewData[TVIEW];
+ if (tView.firstTemplatePass) {
+ const isComponent = isComponentDef(def);
+
+ // The list of view providers is processed first, and the flags are updated
+ resolveProvider(viewProviders, tView.data, tView.blueprint, isComponent, true);
+
+ // Then, the list of providers is processed, and the flags are updated
+ resolveProvider(providers, tView.data, tView.blueprint, isComponent, false);
+ }
+}
+
+/**
+* Resolves a provider and publishes it to the DI system.
+*/
+function resolveProvider(
+ provider: Provider, tInjectables: TData, lInjectablesBlueprint: NodeInjectorFactory[],
+ isComponent: boolean, isViewProvider: boolean): void {
+ provider = resolveForwardRef(provider);
+ if (Array.isArray(provider)) {
+ // Recursively call `resolveProvider`
+ // Recursion is OK in this case because this code will not be in hot-path once we implement
+ // cloning of the initial state.
+ for (let i = 0; i < provider.length; i++) {
+ resolveProvider(
+ provider[i], tInjectables, lInjectablesBlueprint, isComponent, isViewProvider);
+ }
+ } else {
+ const viewData = getViewData();
+ let token: any = isTypeProvider(provider) ? provider : resolveForwardRef(provider.provide);
+ let providerFactory: () => any = providerToFactory(provider);
+
+ const previousOrParentTNode = getPreviousOrParentTNode();
+ const beginIndex =
+ previousOrParentTNode.providerIndexes & TNodeProviderIndexes.ProvidersStartIndexMask;
+ const endIndex = previousOrParentTNode.flags >> TNodeFlags.DirectiveStartingIndexShift;
+ const cptViewProvidersCount =
+ previousOrParentTNode.providerIndexes >> TNodeProviderIndexes.CptViewProvidersCountShift;
+
+ if (isTypeProvider(provider) || !provider.multi) {
+ // Single provider case: the factory is created and pushed immediately
+ const factory = new NodeInjectorFactory(providerFactory, isViewProvider, directiveInject);
+ const existingFactoryIndex = indexOf(
+ token, tInjectables, isViewProvider ? beginIndex : beginIndex + cptViewProvidersCount,
+ endIndex);
+ if (existingFactoryIndex == -1) {
+ diPublicInInjector(
+ getOrCreateNodeInjectorForNode(
+ previousOrParentTNode as TElementNode | TContainerNode | TElementContainerNode,
+ viewData),
+ viewData, token);
+ tInjectables.push(token);
+ previousOrParentTNode.flags += 1 << TNodeFlags.DirectiveStartingIndexShift;
+ if (isViewProvider) {
+ previousOrParentTNode.providerIndexes +=
+ TNodeProviderIndexes.CptViewProvidersCountShifter;
+ }
+ lInjectablesBlueprint.push(factory);
+ viewData.push(factory);
+ } else {
+ lInjectablesBlueprint[existingFactoryIndex] = factory;
+ viewData[existingFactoryIndex] = factory;
+ }
+ } else {
+ // Multi provider case:
+ // We create a multi factory which is going to aggregate all the values.
+ // Since the output of such a factory depends on content or view injection,
+ // we create two of them, which are linked together.
+ //
+ // The first one (for view providers) is always in the first block of the injectables array,
+ // and the second one (for providers) is always in the second block.
+ // This is important because view providers have higher priority. When a multi token
+ // is being looked up, the view providers should be found first.
+ // Note that it is not possible to have a multi factory in the third block (directive block).
+ //
+ // The algorithm to process multi providers is as follows:
+ // 1) If the multi provider comes from the `viewProviders` of the component:
+ // a) If the special view providers factory doesn't exist, it is created and pushed.
+ // b) Else, the multi provider is added to the existing multi factory.
+ // 2) If the multi provider comes from the `providers` of the component or of another
+ // directive:
+ // a) If the multi factory doesn't exist, it is created and provider pushed into it.
+ // It is also linked to the multi factory for view providers, if it exists.
+ // b) Else, the multi provider is added to the existing multi factory.
+
+ const existingProvidersFactoryIndex =
+ indexOf(token, tInjectables, beginIndex + cptViewProvidersCount, endIndex);
+ const existingViewProvidersFactoryIndex =
+ indexOf(token, tInjectables, beginIndex, beginIndex + cptViewProvidersCount);
+ const doesProvidersFactoryExist = existingProvidersFactoryIndex >= 0 &&
+ lInjectablesBlueprint[existingProvidersFactoryIndex];
+ const doesViewProvidersFactoryExist = existingViewProvidersFactoryIndex >= 0 &&
+ lInjectablesBlueprint[existingViewProvidersFactoryIndex];
+
+ if (isViewProvider && !doesViewProvidersFactoryExist ||
+ !isViewProvider && !doesProvidersFactoryExist) {
+ // Cases 1.a and 2.a
+ diPublicInInjector(
+ getOrCreateNodeInjectorForNode(
+ previousOrParentTNode as TElementNode | TContainerNode | TElementContainerNode,
+ viewData),
+ viewData, token);
+ const factory = multiFactory(
+ isViewProvider ? multiViewProvidersFactoryResolver : multiProvidersFactoryResolver,
+ lInjectablesBlueprint.length, isViewProvider, isComponent, providerFactory);
+ if (!isViewProvider && doesViewProvidersFactoryExist) {
+ lInjectablesBlueprint[existingViewProvidersFactoryIndex].providerFactory = factory;
+ }
+ tInjectables.push(token);
+ previousOrParentTNode.flags += 1 << TNodeFlags.DirectiveStartingIndexShift;
+ if (isViewProvider) {
+ previousOrParentTNode.providerIndexes +=
+ TNodeProviderIndexes.CptViewProvidersCountShifter;
+ }
+ lInjectablesBlueprint.push(factory);
+ viewData.push(factory);
+ } else {
+ // Cases 1.b and 2.b
+ multiFactoryAdd(
+ lInjectablesBlueprint ![isViewProvider ? existingViewProvidersFactoryIndex : existingProvidersFactoryIndex],
+ providerFactory, !isViewProvider && isComponent);
+ }
+ if (!isViewProvider && isComponent && doesViewProvidersFactoryExist) {
+ lInjectablesBlueprint[existingViewProvidersFactoryIndex].componentProviders !++;
+ }
+ }
+ }
+}
+
+/**
+* Add a factory in a multi factory.
+*/
+function multiFactoryAdd(
+ multiFactory: NodeInjectorFactory, factory: () => any, isComponentProvider: boolean): void {
+ multiFactory.multi !.push(factory);
+ if (isComponentProvider) {
+ multiFactory.componentProviders !++;
+ }
+}
+
+/**
+* Returns the index of item in the array, but only in the begin to end range.
+*/
+function indexOf(item: any, arr: any[], begin: number, end: number) {
+ for (let i = begin; i < end; i++) {
+ if (arr[i] === item) return i;
+ }
+ return -1;
+}
+
+/**
+* Use this with `multi` `providers`.
+*/
+function multiProvidersFactoryResolver(
+ this: NodeInjectorFactory, _: null, tData: TData, lData: LViewData,
+ tNode: TElementNode): any[] {
+ return multiResolve(this.multi !, []);
+}
+
+/**
+* Use this with `multi` `viewProviders`.
+*
+* This factory knows how to concatenate itself with the existing `multi` `providers`.
+*/
+function multiViewProvidersFactoryResolver(
+ this: NodeInjectorFactory, _: null, tData: TData, lData: LViewData,
+ tNode: TElementNode): any[] {
+ const factories = this.multi !;
+ let result: any[];
+ if (this.providerFactory) {
+ const componentCount = this.providerFactory.componentProviders !;
+ const multiProviders = getNodeInjectable(tData, lData, this.providerFactory !.index !, tNode);
+ // Copy the section of the array which contains `multi` `providers` from the component
+ result = multiProviders.slice(0, componentCount);
+ // Insert the `viewProvider` instances.
+ multiResolve(factories, result);
+ // Copy the section of the array which contains `multi` `providers` from other directives
+ for (let i = componentCount; i < multiProviders.length; i++) {
+ result.push(multiProviders[i]);
+ }
+ } else {
+ result = [];
+ // Insert the `viewProvider` instances.
+ multiResolve(factories, result);
+ }
+ return result;
+}
+
+/**
+* Maps an array of factories into an array of values.
+*/
+function multiResolve(factories: Array<() => any>, result: any[]): any[] {
+ for (let i = 0; i < factories.length; i++) {
+ const factory = factories[i] !as() => null;
+ result.push(factory());
+ }
+ return result;
+}
+
+/**
+* Creates a multi factory.
+*/
+function multiFactory(
+ factoryFn:
+ (this: NodeInjectorFactory, _: null, tData: TData, lData: LViewData, tNode: TElementNode) =>
+ any,
+ index: number, isViewProvider: boolean, isComponent: boolean,
+ f: () => any): NodeInjectorFactory {
+ const factory = new NodeInjectorFactory(factoryFn, isViewProvider, directiveInject);
+ factory.multi = [];
+ factory.index = index;
+ factory.componentProviders = 0;
+ multiFactoryAdd(factory, f, isComponent && !isViewProvider);
+ return factory;
+}
diff --git a/packages/core/src/render3/discovery_utils.ts b/packages/core/src/render3/discovery_utils.ts
index 1752d045d8..e3c84a51ff 100644
--- a/packages/core/src/render3/discovery_utils.ts
+++ b/packages/core/src/render3/discovery_utils.ts
@@ -9,11 +9,12 @@ import {Injector} from '../di/injector';
import {assertDefined} from './assert';
import {discoverDirectives, discoverLocalRefs, getContext, isComponentInstance} from './context_discovery';
-import {NodeInjector} from './di';
import {LContext} from './interfaces/context';
import {TElementNode, TNode, TNodeFlags} from './interfaces/node';
import {CONTEXT, FLAGS, LViewData, LViewFlags, PARENT, RootContext, TVIEW} from './interfaces/view';
import {getComponentViewByIndex, readPatchedLViewData} from './util';
+import {NodeInjector} from './view_engine_compatibility';
+
/**
diff --git a/packages/core/src/render3/features/inherit_definition_feature.ts b/packages/core/src/render3/features/inherit_definition_feature.ts
index d3dc00db92..3ebf845580 100644
--- a/packages/core/src/render3/features/inherit_definition_feature.ts
+++ b/packages/core/src/render3/features/inherit_definition_feature.ts
@@ -102,9 +102,9 @@ export function InheritDefinitionFeature(definition: DirectiveDef| Componen
const superContentQueries = superDef.contentQueries;
if (superContentQueries) {
if (prevContentQueries) {
- definition.contentQueries = () => {
- superContentQueries();
- prevContentQueries();
+ definition.contentQueries = (dirIndex: number) => {
+ superContentQueries(dirIndex);
+ prevContentQueries(dirIndex);
};
} else {
definition.contentQueries = superContentQueries;
diff --git a/packages/core/src/render3/features/providers_feature.ts b/packages/core/src/render3/features/providers_feature.ts
new file mode 100644
index 0000000000..7ecca8807f
--- /dev/null
+++ b/packages/core/src/render3/features/providers_feature.ts
@@ -0,0 +1,45 @@
+/**
+ * @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 {Provider} from '../../di/provider';
+import {providersResolver} from '../di_setup';
+import {DirectiveDef} from '../interfaces/definition';
+
+/**
+ * This feature resolves the providers of a directive (or component),
+ * and publish them into the DI system, making it visible to others for injection.
+ *
+ * For example:
+ * class ComponentWithProviders {
+ * constructor(private greeter: GreeterDE) {}
+ *
+ * static ngComponentDef = defineComponent({
+ * type: ComponentWithProviders,
+ * selectors: [['component-with-providers']],
+ * factory: () => new ComponentWithProviders(directiveInject(GreeterDE as any)),
+ * consts: 1,
+ * vars: 1,
+ * template: function(fs: RenderFlags, ctx: ComponentWithProviders) {
+ * if (fs & RenderFlags.Create) {
+ * text(0);
+ * }
+ * if (fs & RenderFlags.Update) {
+ * textBinding(0, bind(ctx.greeter.greet()));
+ * }
+ * },
+ * features: [ProvidersFeature([GreeterDE])]
+ * });
+ * }
+ *
+ * @param definition
+ */
+export function ProvidersFeature(providers: Provider[], viewProviders: Provider[] = []) {
+ return (definition: DirectiveDef) => {
+ definition.providersResolver = (def: DirectiveDef) =>
+ providersResolver(def, providers, viewProviders);
+ };
+}
diff --git a/packages/core/src/render3/features/public_feature.ts b/packages/core/src/render3/features/public_feature.ts
deleted file mode 100644
index dd1925e7a3..0000000000
--- a/packages/core/src/render3/features/public_feature.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * @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 {diPublic} from '../di';
-import {DirectiveDef} from '../interfaces/definition';
-
-/**
- * This feature publishes the directive (or component) into the DI system, making it visible to
- * others for injection.
- *
- * @param definition
- */
-export function PublicFeature(definition: DirectiveDef) {
- definition.diPublic = diPublic;
-}
diff --git a/packages/core/src/render3/i18n.ts b/packages/core/src/render3/i18n.ts
index 3d62a72c2d..c92dd51091 100644
--- a/packages/core/src/render3/i18n.ts
+++ b/packages/core/src/render3/i18n.ts
@@ -9,13 +9,14 @@
import {NO_CHANGE} from '../../src/render3/tokens';
import {assertEqual, assertLessThan} from './assert';
-import {_getViewData, adjustBlueprintForNewNode, bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4, createNodeAtIndex, getRenderer, load, resetComponentState} from './instructions';
+import {adjustBlueprintForNewNode, bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4, createNodeAtIndex, load} from './instructions';
import {LContainer, NATIVE, RENDER_PARENT} from './interfaces/container';
import {TElementNode, TNode, TNodeType} from './interfaces/node';
import {RComment, RElement} from './interfaces/renderer';
import {StylingContext} from './interfaces/styling';
import {BINDING_INDEX, HEADER_OFFSET, HOST_NODE, TVIEW} from './interfaces/view';
import {appendChild, createTextNode, removeChild} from './node_manipulation';
+import {getRenderer, getViewData, resetComponentState} from './state';
import {getNativeByIndex, getNativeByTNode, getTNode, isLContainer, stringify} from './util';
@@ -256,7 +257,7 @@ function appendI18nNode(tNode: TNode, parentTNode: TNode, previousTNode: TNode):
ngDevMode.rendererMoveNode++;
}
- const viewData = _getViewData();
+ const viewData = getViewData();
// On first pass, re-organize node tree to put this node in the correct position.
const firstTemplatePass = viewData[TVIEW].firstTemplatePass;
@@ -311,7 +312,7 @@ export function i18nEnd(): void {
* @param instructions The list of instructions to apply on the current view.
*/
export function i18nApply(startIndex: number, instructions: I18nInstruction[]): void {
- const viewData = _getViewData();
+ const viewData = getViewData();
if (ngDevMode) {
assertEqual(
viewData[BINDING_INDEX], viewData[TVIEW].bindingStartIndex,
@@ -411,7 +412,7 @@ export function i18nExpMapping(
* @returns The concatenated string when any of the arguments changes, `NO_CHANGE` otherwise.
*/
export function i18nInterpolation1(instructions: I18nExpInstruction[], v0: any): string|NO_CHANGE {
- const different = bindingUpdated(_getViewData()[BINDING_INDEX]++, v0);
+ const different = bindingUpdated(getViewData()[BINDING_INDEX]++, v0);
if (!different) {
return NO_CHANGE;
@@ -442,7 +443,7 @@ export function i18nInterpolation1(instructions: I18nExpInstruction[], v0: any):
*/
export function i18nInterpolation2(instructions: I18nExpInstruction[], v0: any, v1: any): string|
NO_CHANGE {
- const viewData = _getViewData();
+ const viewData = getViewData();
const different = bindingUpdated2(viewData[BINDING_INDEX], v0, v1);
viewData[BINDING_INDEX] += 2;
@@ -482,7 +483,7 @@ export function i18nInterpolation2(instructions: I18nExpInstruction[], v0: any,
*/
export function i18nInterpolation3(
instructions: I18nExpInstruction[], v0: any, v1: any, v2: any): string|NO_CHANGE {
- const viewData = _getViewData();
+ const viewData = getViewData();
const different = bindingUpdated3(viewData[BINDING_INDEX], v0, v1, v2);
viewData[BINDING_INDEX] += 3;
@@ -524,7 +525,7 @@ export function i18nInterpolation3(
*/
export function i18nInterpolation4(
instructions: I18nExpInstruction[], v0: any, v1: any, v2: any, v3: any): string|NO_CHANGE {
- const viewData = _getViewData();
+ const viewData = getViewData();
const different = bindingUpdated4(viewData[BINDING_INDEX], v0, v1, v2, v3);
viewData[BINDING_INDEX] += 4;
@@ -568,7 +569,7 @@ export function i18nInterpolation4(
export function i18nInterpolation5(
instructions: I18nExpInstruction[], v0: any, v1: any, v2: any, v3: any, v4: any): string|
NO_CHANGE {
- const viewData = _getViewData();
+ const viewData = getViewData();
let different = bindingUpdated4(viewData[BINDING_INDEX], v0, v1, v2, v3);
different = bindingUpdated(viewData[BINDING_INDEX] + 4, v4) || different;
viewData[BINDING_INDEX] += 5;
@@ -615,7 +616,7 @@ export function i18nInterpolation5(
i18nInterpolation6(
instructions: I18nExpInstruction[], v0: any, v1: any, v2: any, v3: any, v4: any, v5: any):
string|NO_CHANGE {
- const viewData = _getViewData();
+ const viewData = getViewData();
let different = bindingUpdated4(viewData[BINDING_INDEX], v0, v1, v2, v3);
different = bindingUpdated2(viewData[BINDING_INDEX] + 4, v4, v5) || different;
viewData[BINDING_INDEX] += 6;
@@ -663,7 +664,7 @@ i18nInterpolation6(
export function i18nInterpolation7(
instructions: I18nExpInstruction[], v0: any, v1: any, v2: any, v3: any, v4: any, v5: any,
v6: any): string|NO_CHANGE {
- const viewData = _getViewData();
+ const viewData = getViewData();
let different = bindingUpdated4(viewData[BINDING_INDEX], v0, v1, v2, v3);
different = bindingUpdated3(viewData[BINDING_INDEX] + 4, v4, v5, v6) || different;
viewData[BINDING_INDEX] += 7;
@@ -712,7 +713,7 @@ export function i18nInterpolation7(
export function i18nInterpolation8(
instructions: I18nExpInstruction[], v0: any, v1: any, v2: any, v3: any, v4: any, v5: any,
v6: any, v7: any): string|NO_CHANGE {
- const viewData = _getViewData();
+ const viewData = getViewData();
let different = bindingUpdated4(viewData[BINDING_INDEX], v0, v1, v2, v3);
different = bindingUpdated4(viewData[BINDING_INDEX] + 4, v4, v5, v6, v7) || different;
viewData[BINDING_INDEX] += 8;
@@ -753,7 +754,7 @@ export function i18nInterpolation8(
*/
export function i18nInterpolationV(instructions: I18nExpInstruction[], values: any[]): string|
NO_CHANGE {
- const viewData = _getViewData();
+ const viewData = getViewData();
let different = false;
for (let i = 0; i < values.length; i++) {
// Check if bindings have changed
diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts
index 08f80f3e4e..5293a856bd 100644
--- a/packages/core/src/render3/index.ts
+++ b/packages/core/src/render3/index.ts
@@ -9,11 +9,11 @@ import {LifecycleHooksFeature, getHostElement, getRenderedText, renderComponent,
import {defineBase, defineComponent, defineDirective, defineNgModule, definePipe} from './definition';
import {InheritDefinitionFeature} from './features/inherit_definition_feature';
import {NgOnChangesFeature} from './features/ng_onchanges_feature';
-import {PublicFeature} from './features/public_feature';
+import {ProvidersFeature} from './features/providers_feature';
import {BaseDef, ComponentDef, ComponentDefWithMeta, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveDefWithMeta, DirectiveType, PipeDef, PipeDefWithMeta} from './interfaces/definition';
export {ComponentFactory, ComponentFactoryResolver, ComponentRef, WRAP_RENDERER_FACTORY2, injectComponentFactoryResolver} from './component_ref';
-export {directiveInject, getFactoryOf, getInheritedFactory, injectAttribute} from './di';
+export {getFactoryOf, getInheritedFactory} from './di';
export {RenderFlags} from './interfaces/definition';
export {CssSelectorList} from './interfaces/projection';
@@ -51,9 +51,6 @@ export {
elementStyleProp,
elementStylingApply,
- getCurrentView,
- restoreView,
-
listener,
store,
load,
@@ -62,9 +59,6 @@ export {
namespaceMathML,
namespaceSVG,
- enableBindings,
- disableBindings,
-
projection,
projectionDef,
@@ -79,8 +73,19 @@ export {
detectChanges,
markDirty,
tick,
+
+ directiveInject,
+ injectAttribute,
} from './instructions';
+export {
+ getCurrentView,
+ restoreView,
+
+ enableBindings,
+ disableBindings,
+} from './state';
+
export {
i18nAttribute,
i18nExp,
@@ -157,7 +162,7 @@ export {
DirectiveType,
NgOnChangesFeature,
InheritDefinitionFeature,
- PublicFeature,
+ ProvidersFeature,
PipeDef,
PipeDefWithMeta,
LifecycleHooksFeature,
diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts
index 059f146c5c..84a8170cdc 100644
--- a/packages/core/src/render3/instructions.ts
+++ b/packages/core/src/render3/instructions.ts
@@ -8,32 +8,37 @@
import './ng_dev_mode';
+import {InjectionToken} from '../di/injection_token';
+import {InjectFlags} from '../di/injector';
import {QueryList} from '../linker';
import {Sanitizer} from '../sanitization/security';
import {StyleSanitizeFn} from '../sanitization/style_sanitizer';
+import {Type} from '../type';
import {assertDefined, assertEqual, assertLessThan, assertNotEqual} from './assert';
import {attachPatchData, getComponentViewByInstance} from './context_discovery';
-import {throwCyclicDependencyError, throwErrorIfNoChangesMode, throwMultipleComponentError} from './errors';
+import {diPublicInInjector, getNodeInjectable, getOrCreateInjectable, getOrCreateNodeInjectorForNode, injectAttributeImpl} from './di';
+import {throwErrorIfNoChangesMode, throwMultipleComponentError} from './errors';
import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} from './hooks';
import {ACTIVE_INDEX, LContainer, VIEWS} from './interfaces/container';
import {ComponentDef, ComponentQuery, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, InitialStylingFlags, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
-import {INJECTOR_SIZE} from './interfaces/injector';
+import {INJECTOR_SIZE, NodeInjectorFactory} from './interfaces/injector';
import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeType, TProjectionNode, TViewNode} from './interfaces/node';
import {PlayerFactory} from './interfaces/player';
import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection';
import {LQueries} from './interfaces/query';
import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, RendererFactory3, isProceduralRenderer} from './interfaces/renderer';
import {StylingIndex} from './interfaces/styling';
-import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTENT_QUERIES, CONTEXT, CurrentMatchesList, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, HOST_NODE, INJECTOR, LViewData, LViewFlags, NEXT, OpaqueViewState, PARENT, QUERIES, RENDERER, RootContext, RootContextFlags, SANITIZER, TAIL, TVIEW, TView} from './interfaces/view';
+import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTENT_QUERIES, CONTEXT, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, HOST_NODE, INJECTOR, LViewData, LViewFlags, NEXT, OpaqueViewState, PARENT, QUERIES, RENDERER, RootContext, RootContextFlags, SANITIZER, TAIL, TVIEW, TView} from './interfaces/view';
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
import {appendChild, appendProjectedNode, createTextNode, findComponentView, getLViewChild, getRenderParent, insertView, removeView} from './node_manipulation';
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
+import {assertDataInRange, assertHasParent, assertPreviousIsParent, decreaseElementDepthCount, enterView, getBindingsEnabled, getCheckNoChangesMode, getCleanup, getContextViewData, getCreationMode, getCurrentQueries, getCurrentSanitizer, getElementDepthCount, getFirstTemplatePass, getIsParent, getPreviousOrParentTNode, getRenderer, getRendererFactory, getTView, getTViewCleanup, getViewData, increaseElementDepthCount, leaveView, nextContextImpl, resetComponentState, setBindingRoot, setCheckNoChangesMode, setCurrentQueries, setFirstTemplatePass, setIsParent, setPreviousOrParentTNode, setRenderer, setRendererFactory} from './state';
import {createStylingContextTemplate, renderStyleAndClassBindings, updateClassProp as updateElementClassProp, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling/class_and_style_bindings';
import {BoundPlayerFactory} from './styling/player_factory';
import {getStylingContext} from './styling/util';
import {NO_CHANGE} from './tokens';
-import {assertDataInRangeInternal, getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getRootContext, getRootView, getTNode, isComponent, isContentQueryHost, isDifferent, loadInternal, readPatchedLViewData, stringify} from './util';
+import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getRootContext, getRootView, getTNode, isComponent, isComponentDef, isDifferent, loadInternal, readPatchedLViewData, stringify} from './util';
@@ -43,271 +48,15 @@ import {assertDataInRangeInternal, getComponentViewByIndex, getNativeByIndex, ge
*/
const _CLEAN_PROMISE = Promise.resolve(null);
-/**
- * Function used to sanitize the value before writing it into the renderer.
- */
-export type SanitizerFn = (value: any) => string;
-
-/**
- * Token set in currentMatches while dependencies are being resolved.
- *
- * If we visit a directive that has a value set to CIRCULAR, we know we've
- * already seen it, and thus have a circular dependency.
- */
-export const CIRCULAR = '__CIRCULAR__';
-
-/**
- * This property gets set before entering a template.
- *
- * This renderer can be one of two varieties of Renderer3:
- *
- * - ObjectedOrientedRenderer3
- *
- * This is the native browser API style, e.g. operations are methods on individual objects
- * like HTMLElement. With this style, no additional code is needed as a facade (reducing payload
- * size).
- *
- * - ProceduralRenderer3
- *
- * In non-native browser environments (e.g. platforms such as web-workers), this is the facade
- * that enables element manipulation. This also facilitates backwards compatibility with
- * Renderer2.
- */
-let renderer: Renderer3;
-
-export function getRenderer(): Renderer3 {
- // top level variables should not be exported for performance reasons (PERF_NOTES.md)
- return renderer;
-}
-
-let rendererFactory: RendererFactory3;
-
-export function getRendererFactory(): RendererFactory3 {
- // top level variables should not be exported for performance reasons (PERF_NOTES.md)
- return rendererFactory;
-}
-
-export function getCurrentSanitizer(): Sanitizer|null {
- return viewData && viewData[SANITIZER];
-}
-
-/**
- * Store the element depth count. This is used to identify the root elements of the template
- * so that we can than attach `LViewData` to only those elements.
- */
-let elementDepthCount !: number;
-
-/**
- * Stores whether directives should be matched to elements.
- *
- * When template contains `ngNonBindable` than we need to prevent the runtime form matching
- * directives on children of that element.
- *
- * Example:
- * ```
- *
- * Should match component / directive.
- *
- *
- *
- * Should not match component / directive because we are in ngNonBindable.
- *
- *
- * ```
- */
-let bindingsEnabled !: boolean;
-
-/**
- * Returns the current OpaqueViewState instance.
- *
- * Used in conjunction with the restoreView() instruction to save a snapshot
- * of the current view and restore it when listeners are invoked. This allows
- * walking the declaration view tree in listeners to get vars from parent views.
- */
-export function getCurrentView(): OpaqueViewState {
- return viewData as any as OpaqueViewState;
-}
-
-/**
- * Restores `contextViewData` to the given OpaqueViewState instance.
- *
- * Used in conjunction with the getCurrentView() instruction to save a snapshot
- * of the current view and restore it when listeners are invoked. This allows
- * walking the declaration view tree in listeners to get vars from parent views.
- *
- * @param viewToRestore The OpaqueViewState instance to restore.
- */
-export function restoreView(viewToRestore: OpaqueViewState) {
- contextViewData = viewToRestore as any as LViewData;
-}
-
-/** Used to set the parent property when nodes are created and track query results. */
-let previousOrParentTNode: TNode;
-
-export function getPreviousOrParentTNode(): TNode {
- // top level variables should not be exported for performance reasons (PERF_NOTES.md)
- return previousOrParentTNode;
-}
-
-export function setEnvironment(tNode: TNode, view: LViewData) {
- previousOrParentTNode = tNode;
- viewData = view;
-}
-
-/**
- * If `isParent` is:
- * - `true`: then `previousOrParentTNode` points to a parent node.
- * - `false`: then `previousOrParentTNode` points to previous node (sibling).
- */
-let isParent: boolean;
-
-let tView: TView;
-
-let currentQueries: LQueries|null;
-
-/**
- * Query instructions can ask for "current queries" in 2 different cases:
- * - when creating view queries (at the root of a component view, before any node is created - in
- * this case currentQueries points to view queries)
- * - when creating content queries (i.e. this previousOrParentTNode points to a node on which we
- * create content queries).
- */
-export function getOrCreateCurrentQueries(
- QueryType: {new (parent: null, shallow: null, deep: null): LQueries}): LQueries {
- // if this is the first content query on a node, any existing LQueries needs to be cloned
- // in subsequent template passes, the cloning occurs before directive instantiation.
- if (previousOrParentTNode && previousOrParentTNode !== viewData[HOST_NODE] &&
- !isContentQueryHost(previousOrParentTNode)) {
- currentQueries && (currentQueries = currentQueries.clone());
- previousOrParentTNode.flags |= TNodeFlags.hasContentQuery;
- }
-
- return currentQueries || (currentQueries = new QueryType(null, null, null));
-}
-
-/**
- * This property gets set before entering a template.
- */
-let creationMode: boolean;
-
-export function getCreationMode(): boolean {
- // top level variables should not be exported for performance reasons (PERF_NOTES.md)
- return creationMode;
-}
-
-/**
- * State of the current view being processed.
- *
- * An array of nodes (text, element, container, etc), pipes, their bindings, and
- * any local variables that need to be stored between invocations.
- */
-let viewData: LViewData;
-
-/**
- * Internal function that returns the current LViewData instance.
- *
- * The getCurrentView() instruction should be used for anything public.
- */
-export function _getViewData(): LViewData {
- // top level variables should not be exported for performance reasons (PERF_NOTES.md)
- return viewData;
-}
-
-/**
- * The last viewData retrieved by nextContext().
- * Allows building nextContext() and reference() calls.
- *
- * e.g. const inner = x().$implicit; const outer = x().$implicit;
- */
-let contextViewData: LViewData = null !;
-
-function getCleanup(view: LViewData): any[] {
- // top level variables should not be exported for performance reasons (PERF_NOTES.md)
- return view[CLEANUP] || (view[CLEANUP] = []);
-}
-
-function getTViewCleanup(view: LViewData): any[] {
- return view[TVIEW].cleanup || (view[TVIEW].cleanup = []);
-}
-/**
- * In this mode, any changes in bindings will throw an ExpressionChangedAfterChecked error.
- *
- * Necessary to support ChangeDetectorRef.checkNoChanges().
- */
-let checkNoChangesMode = false;
-
-/** Whether or not this is the first time the current view has been processed. */
-let firstTemplatePass = true;
-
-/**
- * The root index from which pure function instructions should calculate their binding
- * indices. In component views, this is TView.bindingStartIndex. In a host binding
- * context, this is the TView.expandoStartIndex + any dirs/hostVars before the given dir.
- */
-let bindingRootIndex: number = -1;
-
-// top level variables should not be exported for performance reasons (PERF_NOTES.md)
-export function getBindingRoot() {
- return bindingRootIndex;
-}
-
const enum BindingDirection {
Input,
Output,
}
/**
- * Swap the current state with a new state.
- *
- * For performance reasons we store the state in the top level of the module.
- * This way we minimize the number of properties to read. Whenever a new view
- * is entered we have to store the state for later, and when the view is
- * exited the state has to be restored
- *
- * @param newView New state to become active
- * @param host Element to which the View is a child of
- * @returns the previous state;
+ * Function used to sanitize the value before writing it into the renderer.
*/
-export function enterView(
- newView: LViewData, hostTNode: TElementNode | TViewNode | null): LViewData {
- const oldView: LViewData = viewData;
- tView = newView && newView[TVIEW];
-
- creationMode = newView && (newView[FLAGS] & LViewFlags.CreationMode) === LViewFlags.CreationMode;
- firstTemplatePass = newView && tView.firstTemplatePass;
- bindingRootIndex = newView && tView.bindingStartIndex;
- renderer = newView && newView[RENDERER];
-
- previousOrParentTNode = hostTNode !;
- isParent = true;
-
- viewData = contextViewData = newView;
- oldView && (oldView[QUERIES] = currentQueries);
- currentQueries = newView && newView[QUERIES];
-
- return oldView;
-}
-
-/**
- * Used in lieu of enterView to make it clear when we are exiting a child view. This makes
- * the direction of traversal (up or down the view tree) a bit clearer.
- *
- * @param newView New state to become active
- * @param creationOnly An optional boolean to indicate that the view was processed in creation mode
- * only, i.e. the first update will be done later. Only possible for dynamically created views.
- */
-export function leaveView(newView: LViewData, creationOnly?: boolean): void {
- if (!creationOnly) {
- if (!checkNoChangesMode) {
- executeHooks(viewData, tView.viewHooks, tView.viewCheckHooks, creationMode);
- }
- // Views are clean and in update mode after being checked, so these bits are cleared
- viewData[FLAGS] &= ~(LViewFlags.CreationMode | LViewFlags.Dirty);
- }
- viewData[FLAGS] |= LViewFlags.RunInit;
- viewData[BINDING_INDEX] = tView.bindingStartIndex;
- enterView(newView, null);
-}
+type SanitizerFn = (value: any) => string;
/**
* Refreshes the view, executing the following steps in that order:
@@ -315,12 +64,16 @@ export function leaveView(newView: LViewData, creationOnly?: boolean): void {
* bindings, refreshes child components.
* Note: view hooks are triggered later when leaving the view.
*/
-function refreshDescendantViews() {
- setHostBindings();
- const parentFirstTemplatePass = firstTemplatePass;
+function refreshDescendantViews(viewData: LViewData) {
+ const tView = getTView();
+ const creationMode = getCreationMode();
+ const checkNoChangesMode = getCheckNoChangesMode();
+ setHostBindings(tView, viewData);
+ const parentFirstTemplatePass = getFirstTemplatePass();
// This needs to be set before children are processed to support recursive components
- tView.firstTemplatePass = firstTemplatePass = false;
+ tView.firstTemplatePass = false;
+ setFirstTemplatePass(false);
if (!checkNoChangesMode) {
executeInitHooks(viewData, tView, creationMode);
@@ -339,9 +92,10 @@ function refreshDescendantViews() {
/** Sets the host bindings for the current view. */
-export function setHostBindings(): void {
+export function setHostBindings(tView: TView, viewData: LViewData): void {
if (tView.expandoInstructions) {
- bindingRootIndex = viewData[BINDING_INDEX] = tView.expandoStartIndex;
+ let bindingRootIndex = viewData[BINDING_INDEX] = tView.expandoStartIndex;
+ setBindingRoot(bindingRootIndex);
let currentDirectiveIndex = -1;
let currentElementIndex = -1;
for (let i = 0; i < tView.expandoInstructions.length; i++) {
@@ -351,11 +105,9 @@ export function setHostBindings(): void {
// Negative numbers mean that we are starting new EXPANDO block and need to update
// the current element and directive index.
currentElementIndex = -instruction;
- if (typeof viewData[bindingRootIndex] === 'number') {
- // We've hit an injector. It may or may not exist depending on whether
- // there is a public directive on this node.
- bindingRootIndex += INJECTOR_SIZE;
- }
+ // Injector block is taken into account.
+ bindingRootIndex += INJECTOR_SIZE;
+
currentDirectiveIndex = bindingRootIndex;
} else {
// This is either the injector size (so the binding root can skip over directives
@@ -363,6 +115,7 @@ export function setHostBindings(): void {
// (to get to the next set of host bindings on this node).
bindingRootIndex += instruction;
}
+ setBindingRoot(bindingRootIndex);
} else {
// If it's not a number, it's a host binding function that needs to be executed.
viewData[BINDING_INDEX] = bindingRootIndex;
@@ -398,8 +151,10 @@ function refreshChildComponents(
}
}
-export function executeInitAndContentHooks(): void {
- if (!checkNoChangesMode) {
+export function executeInitAndContentHooks(viewData: LViewData): void {
+ if (!getCheckNoChangesMode()) {
+ const tView = getTView();
+ const creationMode = getCreationMode();
executeInitHooks(viewData, tView, creationMode);
executeHooks(viewData, tView.contentHooks, tView.contentCheckHooks, creationMode);
}
@@ -408,6 +163,7 @@ export function executeInitAndContentHooks(): void {
export function createLViewData(
renderer: Renderer3, tView: TView, context: T | null, flags: LViewFlags,
sanitizer?: Sanitizer | null): LViewData {
+ const viewData = getViewData();
const instance = tView.blueprint.slice() as LViewData;
instance[FLAGS] = flags | LViewFlags.CreationMode | LViewFlags.Attached | LViewFlags.RunInit;
instance[PARENT] = instance[DECLARATION_VIEW] = viewData;
@@ -443,6 +199,8 @@ export function createNodeAtIndex(
export function createNodeAtIndex(
index: number, type: TNodeType, native: RText | RElement | RComment | null, name: string | null,
attrs: TAttributes | null): TElementNode&TContainerNode&TElementContainerNode&TProjectionNode {
+ const viewData = getViewData();
+ const tView = getTView();
const adjustedIndex = index + HEADER_OFFSET;
ngDevMode &&
assertLessThan(adjustedIndex, viewData.length, `Slot should have been initialized with null`);
@@ -450,7 +208,10 @@ export function createNodeAtIndex(
let tNode = tView.data[adjustedIndex] as TNode;
if (tNode == null) {
- tNode = tView.data[adjustedIndex] = createTNode(type, adjustedIndex, name, attrs, null);
+ const previousOrParentTNode = getPreviousOrParentTNode();
+ const isParent = getIsParent();
+ tNode = tView.data[adjustedIndex] =
+ createTNode(viewData, type, adjustedIndex, name, attrs, null);
// Now link ourselves into the tree.
if (previousOrParentTNode) {
@@ -468,8 +229,8 @@ export function createNodeAtIndex(
tView.firstChild = tNode;
}
- previousOrParentTNode = tNode;
- isParent = true;
+ setPreviousOrParentTNode(tNode);
+ setIsParent(true);
return tNode as TElementNode & TViewNode & TContainerNode & TElementContainerNode &
TProjectionNode;
}
@@ -478,11 +239,13 @@ export function createViewNode(index: number, view: LViewData) {
// View nodes are not stored in data because they can be added / removed at runtime (which
// would cause indices to change). Their TNodes are instead stored in tView.node.
if (view[TVIEW].node == null) {
- view[TVIEW].node = createTNode(TNodeType.View, index, null, null, null) as TViewNode;
+ view[TVIEW].node = createTNode(view, TNodeType.View, index, null, null, null) as TViewNode;
}
- isParent = true;
- return previousOrParentTNode = view[HOST_NODE] = view[TVIEW].node as TViewNode;
+ setIsParent(true);
+ const tNode = view[TVIEW].node as TViewNode;
+ setPreviousOrParentTNode(tNode);
+ return view[HOST_NODE] = tNode;
}
@@ -505,16 +268,6 @@ export function adjustBlueprintForNewNode(view: LViewData) {
//// Render
//////////////////////////
-/**
- * Resets the application state.
- */
-export function resetComponentState() {
- isParent = false;
- previousOrParentTNode = null !;
- elementDepthCount = 0;
- bindingsEnabled = true;
-}
-
/**
*
* @param hostNode Existing node to render into.
@@ -533,12 +286,16 @@ export function renderTemplate(
sanitizer?: Sanitizer | null): LViewData {
if (hostView == null) {
resetComponentState();
- rendererFactory = providedRendererFactory;
- renderer = providedRendererFactory.createRenderer(null, null);
+ setRendererFactory(providedRendererFactory);
+ const renderer = providedRendererFactory.createRenderer(null, null);
+ setRenderer(renderer);
// We need to create a root view so it's possible to look up the host element through its index
- tView = createTView(-1, null, 1, 0, null, null, null);
- viewData = createLViewData(renderer, tView, {}, LViewFlags.CheckAlways | LViewFlags.IsRoot);
+ enterView(
+ createLViewData(
+ renderer, createTView(-1, null, 1, 0, null, null, null), {},
+ LViewFlags.CheckAlways | LViewFlags.IsRoot),
+ null);
const componentTView =
getOrCreateTView(templateFn, consts, vars, directives || null, pipes || null, null);
@@ -559,10 +316,10 @@ export function renderTemplate(
export function createEmbeddedViewAndNode(
tView: TView, context: T, declarationView: LViewData, renderer: Renderer3,
queries: LQueries | null, injectorIndex: number): LViewData {
- const _isParent = isParent;
- const _previousOrParentTNode = previousOrParentTNode;
- isParent = true;
- previousOrParentTNode = null !;
+ const _isParent = getIsParent();
+ const _previousOrParentTNode = getPreviousOrParentTNode();
+ setIsParent(true);
+ setPreviousOrParentTNode(null !);
const lView =
createLViewData(renderer, tView, context, LViewFlags.CheckAlways, getCurrentSanitizer());
@@ -577,8 +334,8 @@ export function createEmbeddedViewAndNode(
tView.node !.injectorIndex = injectorIndex;
}
- isParent = _isParent;
- previousOrParentTNode = _previousOrParentTNode;
+ setIsParent(_isParent);
+ setPreviousOrParentTNode(_previousOrParentTNode);
return lView;
}
@@ -594,36 +351,39 @@ export function createEmbeddedViewAndNode(
*/
export function renderEmbeddedTemplate(
viewToRender: LViewData, tView: TView, context: T, rf: RenderFlags) {
- const _isParent = isParent;
- const _previousOrParentTNode = previousOrParentTNode;
+ const _isParent = getIsParent();
+ const _previousOrParentTNode = getPreviousOrParentTNode();
+ setIsParent(true);
+ setPreviousOrParentTNode(null !);
let oldView: LViewData;
if (viewToRender[FLAGS] & LViewFlags.IsRoot) {
// This is a root view inside the view tree
tickRootContext(viewToRender[CONTEXT] as RootContext);
} else {
try {
- isParent = true;
- previousOrParentTNode = null !;
+ setIsParent(true);
+ setPreviousOrParentTNode(null !);
oldView = enterView(viewToRender, viewToRender[HOST_NODE]);
namespaceHTML();
tView.template !(rf, context);
if (rf & RenderFlags.Update) {
- refreshDescendantViews();
+ refreshDescendantViews(viewToRender);
} else {
// This must be set to false immediately after the first creation run because in an
// ngFor loop, all the views will be created together before update mode runs and turns
// off firstTemplatePass. If we don't set it here, instances will perform directive
// matching, etc again and again.
- viewToRender[TVIEW].firstTemplatePass = firstTemplatePass = false;
+ viewToRender[TVIEW].firstTemplatePass = false;
+ setFirstTemplatePass(false);
}
} finally {
// renderEmbeddedTemplate() is called twice, once for creation only and then once for
// update. When for creation only, leaveView() must not trigger view hooks, nor clean flags.
const isCreationOnly = (rf & RenderFlags.Create) === RenderFlags.Create;
leaveView(oldView !, isCreationOnly);
- isParent = _isParent;
- previousOrParentTNode = _previousOrParentTNode;
+ setIsParent(_isParent);
+ setPreviousOrParentTNode(_previousOrParentTNode);
}
}
}
@@ -639,12 +399,12 @@ export function renderEmbeddedTemplate(
* @returns context
*/
export function nextContext(level: number = 1): T {
- contextViewData = walkUpViews(level, contextViewData !);
- return contextViewData[CONTEXT] as T;
+ return nextContextImpl(level);
}
-export function renderComponentOrTemplate(
+function renderComponentOrTemplate(
hostView: LViewData, componentOrContext: T, templateFn?: ComponentTemplate) {
+ const rendererFactory = getRendererFactory();
const oldView = enterView(hostView, hostView[HOST_NODE]);
try {
if (rendererFactory.begin) {
@@ -653,13 +413,13 @@ export function renderComponentOrTemplate(
if (templateFn) {
namespaceHTML();
templateFn(getRenderFlags(hostView), componentOrContext !);
- refreshDescendantViews();
+ refreshDescendantViews(hostView);
} else {
- executeInitAndContentHooks();
+ executeInitAndContentHooks(hostView);
// Element was stored at 0 in data and directive was stored at 0 in directives
// in renderComponent()
- setHostBindings();
+ setHostBindings(getTView(), hostView);
componentRefresh(HEADER_OFFSET, false);
}
} finally {
@@ -734,6 +494,9 @@ export function element(
*/
export function elementContainerStart(
index: number, attrs?: TAttributes | null, localRefs?: string[] | null): void {
+ const viewData = getViewData();
+ const tView = getTView();
+ const renderer = getRenderer();
ngDevMode && assertEqual(
viewData[BINDING_INDEX], tView.bindingStartIndex,
'element containers should be created before any bindings');
@@ -745,21 +508,26 @@ export function elementContainerStart(
const tNode = createNodeAtIndex(index, TNodeType.ElementContainer, native, null, attrs || null);
appendChild(native, tNode, viewData);
- createDirectivesAndLocals(localRefs);
+ createDirectivesAndLocals(tView, viewData, localRefs);
}
/** Mark the end of the . */
export function elementContainerEnd(): void {
- if (isParent) {
- isParent = false;
+ let previousOrParentTNode = getPreviousOrParentTNode();
+ const tView = getTView();
+ if (getIsParent()) {
+ setIsParent(false);
} else {
ngDevMode && assertHasParent();
previousOrParentTNode = previousOrParentTNode.parent !;
+ setPreviousOrParentTNode(previousOrParentTNode);
}
ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.ElementContainer);
- currentQueries &&
- (currentQueries = currentQueries.addNode(previousOrParentTNode as TElementContainerNode));
+ const currentQueries = getCurrentQueries();
+ if (currentQueries) {
+ setCurrentQueries(currentQueries.addNode(previousOrParentTNode as TElementContainerNode));
+ }
queueLifecycleHooks(previousOrParentTNode.flags, tView);
}
@@ -778,6 +546,8 @@ export function elementContainerEnd(): void {
*/
export function elementStart(
index: number, name: string, attrs?: TAttributes | null, localRefs?: string[] | null): void {
+ const viewData = getViewData();
+ const tView = getTView();
ngDevMode && assertEqual(
viewData[BINDING_INDEX], tView.bindingStartIndex,
'elements should be created before any bindings ');
@@ -795,15 +565,15 @@ export function elementStart(
}
appendChild(native, tNode, viewData);
- createDirectivesAndLocals(localRefs);
+ createDirectivesAndLocals(tView, viewData, localRefs);
// any immediate children of a component or template container must be pre-emptively
// monkey-patched with the component view data so that the element can be inspected
// later on using any element discovery utility methods (see `element_discovery.ts`)
- if (elementDepthCount === 0) {
+ if (getElementDepthCount() === 0) {
attachPatchData(native, viewData);
}
- elementDepthCount++;
+ increaseElementDepthCount();
}
/**
@@ -814,7 +584,7 @@ export function elementStart(
*/
export function elementCreate(name: string, overriddenRenderer?: Renderer3): RElement {
let native: RElement;
- const rendererToUse = overriddenRenderer || renderer;
+ const rendererToUse = overriddenRenderer || getRenderer();
if (isProceduralRenderer(rendererToUse)) {
native = rendererToUse.createElement(name, _currentNamespace);
@@ -835,197 +605,36 @@ export function elementCreate(name: string, overriddenRenderer?: Renderer3): REl
* @param localRefExtractor mapping function that extracts local ref value from TNode
*/
function createDirectivesAndLocals(
- localRefs: string[] | null | undefined,
+ tView: TView, viewData: LViewData, localRefs: string[] | null | undefined,
localRefExtractor: LocalRefExtractor = getNativeByTNode) {
- if (!bindingsEnabled) return;
- if (firstTemplatePass) {
+ if (!getBindingsEnabled()) return;
+ const previousOrParentTNode = getPreviousOrParentTNode();
+ if (getFirstTemplatePass()) {
ngDevMode && ngDevMode.firstTemplatePass++;
- cacheMatchingDirectivesForNode(previousOrParentTNode, tView, localRefs || null);
- } else {
- instantiateDirectivesDirectly();
- }
- saveResolvedLocalsInData(localRefExtractor);
-}
-
-/**
- * On first template pass, we match each node against available directive selectors and save
- * the resulting defs in the correct instantiation order for subsequent change detection runs
- * (so dependencies are always created before the directives that inject them).
- */
-function cacheMatchingDirectivesForNode(
- tNode: TNode, tView: TView, localRefs: string[] | null): void {
- // Please make sure to have explicit type for `exportsMap`. Inferred type triggers bug in tsickle.
- const exportsMap: ({[key: string]: number} | null) = localRefs ? {'': -1} : null;
- const matches = tView.currentMatches = findDirectiveMatches(tNode);
- generateExpandoBlock(tNode, matches);
- let totalHostVars = 0;
- if (matches) {
- for (let i = 0; i < matches.length; i += 2) {
- const def = matches[i] as DirectiveDef;
- const valueIndex = i + 1;
- resolveDirective(def, valueIndex, matches);
- totalHostVars += def.hostVars;
- saveNameToExportMap(matches[valueIndex] as number, def, exportsMap);
- }
- }
- if (exportsMap) cacheMatchingLocalNames(tNode, localRefs, exportsMap);
- prefillHostVars(totalHostVars);
-}
-
-/**
- * Generates a new block in TView.expandoInstructions for this node.
- *
- * Each expando block starts with the element index (turned negative so we can distinguish
- * it from the hostVar count) and the directive count. See more in VIEW_DATA.md.
- */
-function generateExpandoBlock(tNode: TNode, matches: CurrentMatchesList | null): void {
- const directiveCount = matches ? matches.length / 2 : 0;
- const elementIndex = -(tNode.index - HEADER_OFFSET);
- if (directiveCount > 0) {
- (tView.expandoInstructions || (tView.expandoInstructions = [
- ])).push(elementIndex, directiveCount);
- }
-}
-
-/**
- * On the first template pass, we need to reserve space for host binding values
- * after directives are matched (so all directives are saved, then bindings).
- * Because we are updating the blueprint, we only need to do this once.
- */
-export function prefillHostVars(totalHostVars: number): void {
- for (let i = 0; i < totalHostVars; i++) {
- viewData.push(NO_CHANGE);
- tView.blueprint.push(NO_CHANGE);
- tView.data.push(null);
- }
-}
-
-/** Matches the current node against all available selectors. */
-function findDirectiveMatches(tNode: TNode): CurrentMatchesList|null {
- const registry = tView.directiveRegistry;
- let matches: any[]|null = null;
- if (registry) {
- for (let i = 0; i < registry.length; i++) {
- const def = registry[i];
- if (isNodeMatchingSelectorList(tNode, def.selectors !)) {
- matches || (matches = []);
- if (def.diPublic) def.diPublic(def);
-
- if ((def as ComponentDef).template) {
- if (tNode.flags & TNodeFlags.isComponent) throwMultipleComponentError(tNode);
- addComponentLogic(def as ComponentDef);
- // The component is always stored first with directives after.
- matches.unshift(def, null);
- } else {
- matches.push(def, null);
- }
- }
- }
- }
- return matches as CurrentMatchesList;
-}
-
-export function resolveDirective(
- def: DirectiveDef, valueIndex: number, matches: CurrentMatchesList): any {
- if (matches[valueIndex] === null) {
- matches[valueIndex] = CIRCULAR;
- const instance = def.factory();
- return directiveCreate(matches[valueIndex] = viewData.length, instance, def);
- } else if (matches[valueIndex] === CIRCULAR) {
- // If we revisit this directive before it's resolved, we know it's circular
- throwCyclicDependencyError(def.type);
- }
- return null;
-}
-
-/** Stores index of component's host element so it will be queued for view refresh during CD. */
-function queueComponentIndexForCheck(): void {
- if (firstTemplatePass) {
- (tView.components || (tView.components = [])).push(previousOrParentTNode.index);
- }
-}
-
-/** Stores index of directive and host element so it will be queued for binding refresh during CD.
- */
-export function queueHostBindingForCheck(
- dirIndex: number, def: DirectiveDef| ComponentDef): void {
- ngDevMode &&
- assertEqual(firstTemplatePass, true, 'Should only be called in first template pass.');
- tView.expandoInstructions !.push(def.hostBindings !, def.hostVars);
-}
-
-/**
- * This function instantiates the given directives.
- */
-function instantiateDirectivesDirectly() {
- ngDevMode && assertEqual(
- firstTemplatePass, false,
- `Directives should only be instantiated directly after first template pass`);
- const count = previousOrParentTNode.flags & TNodeFlags.DirectiveCountMask;
-
- if (isContentQueryHost(previousOrParentTNode) && currentQueries) {
- currentQueries = currentQueries.clone();
- }
-
- if (count > 0) {
- const start = previousOrParentTNode.flags >> TNodeFlags.DirectiveStartingIndexShift;
- const end = start + count;
-
- for (let i = start; i < end; i++) {
- const def = tView.data[i] as DirectiveDef| ComponentDef;
-
- // Component view must be set on node before the factory is created so
- // ChangeDetectorRefs have a way to store component view on creation.
- if ((def as ComponentDef).template) {
- addComponentLogic(def as ComponentDef);
- }
- directiveCreate(i, def.factory(), def);
- }
- }
-}
-
-/** Caches local names and their matching directive indices for query and template lookups. */
-function cacheMatchingLocalNames(
- tNode: TNode, localRefs: string[] | null, exportsMap: {[key: string]: number}): void {
- if (localRefs) {
- const localNames: (string | number)[] = tNode.localNames = [];
-
- // Local names must be stored in tNode in the same order that localRefs are defined
- // in the template to ensure the data is loaded in the same slots as their refs
- // in the template (for template queries).
- for (let i = 0; i < localRefs.length; i += 2) {
- const index = exportsMap[localRefs[i + 1]];
- if (index == null) throw new Error(`Export of name '${localRefs[i + 1]}' not found!`);
- localNames.push(localRefs[i], index);
- }
- }
-}
-
-/**
- * Builds up an export map as directives are created, so local refs can be quickly mapped
- * to their directive instances.
- */
-function saveNameToExportMap(
- index: number, def: DirectiveDef| ComponentDef,
- exportsMap: {[key: string]: number} | null) {
- if (exportsMap) {
- if (def.exportAs) exportsMap[def.exportAs] = index;
- if ((def as ComponentDef).template) exportsMap[''] = index;
+
+ resolveDirectives(
+ tView, viewData, findDirectiveMatches(tView, viewData, previousOrParentTNode),
+ previousOrParentTNode, localRefs || null);
}
+ instantiateAllDirectives(tView, viewData, previousOrParentTNode);
+ saveResolvedLocalsInData(viewData, previousOrParentTNode, localRefExtractor);
}
/**
* Takes a list of local names and indices and pushes the resolved local variable values
* to LViewData in the same order as they are loaded in the template with load().
*/
-function saveResolvedLocalsInData(localRefExtractor: LocalRefExtractor): void {
- const localNames = previousOrParentTNode.localNames;
- const tNode = previousOrParentTNode as TElementNode | TContainerNode | TElementContainerNode;
+function saveResolvedLocalsInData(
+ viewData: LViewData, tNode: TNode, localRefExtractor: LocalRefExtractor): void {
+ const localNames = tNode.localNames;
if (localNames) {
- let localIndex = previousOrParentTNode.index + 1;
+ let localIndex = tNode.index + 1;
for (let i = 0; i < localNames.length; i += 2) {
const index = localNames[i + 1] as number;
- const value = index === -1 ? localRefExtractor(tNode, viewData) : viewData[index];
+ const value = index === -1 ?
+ localRefExtractor(
+ tNode as TElementNode | TContainerNode | TElementContainerNode, viewData) :
+ viewData[index];
viewData[localIndex++] = value;
}
}
@@ -1103,7 +712,6 @@ export function createTView(
components: null,
directiveRegistry: typeof directives === 'function' ? directives() : directives,
pipeRegistry: typeof pipes === 'function' ? pipes() : pipes,
- currentMatches: null,
firstChild: null,
};
}
@@ -1118,6 +726,7 @@ function createViewBlueprint(bindingStartIndex: number, initialViewLength: numbe
}
function setUpAttributes(native: RElement, attrs: TAttributes): void {
+ const renderer = getRenderer();
const isProc = isProceduralRenderer(renderer);
let i = 0;
@@ -1164,7 +773,7 @@ export function createError(text: string, token: any) {
export function locateHostElement(
factory: RendererFactory3, elementOrSelector: RElement | string): RElement|null {
ngDevMode && assertDataInRange(-1);
- rendererFactory = factory;
+ setRendererFactory(factory);
const defaultRenderer = factory.createRenderer(null, null);
const rNode = typeof elementOrSelector === 'string' ?
(isProceduralRenderer(defaultRenderer) ?
@@ -1193,14 +802,16 @@ export function locateHostElement(
*/
export function listener(
eventName: string, listenerFn: (e?: any) => any, useCapture = false): void {
- const tNode = previousOrParentTNode;
+ const viewData = getViewData();
+ const tNode = getPreviousOrParentTNode();
ngDevMode && assertNodeOfPossibleTypes(
tNode, TNodeType.Element, TNodeType.Container, TNodeType.ElementContainer);
// add native event listener - applicable to elements only
if (tNode.type === TNodeType.Element) {
- const native = getNativeByTNode(previousOrParentTNode, viewData) as RElement;
+ const native = getNativeByTNode(tNode, viewData) as RElement;
ngDevMode && ngDevMode.rendererAddEventListener++;
+ const renderer = getRenderer();
// In order to match current behavior, native DOM event listeners must be added for all
// events (including outputs).
@@ -1212,7 +823,7 @@ export function listener(
native.addEventListener(eventName, wrappedListener, useCapture);
const cleanupInstances = getCleanup(viewData);
cleanupInstances.push(wrappedListener);
- if (firstTemplatePass) {
+ if (getFirstTemplatePass()) {
getTViewCleanup(viewData).push(
eventName, tNode.index, cleanupInstances !.length - 1, useCapture);
}
@@ -1229,7 +840,7 @@ export function listener(
const outputs = tNode.outputs;
let outputData: PropertyAliasValue|undefined;
if (outputs && (outputData = outputs[eventName])) {
- createOutput(outputData, listenerFn);
+ createOutput(viewData, outputData, listenerFn);
}
}
@@ -1237,7 +848,7 @@ export function listener(
* Iterates through the outputs associated with a particular event name and subscribes to
* each output.
*/
-function createOutput(outputs: PropertyAliasValue, listener: Function): void {
+function createOutput(viewData: LViewData, outputs: PropertyAliasValue, listener: Function): void {
for (let i = 0; i < outputs.length; i += 2) {
ngDevMode && assertDataInRange(outputs[i] as number, viewData);
const subscription = viewData[outputs[i] as number][outputs[i + 1]].subscribe(listener);
@@ -1254,7 +865,7 @@ function createOutput(outputs: PropertyAliasValue, listener: Function): void {
*/
export function storeCleanupWithContext(
view: LViewData | null, context: any, cleanupFn: Function): void {
- if (!view) view = viewData;
+ if (!view) view = getViewData();
getCleanup(view).push(context);
if (view[TVIEW].firstTemplatePass) {
@@ -1280,18 +891,22 @@ export function storeCleanupFn(view: LViewData, cleanupFn: Function): void {
/** Mark the end of the element. */
export function elementEnd(): void {
- if (isParent) {
- isParent = false;
+ let previousOrParentTNode = getPreviousOrParentTNode();
+ if (getIsParent()) {
+ setIsParent(false);
} else {
ngDevMode && assertHasParent();
previousOrParentTNode = previousOrParentTNode.parent !;
+ setPreviousOrParentTNode(previousOrParentTNode);
}
ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.Element);
- currentQueries &&
- (currentQueries = currentQueries.addNode(previousOrParentTNode as TElementNode));
+ const currentQueries = getCurrentQueries();
+ if (currentQueries) {
+ setCurrentQueries(currentQueries.addNode(previousOrParentTNode as TElementNode));
+ }
- queueLifecycleHooks(previousOrParentTNode.flags, tView);
- elementDepthCount--;
+ queueLifecycleHooks(previousOrParentTNode.flags, getTView());
+ decreaseElementDepthCount();
}
/**
@@ -1306,6 +921,8 @@ export function elementEnd(): void {
export function elementAttribute(
index: number, name: string, value: any, sanitizer?: SanitizerFn): void {
if (value !== NO_CHANGE) {
+ const viewData = getViewData();
+ const renderer = getRenderer();
const element = getNativeByIndex(index, viewData);
if (value == null) {
ngDevMode && ngDevMode.rendererRemoveAttribute++;
@@ -1337,14 +954,16 @@ export function elementAttribute(
export function elementProperty(
index: number, propName: string, value: T | NO_CHANGE, sanitizer?: SanitizerFn): void {
if (value === NO_CHANGE) return;
+ const viewData = getViewData();
const element = getNativeByIndex(index, viewData) as RElement | RComment;
const tNode = getTNode(index, viewData);
const inputData = initializeTNodeInputs(tNode);
let dataValue: PropertyAliasValue|undefined;
if (inputData && (dataValue = inputData[propName])) {
- setInputsForProperty(dataValue, value);
- if (isComponent(tNode)) markDirtyIfOnPush(index + HEADER_OFFSET);
+ setInputsForProperty(viewData, dataValue, value);
+ if (isComponent(tNode)) markDirtyIfOnPush(viewData, index + HEADER_OFFSET);
} else if (tNode.type === TNodeType.Element) {
+ const renderer = getRenderer();
// It is assumed that the sanitizer is only added when the compiler determines that the property
// is risky, so sanitization can be done without further checks.
value = sanitizer != null ? (sanitizer(value) as any) : value;
@@ -1356,48 +975,6 @@ export function elementProperty(
}
}
-/**
- * Enables directive matching on elements.
- *
- * * Example:
- * ```
- *
- * Should match component / directive.
- *
- *
- *
- *
- * Should not match component / directive because we are in ngNonBindable.
- *
- *
- *
- * ```
- */
-export function enableBindings(): void {
- bindingsEnabled = true;
-}
-
-/**
- * Disables directive matching on element.
- *
- * * Example:
- * ```
- *
- * Should match component / directive.
- *
- *
- *
- *
- * Should not match component / directive because we are in ngNonBindable.
- *
- *
- *
- * ```
- */
-export function disableBindings(): void {
- bindingsEnabled = false;
-}
-
/**
* Constructs a TNode object from the arguments.
*
@@ -1409,11 +986,12 @@ export function disableBindings(): void {
* @returns the TNode object
*/
export function createTNode(
- type: TNodeType, adjustedIndex: number, tagName: string | null, attrs: TAttributes | null,
- tViews: TView[] | null): TNode {
+ viewData: LViewData, type: TNodeType, adjustedIndex: number, tagName: string | null,
+ attrs: TAttributes | null, tViews: TView[] | null): TNode {
+ const previousOrParentTNode = getPreviousOrParentTNode();
ngDevMode && ngDevMode.tNode++;
const parent =
- isParent ? previousOrParentTNode : previousOrParentTNode && previousOrParentTNode.parent;
+ getIsParent() ? previousOrParentTNode : previousOrParentTNode && previousOrParentTNode.parent;
// Parents cannot cross component boundaries because components will be used in multiple places,
// so it's only set if the view is the same.
@@ -1425,6 +1003,7 @@ export function createTNode(
index: adjustedIndex,
injectorIndex: tParent ? tParent.injectorIndex : -1,
flags: 0,
+ providerIndexes: 0,
tagName: tagName,
attrs: attrs,
localNames: null,
@@ -1445,7 +1024,7 @@ export function createTNode(
* Given a list of directive indices and minified input names, sets the
* input properties on the corresponding directives.
*/
-function setInputsForProperty(inputs: PropertyAliasValue, value: any): void {
+function setInputsForProperty(viewData: LViewData, inputs: PropertyAliasValue, value: any): void {
for (let i = 0; i < inputs.length; i += 2) {
ngDevMode && assertDataInRange(inputs[i] as number, viewData);
viewData[inputs[i] as number][inputs[i + 1]] = value;
@@ -1461,6 +1040,7 @@ function setInputsForProperty(inputs: PropertyAliasValue, value: any): void {
*/
function generatePropertyAliases(
tNodeFlags: TNodeFlags, direction: BindingDirection): PropertyAliases|null {
+ const tView = getTView();
const count = tNodeFlags & TNodeFlags.DirectiveCountMask;
let propStore: PropertyAliases|null = null;
@@ -1502,7 +1082,7 @@ export function elementClassProp(
index: number, stylingIndex: number, value: boolean | PlayerFactory): void {
const val =
(value instanceof BoundPlayerFactory) ? (value as BoundPlayerFactory) : (!!value);
- updateElementClassProp(getStylingContext(index, viewData), stylingIndex, val);
+ updateElementClassProp(getStylingContext(index, getViewData()), stylingIndex, val);
}
/**
@@ -1537,7 +1117,7 @@ export function elementStyling(
classDeclarations?: (string | boolean | InitialStylingFlags)[] | null,
styleDeclarations?: (string | boolean | InitialStylingFlags)[] | null,
styleSanitizer?: StyleSanitizeFn | null): void {
- const tNode = previousOrParentTNode;
+ const tNode = getPreviousOrParentTNode();
const inputData = initializeTNodeInputs(tNode);
if (!tNode.stylingTemplate) {
@@ -1555,9 +1135,9 @@ export function elementStyling(
classDeclarations && classDeclarations.length) {
const index = tNode.index - HEADER_OFFSET;
if (delegateToClassInput(tNode)) {
- const stylingContext = getStylingContext(index, viewData);
+ const stylingContext = getStylingContext(index, getViewData());
const initialClasses = stylingContext[StylingIndex.PreviousOrCachedMultiClassValue] as string;
- setInputsForProperty(tNode.inputs !['class'] !, initialClasses);
+ setInputsForProperty(getViewData(), tNode.inputs !['class'] !, initialClasses);
}
elementStylingApply(index);
}
@@ -1579,8 +1159,9 @@ export function elementStyling(
* index.)
*/
export function elementStylingApply(index: number): void {
+ const viewData = getViewData();
const totalPlayersQueued =
- renderStyleAndClassBindings(getStylingContext(index, viewData), renderer, viewData);
+ renderStyleAndClassBindings(getStylingContext(index, viewData), getRenderer(), viewData);
if (totalPlayersQueued > 0) {
const rootContext = getRootContext(viewData);
scheduleTick(rootContext, RootContextFlags.FlushPlayers);
@@ -1624,7 +1205,7 @@ export function elementStyleProp(
valueToAdd = value as any as string;
}
}
- updateElementStyleProp(getStylingContext(index, viewData), styleIndex, valueToAdd);
+ updateElementStyleProp(getStylingContext(index, getViewData()), styleIndex, valueToAdd);
}
/**
@@ -1651,13 +1232,14 @@ export function elementStyleProp(
export function elementStylingMap(
index: number, classes: {[key: string]: any} | string | NO_CHANGE | null,
styles?: {[styleName: string]: any} | NO_CHANGE | null): void {
+ const viewData = getViewData();
const tNode = getTNode(index, viewData);
const stylingContext = getStylingContext(index, viewData);
if (delegateToClassInput(tNode) && classes !== NO_CHANGE) {
const initialClasses = stylingContext[StylingIndex.PreviousOrCachedMultiClassValue] as string;
const classInputVal =
(initialClasses.length ? (initialClasses + ' ') : '') + (classes as string);
- setInputsForProperty(tNode.inputs !['class'] !, classInputVal);
+ setInputsForProperty(getViewData(), tNode.inputs !['class'] !, classInputVal);
}
updateStylingMap(stylingContext, classes, styles);
}
@@ -1673,15 +1255,16 @@ export function elementStylingMap(
* @param value Value to write. This value will be stringified.
*/
export function text(index: number, value?: any): void {
+ const viewData = getViewData();
ngDevMode && assertEqual(
- viewData[BINDING_INDEX], tView.bindingStartIndex,
+ viewData[BINDING_INDEX], getTView().bindingStartIndex,
'text nodes should be created before any bindings');
ngDevMode && ngDevMode.rendererCreateTextNode++;
- const textNative = createTextNode(value, renderer);
+ const textNative = createTextNode(value, getRenderer());
const tNode = createNodeAtIndex(index, TNodeType.Element, textNative, null, null);
// Text nodes are self closing.
- isParent = false;
+ setIsParent(false);
appendChild(textNative, tNode, viewData);
}
@@ -1695,9 +1278,10 @@ export function text(index: number, value?: any): void {
export function textBinding(index: number, value: T | NO_CHANGE): void {
if (value !== NO_CHANGE) {
ngDevMode && assertDataInRange(index + HEADER_OFFSET);
- const element = getNativeByIndex(index, viewData) as any as RText;
+ const element = getNativeByIndex(index, getViewData()) as any as RText;
ngDevMode && assertDefined(element, 'native element should exist');
ngDevMode && ngDevMode.rendererSetText++;
+ const renderer = getRenderer();
isProceduralRenderer(renderer) ? renderer.setValue(element, stringify(value)) :
element.textContent = stringify(value);
}
@@ -1708,43 +1292,274 @@ export function textBinding(index: number, value: T | NO_CHANGE): void {
//////////////////////////
/**
- * Create a directive and their associated content queries.
- *
- * NOTE: directives can be created in order other than the index order. They can also
- * be retrieved before they are created in which case the value will be null.
- *
- * @param directive The directive instance.
- * @param directiveDef DirectiveDef object which contains information about the template.
+ * Instantiate a root component.
*/
-export function directiveCreate(
- directiveDefIdx: number, directive: T, directiveDef: DirectiveDef| ComponentDef): T {
- const native = getNativeByTNode(previousOrParentTNode, viewData);
- const instance = baseDirectiveCreate(directiveDefIdx, directive, directiveDef, native);
+export function instantiateRootComponent(
+ tView: TView, viewData: LViewData, def: ComponentDef): T {
+ if (getFirstTemplatePass()) {
+ if (def.providersResolver) def.providersResolver(def);
+ baseResolveDirective(tView, viewData, def, def.factory);
+ }
+ const previousOrParentTNode = getPreviousOrParentTNode();
+ const directive = getNodeInjectable(
+ tView.data, viewData, viewData.length - 1, previousOrParentTNode as TElementNode);
+ postProcessBaseDirective(viewData, previousOrParentTNode, directive, def as DirectiveDef);
+ return directive;
+}
- if ((directiveDef as ComponentDef).template) {
+/**
+ * Resolve the matched directives on a node.
+ */
+function resolveDirectives(
+ tView: TView, viewData: LViewData, directives: DirectiveDef[] | null, tNode: TNode,
+ localRefs: string[] | null): void {
+ // Please make sure to have explicit type for `exportsMap`. Inferred type triggers bug in tsickle.
+ ngDevMode && assertEqual(getFirstTemplatePass(), true, 'should run on first template pass only');
+ const exportsMap: ({[key: string]: number} | null) = localRefs ? {'': -1} : null;
+ generateExpandoInstructionBlock(tView, tNode, directives);
+ let totalHostVars = 0;
+ if (directives) {
+ initNodeFlags(tNode, tView.data.length, directives.length);
+ // When the same token is provided by several directives on the same node, some rules apply in
+ // the viewEngine:
+ // - viewProviders have priority over providers
+ // - the last directive in NgModule.declarations has priority over the previous one
+ // So to match these rules, the order in which providers are added in the arrays is very
+ // important.
+ for (let i = 0; i < directives.length; i++) {
+ const def = directives[i] as DirectiveDef;
+ if (def.providersResolver) def.providersResolver(def);
+ }
+ for (let i = 0; i < directives.length; i++) {
+ const def = directives[i] as DirectiveDef;
+
+ const directiveDefIdx = tView.data.length;
+ baseResolveDirective(tView, viewData, def, def.factory);
+
+ totalHostVars += def.hostVars;
+ saveNameToExportMap(tView.data !.length - 1, def, exportsMap);
+
+ // Init hooks are queued now so ngOnInit is called in host components before
+ // any projected components.
+ queueInitHooks(directiveDefIdx, def.onInit, def.doCheck, tView);
+ }
+ }
+ if (exportsMap) cacheMatchingLocalNames(tNode, localRefs, exportsMap);
+ prefillHostVars(tView, viewData, totalHostVars);
+}
+
+/**
+ * Instantiate all the directives that were previously resolved on the current node.
+ */
+function instantiateAllDirectives(tView: TView, viewData: LViewData, previousOrParentTNode: TNode) {
+ const start = previousOrParentTNode.flags >> TNodeFlags.DirectiveStartingIndexShift;
+ const end = start + previousOrParentTNode.flags & TNodeFlags.DirectiveCountMask;
+ if (!getFirstTemplatePass() && start < end) {
+ getOrCreateNodeInjectorForNode(
+ previousOrParentTNode as TElementNode | TContainerNode | TElementContainerNode, viewData);
+ }
+ for (let i = start; i < end; i++) {
+ const def = tView.data[i] as DirectiveDef;
+ if (isComponentDef(def)) {
+ addComponentLogic(viewData, previousOrParentTNode, def as ComponentDef);
+ }
+ const directive =
+ getNodeInjectable(tView.data, viewData !, i, previousOrParentTNode as TElementNode);
+ postProcessDirective(viewData, directive, def, i);
+ }
+}
+
+/**
+* Generates a new block in TView.expandoInstructions for this node.
+*
+* Each expando block starts with the element index (turned negative so we can distinguish
+* it from the hostVar count) and the directive count. See more in VIEW_DATA.md.
+*/
+function generateExpandoInstructionBlock(
+ tView: TView, tNode: TNode, directives: DirectiveDef[] | null): void {
+ const directiveCount = directives ? directives.length : 0;
+ const elementIndex = -(tNode.index - HEADER_OFFSET);
+ if (directiveCount > 0) {
+ (tView.expandoInstructions || (tView.expandoInstructions = [
+ ])).push(elementIndex, directiveCount);
+ }
+}
+
+/**
+* On the first template pass, we need to reserve space for host binding values
+* after directives are matched (so all directives are saved, then bindings).
+* Because we are updating the blueprint, we only need to do this once.
+*/
+export function prefillHostVars(tView: TView, viewData: LViewData, totalHostVars: number): void {
+ for (let i = 0; i < totalHostVars; i++) {
+ viewData.push(NO_CHANGE);
+ tView.blueprint.push(NO_CHANGE);
+ tView.data.push(null);
+ }
+}
+
+/**
+ * Process a directive on the current node after its creation.
+ */
+function postProcessDirective(
+ viewData: LViewData, directive: T, def: DirectiveDef, directiveDefIdx: number): void {
+ const previousOrParentTNode = getPreviousOrParentTNode();
+ postProcessBaseDirective(viewData, previousOrParentTNode, directive, def);
+ ngDevMode && assertDefined(previousOrParentTNode, 'previousOrParentTNode');
+ if (previousOrParentTNode && previousOrParentTNode.attrs) {
+ setInputsFromAttrs(directiveDefIdx, directive, def.inputs, previousOrParentTNode);
+ }
+
+ if (def.contentQueries) {
+ def.contentQueries(directiveDefIdx);
+ }
+
+ if (isComponentDef(def)) {
const componentView = getComponentViewByIndex(previousOrParentTNode.index, viewData);
componentView[CONTEXT] = directive;
}
-
- if (firstTemplatePass) {
- // Init hooks are queued now so ngOnInit is called in host components before
- // any projected components.
- queueInitHooks(directiveDefIdx, directiveDef.onInit, directiveDef.doCheck, tView);
- }
-
- ngDevMode && assertDefined(previousOrParentTNode, 'previousOrParentTNode');
- if (previousOrParentTNode && previousOrParentTNode.attrs) {
- setInputsFromAttrs(directiveDefIdx, instance, directiveDef.inputs, previousOrParentTNode);
- }
-
- if (directiveDef.contentQueries) {
- directiveDef.contentQueries();
- }
-
- return instance;
}
-function addComponentLogic(def: ComponentDef): void {
+/**
+ * A lighter version of postProcessDirective() that is used for the root component.
+ */
+function postProcessBaseDirective(
+ viewData: LViewData, previousOrParentTNode: TNode, directive: T, def: DirectiveDef): void {
+ const native = getNativeByTNode(previousOrParentTNode, viewData);
+
+ ngDevMode && assertEqual(
+ viewData[BINDING_INDEX], getTView().bindingStartIndex,
+ 'directives should be created before any bindings');
+ ngDevMode && assertPreviousIsParent();
+
+ attachPatchData(directive, viewData);
+ if (native) {
+ attachPatchData(native, viewData);
+ }
+
+ // TODO(misko): setUpAttributes should be a feature for better treeshakability.
+ if (def.attributes != null && previousOrParentTNode.type == TNodeType.Element) {
+ setUpAttributes(native as RElement, def.attributes as string[]);
+ }
+}
+
+
+
+/**
+* Matches the current node against all available selectors.
+* If a component is matched (at most one), it is returned in first position in the array.
+*/
+function findDirectiveMatches(tView: TView, viewData: LViewData, tNode: TNode): DirectiveDef[]|
+ null {
+ ngDevMode && assertEqual(getFirstTemplatePass(), true, 'should run on first template pass only');
+ const registry = tView.directiveRegistry;
+ let matches: any[]|null = null;
+ if (registry) {
+ for (let i = 0; i < registry.length; i++) {
+ const def = registry[i] as ComponentDef| DirectiveDef;
+ if (isNodeMatchingSelectorList(tNode, def.selectors !)) {
+ matches || (matches = []);
+ diPublicInInjector(
+ getOrCreateNodeInjectorForNode(
+ getPreviousOrParentTNode() as TElementNode | TContainerNode | TElementContainerNode,
+ viewData),
+ viewData, def.type);
+
+ if (isComponentDef(def)) {
+ if (tNode.flags & TNodeFlags.isComponent) throwMultipleComponentError(tNode);
+ tNode.flags = TNodeFlags.isComponent;
+
+ // The component is always stored first with directives after.
+ matches.unshift(def);
+ } else {
+ matches.push(def);
+ }
+ }
+ }
+ }
+ return matches;
+}
+
+/** Stores index of component's host element so it will be queued for view refresh during CD. */
+function queueComponentIndexForCheck(previousOrParentTNode: TNode): void {
+ ngDevMode &&
+ assertEqual(getFirstTemplatePass(), true, 'Should only be called in first template pass.');
+ const tView = getTView();
+ (tView.components || (tView.components = [])).push(previousOrParentTNode.index);
+}
+
+/** Stores index of directive and host element so it will be queued for binding refresh during CD.
+*/
+function queueHostBindingForCheck(tView: TView, def: DirectiveDef| ComponentDef): void {
+ ngDevMode &&
+ assertEqual(getFirstTemplatePass(), true, 'Should only be called in first template pass.');
+ tView.expandoInstructions !.push(def.hostBindings !, def.hostVars);
+}
+
+/** Caches local names and their matching directive indices for query and template lookups. */
+function cacheMatchingLocalNames(
+ tNode: TNode, localRefs: string[] | null, exportsMap: {[key: string]: number}): void {
+ if (localRefs) {
+ const localNames: (string | number)[] = tNode.localNames = [];
+
+ // Local names must be stored in tNode in the same order that localRefs are defined
+ // in the template to ensure the data is loaded in the same slots as their refs
+ // in the template (for template queries).
+ for (let i = 0; i < localRefs.length; i += 2) {
+ const index = exportsMap[localRefs[i + 1]];
+ if (index == null) throw new Error(`Export of name '${localRefs[i + 1]}' not found!`);
+ localNames.push(localRefs[i], index);
+ }
+ }
+}
+
+/**
+* Builds up an export map as directives are created, so local refs can be quickly mapped
+* to their directive instances.
+*/
+function saveNameToExportMap(
+ index: number, def: DirectiveDef| ComponentDef,
+ exportsMap: {[key: string]: number} | null) {
+ if (exportsMap) {
+ if (def.exportAs) exportsMap[def.exportAs] = index;
+ if ((def as ComponentDef).template) exportsMap[''] = index;
+ }
+}
+
+/**
+ * Initializes the flags on the current node, setting all indices to the initial index,
+ * the directive count to 0, and adding the isComponent flag.
+ * @param index the initial index
+ */
+export function initNodeFlags(tNode: TNode, index: number, numberOfDirectives: number) {
+ ngDevMode && assertEqual(getFirstTemplatePass(), true, 'expected firstTemplatePass to be true');
+ const flags = tNode.flags;
+ ngDevMode && assertEqual(
+ flags === 0 || flags === TNodeFlags.isComponent, true,
+ 'expected node flags to not be initialized');
+
+ ngDevMode && assertNotEqual(
+ numberOfDirectives, TNodeFlags.DirectiveCountMask,
+ 'Reached the max number of directives');
+ // When the first directive is created on a node, save the index
+ tNode.flags = index << TNodeFlags.DirectiveStartingIndexShift | flags & TNodeFlags.isComponent |
+ numberOfDirectives;
+ tNode.providerIndexes = index;
+}
+
+function baseResolveDirective(
+ tView: TView, viewData: LViewData, def: DirectiveDef,
+ directiveFactory: (t: Type| null) => any) {
+ tView.data.push(def);
+ const nodeInjectorFactory = new NodeInjectorFactory(directiveFactory, isComponentDef(def), null);
+ tView.blueprint.push(nodeInjectorFactory);
+ viewData.push(nodeInjectorFactory);
+
+ if (def.hostBindings) queueHostBindingForCheck(tView, def);
+}
+
+function addComponentLogic(
+ viewData: LViewData, previousOrParentTNode: TNode, def: ComponentDef): void {
const native = getNativeByTNode(previousOrParentTNode, viewData);
const tView = getOrCreateTView(
@@ -1755,7 +1570,7 @@ function addComponentLogic(def: ComponentDef): void {
const componentView = addToViewTree(
viewData, previousOrParentTNode.index as number,
createLViewData(
- rendererFactory.createRenderer(native as RElement, def), tView, null,
+ getRendererFactory().createRenderer(native as RElement, def), tView, null,
def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, getCurrentSanitizer()));
componentView[HOST_NODE] = previousOrParentTNode as TElementNode;
@@ -1765,65 +1580,11 @@ function addComponentLogic(def: ComponentDef): void {
componentView[HOST] = viewData[previousOrParentTNode.index];
viewData[previousOrParentTNode.index] = componentView;
- if (firstTemplatePass) {
- queueComponentIndexForCheck();
- previousOrParentTNode.flags =
- viewData.length << TNodeFlags.DirectiveStartingIndexShift | TNodeFlags.isComponent;
+ if (getFirstTemplatePass()) {
+ queueComponentIndexForCheck(previousOrParentTNode);
}
}
-/**
- * A lighter version of directiveCreate() that is used for the root component
- *
- * This version does not contain features that we don't already support at root in
- * current Angular. Example: local refs and inputs on root component.
- */
-export function baseDirectiveCreate(
- index: number, directive: T, directiveDef: DirectiveDef| ComponentDef,
- native: RNode | null): T {
- ngDevMode && assertEqual(
- viewData[BINDING_INDEX], tView.bindingStartIndex,
- 'directives should be created before any bindings');
- ngDevMode && assertPreviousIsParent();
-
- attachPatchData(directive, viewData);
- if (native) {
- attachPatchData(native, viewData);
- }
-
- viewData[index] = directive;
-
- if (firstTemplatePass) {
- const flags = previousOrParentTNode.flags;
- if (flags === 0) {
- // When the first directive is created:
- // - save the index,
- // - set the number of directives to 1
- previousOrParentTNode.flags =
- index << TNodeFlags.DirectiveStartingIndexShift | flags & TNodeFlags.isComponent | 1;
- } else {
- // Only need to bump the size when subsequent directives are created
- ngDevMode && assertNotEqual(
- flags & TNodeFlags.DirectiveCountMask, TNodeFlags.DirectiveCountMask,
- 'Reached the max number of directives');
- previousOrParentTNode.flags++;
- }
-
- tView.data.push(directiveDef);
- tView.blueprint.push(null);
- if (directiveDef.hostBindings) queueHostBindingForCheck(index, directiveDef);
- } else {
- const diPublic = directiveDef !.diPublic;
- if (diPublic) diPublic(directiveDef !);
- }
-
- if (directiveDef !.attributes != null && previousOrParentTNode.type == TNodeType.Element) {
- setUpAttributes(native as RElement, directiveDef !.attributes as string[]);
- }
-
- return directive;
-}
-
/**
* Sets initial input properties on directive instances from attribute data
*
@@ -1942,19 +1703,24 @@ export function template(
index: number, templateFn: ComponentTemplate| null, consts: number, vars: number,
tagName?: string | null, attrs?: TAttributes | null, localRefs?: string[] | null,
localRefExtractor?: LocalRefExtractor) {
+ const viewData = getViewData();
+ const tView = getTView();
// TODO: consider a separate node type for templates
const tNode = containerInternal(index, tagName || null, attrs || null);
- if (firstTemplatePass) {
+ if (getFirstTemplatePass()) {
tNode.tViews = createTView(
-1, templateFn, consts, vars, tView.directiveRegistry, tView.pipeRegistry, null);
}
- createDirectivesAndLocals(localRefs, localRefExtractor);
- currentQueries &&
- (currentQueries = currentQueries.addNode(previousOrParentTNode as TContainerNode));
+ createDirectivesAndLocals(tView, viewData, localRefs, localRefExtractor);
+ const currentQueries = getCurrentQueries();
+ const previousOrParentTNode = getPreviousOrParentTNode();
+ if (currentQueries) {
+ setCurrentQueries(currentQueries.addNode(previousOrParentTNode as TContainerNode));
+ }
queueLifecycleHooks(tNode.flags, tView);
- isParent = false;
+ setIsParent(false);
}
/**
@@ -1968,18 +1734,19 @@ export function template(
*/
export function container(index: number): void {
const tNode = containerInternal(index, null, null);
- firstTemplatePass && (tNode.tViews = []);
- isParent = false;
+ getFirstTemplatePass() && (tNode.tViews = []);
+ setIsParent(false);
}
function containerInternal(
index: number, tagName: string | null, attrs: TAttributes | null): TNode {
+ const viewData = getViewData();
ngDevMode && assertEqual(
- viewData[BINDING_INDEX], tView.bindingStartIndex,
+ viewData[BINDING_INDEX], getTView().bindingStartIndex,
'container nodes should be created before any bindings');
const adjustedIndex = index + HEADER_OFFSET;
- const comment = renderer.createComment(ngDevMode ? 'container' : '');
+ const comment = getRenderer().createComment(ngDevMode ? 'container' : '');
ngDevMode && ngDevMode.rendererCreateComment++;
const tNode = createNodeAtIndex(index, TNodeType.Container, comment, tagName, attrs);
const lContainer = viewData[adjustedIndex] =
@@ -1991,12 +1758,13 @@ function containerInternal(
// because views can be removed and re-inserted.
addToViewTree(viewData, index + HEADER_OFFSET, lContainer);
+ const currentQueries = getCurrentQueries();
if (currentQueries) {
// prepare place for matching nodes from views inserted into a given container
lContainer[QUERIES] = currentQueries.container();
}
- ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.Container);
+ ngDevMode && assertNodeType(getPreviousOrParentTNode(), TNodeType.Container);
return tNode;
}
@@ -2006,17 +1774,20 @@ function containerInternal(
* @param index The index of the container in the data array
*/
export function containerRefreshStart(index: number): void {
- previousOrParentTNode = loadInternal(index, tView.data) as TNode;
+ const viewData = getViewData();
+ const tView = getTView();
+ let previousOrParentTNode = loadInternal(index, tView.data) as TNode;
+ setPreviousOrParentTNode(previousOrParentTNode);
ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.Container);
- isParent = true;
+ setIsParent(true);
viewData[index + HEADER_OFFSET][ACTIVE_INDEX] = 0;
- if (!checkNoChangesMode) {
+ if (!getCheckNoChangesMode()) {
// We need to execute init hooks here so ngOnInit hooks are called in top level views
// before they are called in embedded views (for backwards compatibility).
- executeInitHooks(viewData, tView, creationMode);
+ executeInitHooks(viewData, tView, getCreationMode());
}
}
@@ -2026,17 +1797,19 @@ export function containerRefreshStart(index: number): void {
* Marking the end of LContainer is the time when to child views get inserted or removed.
*/
export function containerRefreshEnd(): void {
- if (isParent) {
- isParent = false;
+ let previousOrParentTNode = getPreviousOrParentTNode();
+ if (getIsParent()) {
+ setIsParent(false);
} else {
ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.View);
ngDevMode && assertHasParent();
previousOrParentTNode = previousOrParentTNode.parent !;
+ setPreviousOrParentTNode(previousOrParentTNode);
}
ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.Container);
- const lContainer = viewData[previousOrParentTNode.index];
+ const lContainer = getViewData()[previousOrParentTNode.index];
const nextIndex = lContainer[ACTIVE_INDEX];
// remove extra views at the end of the container
@@ -2093,7 +1866,7 @@ function scanForView(
} else {
// found a view with id greater than the one we are searching for
// which means that required view doesn't exist and can't be found at
- // later positions in the views array - stop the search here
+ // later positions in the views array - stop the searchdef.cont here
break;
}
}
@@ -2107,24 +1880,25 @@ function scanForView(
* @return boolean Whether or not this view is in creation mode
*/
export function embeddedViewStart(viewBlockId: number, consts: number, vars: number): RenderFlags {
+ const viewData = getViewData();
+ const previousOrParentTNode = getPreviousOrParentTNode();
// The previous node can be a view node if we are processing an inline for loop
const containerTNode = previousOrParentTNode.type === TNodeType.View ?
previousOrParentTNode.parent ! :
previousOrParentTNode;
const lContainer = viewData[containerTNode.index] as LContainer;
- const currentView = viewData;
ngDevMode && assertNodeType(containerTNode, TNodeType.Container);
let viewToRender = scanForView(
lContainer, containerTNode as TContainerNode, lContainer[ACTIVE_INDEX] !, viewBlockId);
if (viewToRender) {
- isParent = true;
+ setIsParent(true);
enterView(viewToRender, viewToRender[TVIEW].node);
} else {
// When we create a new LView, we always reset the state of the instructions.
viewToRender = createLViewData(
- renderer,
+ getRenderer(),
getOrCreateEmbeddedTView(viewBlockId, consts, vars, containerTNode as TContainerNode), null,
LViewFlags.CheckAlways, getCurrentSanitizer());
@@ -2136,9 +1910,9 @@ export function embeddedViewStart(viewBlockId: number, consts: number, vars: num
enterView(viewToRender, viewToRender[TVIEW].node);
}
if (lContainer) {
- if (creationMode) {
+ if (getCreationMode()) {
// it is a new view, insert it into collection of views for a given container
- insertView(viewToRender, lContainer, currentView, lContainer[ACTIVE_INDEX] !, -1);
+ insertView(viewToRender, lContainer, viewData, lContainer[ACTIVE_INDEX] !, -1);
}
lContainer[ACTIVE_INDEX] !++;
}
@@ -2160,6 +1934,7 @@ export function embeddedViewStart(viewBlockId: number, consts: number, vars: num
*/
function getOrCreateEmbeddedTView(
viewIndex: number, consts: number, vars: number, parent: TContainerNode): TView {
+ const tView = getTView();
ngDevMode && assertNodeType(parent, TNodeType.Container);
const containerTViews = parent.tViews as TView[];
ngDevMode && assertDefined(containerTViews, 'TView expected');
@@ -2173,11 +1948,12 @@ function getOrCreateEmbeddedTView(
/** Marks the end of an embedded view. */
export function embeddedViewEnd(): void {
+ const viewData = getViewData();
const viewHost = viewData[HOST_NODE];
- refreshDescendantViews();
+ refreshDescendantViews(viewData);
leaveView(viewData[PARENT] !);
- previousOrParentTNode = viewHost !;
- isParent = false;
+ setPreviousOrParentTNode(viewHost !);
+ setIsParent(false);
}
/////////////
@@ -2190,8 +1966,8 @@ export function embeddedViewEnd(): void {
export function componentRefresh(
adjustedElementIndex: number, parentFirstTemplatePass: boolean): void {
ngDevMode && assertDataInRange(adjustedElementIndex);
- const hostView = getComponentViewByIndex(adjustedElementIndex, viewData);
- ngDevMode && assertNodeType(tView.data[adjustedElementIndex] as TNode, TNodeType.Element);
+ const hostView = getComponentViewByIndex(adjustedElementIndex, getViewData());
+ ngDevMode && assertNodeType(getTView().data[adjustedElementIndex] as TNode, TNodeType.Element);
// Only attached CheckAlways components or attached, dirty OnPush components should be checked
if (viewAttached(hostView) && hostView[FLAGS] & (LViewFlags.CheckAlways | LViewFlags.Dirty)) {
@@ -2260,7 +2036,7 @@ export function viewAttached(view: LViewData): boolean {
* @param rawSelectors A collection of CSS selectors in the raw, un-parsed form
*/
export function projectionDef(selectors?: CssSelectorList[], textSelectors?: string[]): void {
- const componentNode = findComponentView(viewData)[HOST_NODE] as TElementNode;
+ const componentNode = findComponentView(getViewData())[HOST_NODE] as TElementNode;
if (!componentNode.projection) {
const noOfNodeBuckets = selectors ? selectors.length + 1 : 1;
@@ -2307,6 +2083,7 @@ const projectionNodeStack: (LViewData | TNode)[] = [];
* - 1 based index of the selector from the {@link projectionDef}
*/
export function projection(nodeIndex: number, selectorIndex: number = 0, attrs?: string[]): void {
+ const viewData = getViewData();
const tProjectionNode =
createNodeAtIndex(nodeIndex, TNodeType.Projection, null, null, attrs || null);
@@ -2314,7 +2091,7 @@ export function projection(nodeIndex: number, selectorIndex: number = 0, attrs?:
if (tProjectionNode.projection === null) tProjectionNode.projection = selectorIndex;
// `` has no content
- isParent = false;
+ setIsParent(false);
// re-distribution of projectable nodes is stored on a component's view level
const componentView = findComponentView(viewData);
@@ -2369,6 +2146,8 @@ export function projection(nodeIndex: number, selectorIndex: number = 0, attrs?:
*/
export function addToViewTree(
currentView: LViewData, adjustedHostIndex: number, state: T): T {
+ const tView = getTView();
+ const firstTemplatePass = getFirstTemplatePass();
if (currentView[TAIL]) {
currentView[TAIL] ![NEXT] = state;
} else if (firstTemplatePass) {
@@ -2383,7 +2162,7 @@ export function addToViewTree(
///////////////////////////////
/** If node is an OnPush component, marks its LViewData dirty. */
-export function markDirtyIfOnPush(viewIndex: number): void {
+function markDirtyIfOnPush(viewData: LViewData, viewIndex: number): void {
const view = getComponentViewByIndex(viewIndex, viewData);
if (!(view[FLAGS] & LViewFlags.CheckAlways)) {
view[FLAGS] |= LViewFlags.Dirty;
@@ -2391,7 +2170,7 @@ export function markDirtyIfOnPush(viewIndex: number): void {
}
/** Wraps an event listener with preventDefault behavior. */
-export function wrapListenerWithPreventDefault(listenerFn: (e?: any) => any): EventListener {
+function wrapListenerWithPreventDefault(listenerFn: (e?: any) => any): EventListener {
return function wrapListenerIn_preventDefault(e: Event) {
if (listenerFn(e) === false) {
e.preventDefault();
@@ -2513,11 +2292,11 @@ export function detectChangesInRootView(lViewData: LViewData): void {
* introduce other changes.
*/
export function checkNoChanges(component: T): void {
- checkNoChangesMode = true;
+ setCheckNoChangesMode(true);
try {
detectChanges(component);
} finally {
- checkNoChangesMode = false;
+ setCheckNoChangesMode(false);
}
}
@@ -2531,11 +2310,11 @@ export function checkNoChanges(component: T): void {
* @param lViewData The view which the change detection should be checked on.
*/
export function checkNoChangesInRootView(lViewData: LViewData): void {
- checkNoChangesMode = true;
+ setCheckNoChangesMode(true);
try {
detectChangesInRootView(lViewData);
} finally {
- checkNoChangesMode = false;
+ setCheckNoChangesMode(false);
}
}
@@ -2550,7 +2329,7 @@ export function detectChangesInternal(hostView: LViewData, component: T) {
namespaceHTML();
createViewQuery(viewQuery, hostView[FLAGS], component);
templateFn(getRenderFlags(hostView), component);
- refreshDescendantViews();
+ refreshDescendantViews(hostView);
updateViewQuery(viewQuery, component);
} finally {
leaveView(oldView);
@@ -2600,7 +2379,7 @@ export function markDirty(component: T) {
* @param value Value to diff
*/
export function bind(value: T): T|NO_CHANGE {
- return bindingUpdated(viewData[BINDING_INDEX]++, value) ? value : NO_CHANGE;
+ return bindingUpdated(getViewData()[BINDING_INDEX]++, value) ? value : NO_CHANGE;
}
/**
@@ -2622,7 +2401,7 @@ export function interpolationV(values: any[]): string|NO_CHANGE {
for (let i = 1; i < values.length; i += 2) {
// Check if bindings (odd indexes) have changed
- bindingUpdated(viewData[BINDING_INDEX]++, values[i]) && (different = true);
+ bindingUpdated(getViewData()[BINDING_INDEX]++, values[i]) && (different = true);
}
if (!different) {
@@ -2646,13 +2425,14 @@ export function interpolationV(values: any[]): string|NO_CHANGE {
* @param suffix static value used for concatenation only.
*/
export function interpolation1(prefix: string, v0: any, suffix: string): string|NO_CHANGE {
- const different = bindingUpdated(viewData[BINDING_INDEX]++, v0);
+ const different = bindingUpdated(getViewData()[BINDING_INDEX]++, v0);
return different ? prefix + stringify(v0) + suffix : NO_CHANGE;
}
/** Creates an interpolation binding with 2 expressions. */
export function interpolation2(
prefix: string, v0: any, i0: string, v1: any, suffix: string): string|NO_CHANGE {
+ const viewData = getViewData();
const different = bindingUpdated2(viewData[BINDING_INDEX], v0, v1);
viewData[BINDING_INDEX] += 2;
@@ -2663,6 +2443,7 @@ export function interpolation2(
export function interpolation3(
prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, suffix: string): string|
NO_CHANGE {
+ const viewData = getViewData();
const different = bindingUpdated3(viewData[BINDING_INDEX], v0, v1, v2);
viewData[BINDING_INDEX] += 3;
@@ -2674,6 +2455,7 @@ export function interpolation3(
export function interpolation4(
prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any,
suffix: string): string|NO_CHANGE {
+ const viewData = getViewData();
const different = bindingUpdated4(viewData[BINDING_INDEX], v0, v1, v2, v3);
viewData[BINDING_INDEX] += 4;
@@ -2687,6 +2469,7 @@ export function interpolation4(
export function interpolation5(
prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any,
i3: string, v4: any, suffix: string): string|NO_CHANGE {
+ const viewData = getViewData();
let different = bindingUpdated4(viewData[BINDING_INDEX], v0, v1, v2, v3);
different = bindingUpdated(viewData[BINDING_INDEX] + 4, v4) || different;
viewData[BINDING_INDEX] += 5;
@@ -2701,6 +2484,7 @@ export function interpolation5(
export function interpolation6(
prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any,
i3: string, v4: any, i4: string, v5: any, suffix: string): string|NO_CHANGE {
+ const viewData = getViewData();
let different = bindingUpdated4(viewData[BINDING_INDEX], v0, v1, v2, v3);
different = bindingUpdated2(viewData[BINDING_INDEX] + 4, v4, v5) || different;
viewData[BINDING_INDEX] += 6;
@@ -2716,6 +2500,7 @@ export function interpolation7(
prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any,
i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, suffix: string): string|
NO_CHANGE {
+ const viewData = getViewData();
let different = bindingUpdated4(viewData[BINDING_INDEX], v0, v1, v2, v3);
different = bindingUpdated3(viewData[BINDING_INDEX] + 4, v4, v5, v6) || different;
viewData[BINDING_INDEX] += 7;
@@ -2731,6 +2516,7 @@ export function interpolation8(
prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any,
i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, v7: any,
suffix: string): string|NO_CHANGE {
+ const viewData = getViewData();
let different = bindingUpdated4(viewData[BINDING_INDEX], v0, v1, v2, v3);
different = bindingUpdated4(viewData[BINDING_INDEX] + 4, v4, v5, v6, v7) || different;
viewData[BINDING_INDEX] += 8;
@@ -2743,13 +2529,14 @@ export function interpolation8(
/** Store a value in the `data` at a given `index`. */
export function store(index: number, value: T): void {
+ const tView = getTView();
// We don't store any static data for local variables, so the first time
// we see the template, we should store as null to avoid a sparse array
const adjustedIndex = index + HEADER_OFFSET;
if (adjustedIndex >= tView.data.length) {
tView.data[adjustedIndex] = null;
}
- viewData[adjustedIndex] = value;
+ getViewData()[adjustedIndex] = value;
}
/**
@@ -2761,21 +2548,12 @@ export function store(index: number, value: T): void {
* @param index The index of the local ref in contextViewData.
*/
export function reference(index: number) {
+ const contextViewData = getContextViewData();
return loadInternal(index, contextViewData);
}
-function walkUpViews(nestingLevel: number, currentView: LViewData): LViewData {
- while (nestingLevel > 0) {
- ngDevMode && assertDefined(
- currentView[DECLARATION_VIEW],
- 'Declaration view should be defined if nesting level is greater than 0.');
- currentView = currentView[DECLARATION_VIEW] !;
- nestingLevel--;
- }
- return currentView;
-}
-
export function loadQueryList(queryListIdx: number): QueryList {
+ const viewData = getViewData();
ngDevMode && assertDefined(
viewData[CONTENT_QUERIES],
'Content QueryList array should be defined if reading a query.');
@@ -2786,11 +2564,12 @@ export function loadQueryList(queryListIdx: number): QueryList {
/** Retrieves a value from current `viewData`. */
export function load(index: number): T {
- return loadInternal(index, viewData);
+ return loadInternal(index, getViewData());
}
/** Gets the current binding value. */
export function getBinding(bindingIndex: number): any {
+ const viewData = getViewData();
ngDevMode && assertDataInRange(viewData[bindingIndex]);
ngDevMode &&
assertNotEqual(viewData[bindingIndex], NO_CHANGE, 'Stored value should never be NO_CHANGE.');
@@ -2799,6 +2578,8 @@ export function getBinding(bindingIndex: number): any {
/** Updates binding if changed, then returns whether it was updated. */
export function bindingUpdated(bindingIndex: number, value: any): boolean {
+ const viewData = getViewData();
+ const checkNoChangesMode = getCheckNoChangesMode();
ngDevMode && assertNotEqual(value, NO_CHANGE, 'Incoming value should never be NO_CHANGE.');
ngDevMode && assertLessThan(
bindingIndex, viewData.length, `Slot should have been initialized to NO_CHANGE`);
@@ -2806,7 +2587,7 @@ export function bindingUpdated(bindingIndex: number, value: any): boolean {
if (viewData[bindingIndex] === NO_CHANGE) {
viewData[bindingIndex] = value;
} else if (isDifferent(viewData[bindingIndex], value, checkNoChangesMode)) {
- throwErrorIfNoChangesMode(creationMode, checkNoChangesMode, viewData[bindingIndex], value);
+ throwErrorIfNoChangesMode(getCreationMode(), checkNoChangesMode, viewData[bindingIndex], value);
viewData[bindingIndex] = value;
} else {
return false;
@@ -2816,7 +2597,7 @@ export function bindingUpdated(bindingIndex: number, value: any): boolean {
/** Updates binding and returns the value. */
export function updateBinding(bindingIndex: number, value: any): any {
- return viewData[bindingIndex] = value;
+ return getViewData()[bindingIndex] = value;
}
/** Updates 2 bindings if changed, then returns whether either was updated. */
@@ -2838,19 +2619,59 @@ export function bindingUpdated4(
return bindingUpdated2(bindingIndex + 2, exp3, exp4) || different;
}
-export function getTView(): TView {
- return tView;
+
+///////////////////////////////
+//// DI
+///////////////////////////////
+
+/**
+ * Returns the value associated to the given token from the injectors.
+ *
+ * `directiveInject` is intended to be used for directive, component and pipe factories.
+ * All other injection use `inject` which does not walk the node injector tree.
+ *
+ * Usage example (in factory function):
+ *
+ * class SomeDirective {
+ * constructor(directive: DirectiveA) {}
+ *
+ * static ngDirectiveDef = defineDirective({
+ * type: SomeDirective,
+ * factory: () => new SomeDirective(directiveInject(DirectiveA))
+ * });
+ * }
+ *
+ * @param token the type or token to inject
+ * @param flags Injection flags
+ * @returns the value from the injector or `null` when not found
+ */
+export function directiveInject(token: Type| InjectionToken): T;
+export function directiveInject(token: Type| InjectionToken, flags: InjectFlags): T;
+export function directiveInject(
+ token: Type| InjectionToken, flags = InjectFlags.Default): T|null {
+ return getOrCreateInjectable(
+ getPreviousOrParentTNode() as TElementNode | TContainerNode | TElementContainerNode,
+ getViewData(), token, flags);
+}
+
+/**
+ * Facade for the attribute injection from DI.
+ */
+export function injectAttribute(attrNameToInject: string): string|undefined {
+ return injectAttributeImpl(getPreviousOrParentTNode(), attrNameToInject);
}
/**
* Registers a QueryList, associated with a content query, for later refresh (part of a view
* refresh).
*/
-export function registerContentQuery(queryList: QueryList): void {
+export function registerContentQuery(
+ queryList: QueryList, currentDirectiveIndex: number): void {
+ const viewData = getViewData();
+ const tView = getTView();
const savedContentQueriesLength =
(viewData[CONTENT_QUERIES] || (viewData[CONTENT_QUERIES] = [])).push(queryList);
- if (firstTemplatePass) {
- const currentDirectiveIndex = viewData.length - 1;
+ if (getFirstTemplatePass()) {
const tViewContentQueries = tView.contentQueries || (tView.contentQueries = []);
const lastSavedDirectiveIndex =
tView.contentQueries.length ? tView.contentQueries[tView.contentQueries.length - 2] : -1;
@@ -2860,25 +2681,6 @@ export function registerContentQuery(queryList: QueryList): void {
}
}
-export function assertPreviousIsParent() {
- assertEqual(isParent, true, 'previousOrParentTNode should be a parent');
-}
-
-function assertHasParent() {
- assertDefined(previousOrParentTNode.parent, 'previousOrParentTNode should have a parent');
-}
-
-function assertDataInRange(index: number, arr?: any[]) {
- if (arr == null) arr = viewData;
- assertDataInRangeInternal(index, arr || viewData);
-}
-
-function assertDataNext(index: number, arr?: any[]) {
- if (arr == null) arr = viewData;
- assertEqual(
- arr.length, index, `index ${index} expected to be at the end of arr (length ${arr.length})`);
-}
-
export const CLEAN_PROMISE = _CLEAN_PROMISE;
function initializeTNodeInputs(tNode: TNode | null) {
diff --git a/packages/core/src/render3/interfaces/definition.ts b/packages/core/src/render3/interfaces/definition.ts
index b94b4af1ca..c49550e4ed 100644
--- a/packages/core/src/render3/interfaces/definition.ts
+++ b/packages/core/src/render3/interfaces/definition.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Provider, ViewEncapsulation} from '../../core';
+import {ViewEncapsulation} from '../../core';
import {Type} from '../../type';
import {CssSelectorList} from './projection';
@@ -112,11 +112,11 @@ export interface DirectiveDef extends BaseDef {
/** Token representing the directive. Used by DI. */
type: Type;
- /** Function that makes a directive public to the DI system. */
- diPublic: ((def: DirectiveDef) => void)|null;
+ /** Function that resolves providers and publishes them into the DI system. */
+ providersResolver: ((def: DirectiveDef) => void)|null;
/** The selectors that will be used to match nodes to this directive. */
- selectors: CssSelectorList;
+ readonly selectors: CssSelectorList;
/**
* Name under which the directive is exported (for use with local references in template)
@@ -126,12 +126,12 @@ export interface DirectiveDef extends BaseDef {
/**
* Factory function used to create a new directive instance.
*/
- factory(): T;
+ factory: (t: Type|null) => T;
/**
* Function to create instances of content queries associated with a given directive.
*/
- contentQueries: (() => void)|null;
+ contentQueries: ((directiveIndex: number) => void)|null;
/** Refreshes content queries associated with directives in a given view */
contentQueriesRefresh: ((directiveIndex: number, queryIndex: number) => void)|null;
@@ -142,7 +142,7 @@ export interface DirectiveDef