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 extends BaseDef { * Used to calculate the length of the LViewData array for the *parent* component * of this directive/component. */ - hostVars: number; + readonly hostVars: number; /** Refreshes host bindings on the associated directive. */ hostBindings: HostBindingsFunction|null; @@ -153,7 +153,7 @@ export interface DirectiveDef extends BaseDef { * Even indices: attribute name * Odd indices: attribute value */ - attributes: string[]|null; + readonly attributes: string[]|null; /* The following are lifecycle hooks for this component */ onInit: (() => void)|null; @@ -167,7 +167,7 @@ export interface DirectiveDef extends BaseDef { /** * The features applied to this directive */ - features: DirectiveDefFeature[]|null; + readonly features: DirectiveDefFeature[]|null; } export type ComponentDefWithMeta< @@ -245,18 +245,7 @@ export interface ComponentDef extends DirectiveDef { readonly onPush: boolean; /** - * Defines the set of injectable providers that are visible to a Directive and its content DOM - * children. - */ - readonly providers: Provider[]|null; - /** - * Defines the set of injectable providers that are visible to a Directive and its view DOM - * children only. - */ - readonly viewProviders: Provider[]|null; - - /** * Registry of directives and components that may be found in this view. * * The property is either an array of `DirectiveDef`s or a function which returns the array of @@ -297,12 +286,12 @@ export interface PipeDef { * * Used to resolve pipe in templates. */ - name: string; + readonly name: string; /** * Factory function used to create a new pipe instance. */ - factory: () => T; + factory: (t: Type|null) => T; /** * Whether or not the pipe is pure. @@ -310,7 +299,7 @@ export interface PipeDef { * Pure pipes result only depends on the pipe input and not on internal * state of the pipe. */ - pure: boolean; + readonly pure: boolean; /* The following are lifecycle hooks for this pipe */ onDestroy: (() => void)|null; diff --git a/packages/core/src/render3/interfaces/injector.ts b/packages/core/src/render3/interfaces/injector.ts index 4acf62ce36..e25b1728b8 100644 --- a/packages/core/src/render3/interfaces/injector.ts +++ b/packages/core/src/render3/interfaces/injector.ts @@ -6,18 +6,32 @@ * found in the LICENSE file at https://angular.io/license */ - -import {TContainerNode, TElementContainerNode, TElementNode,} from './node'; +import {InjectionToken} from '../../di/injection_token'; +import {InjectFlags} from '../../di/injector'; +import {Type} from '../../type'; +import {TElementNode} from './node'; +import {LViewData, TData} from './view'; export const TNODE = 8; export const PARENT_INJECTOR = 8; export const INJECTOR_SIZE = 9; -export const enum InjectorLocationFlags { +/** + * Represents a relative location of parent injector. + * + * The interfaces encodes number of parents `LViewData`s to traverse and index in the `LViewData` + * pointing to the parent injector. + */ +export interface RelativeInjectorLocation { __brand__: 'RelativeInjectorLocationFlags'; } + +export const enum RelativeInjectorLocationFlags { InjectorIndexMask = 0b111111111111111, - ViewOffsetShift = 15 + ViewOffsetShift = 15, + NO_PARENT = -1, } +export const NO_PARENT_INJECTOR: RelativeInjectorLocation = -1 as any; + /** * Each injector is saved in 9 contiguous slots in `LViewData` and 9 contiguous slots in * `TView.data`. This allows us to store information about the current node's tokens (which @@ -98,6 +112,140 @@ export const enum InjectorLocationFlags { * } */ +/** +* Factory for creating instances of injectors in the NodeInjector. +* +* This factory is complicated by the fact that it can resolve `multi` factories as well. +* +* NOTE: Some of the fields are optional which means that this class has two hidden classes. +* - One without `multi` support (most common) +* - One with `multi` values, (rare). +* +* Since VMs can cache up to 4 inline hidden classes this is OK. +* +* - Single factory: Only `resolving` and `factory` is defined. +* - `providers` factory: `componentProviders` is a number and `index = -1`. +* - `viewProviders` factory: `componentProviders` is a number and `index` points to `providers`. +*/ +export class NodeInjectorFactory { + /** + * The inject implementation to be activated when using the factory. + */ + injectImpl: null|((token: Type|InjectionToken, flags: InjectFlags) => T); + + /** + * Marker set to true during factory invocation to see if we get into recursive loop. + * Recursive loop causes an error to be displayed. + */ + resolving = false; + + /** + * Marks that the token can see other Tokens declared in `viewProviders` on the same node. + */ + canSeeViewProviders: boolean; + + /** + * An array of factories to use in case of `multi` provider. + */ + multi?: Array<() => any>; + + /** + * Number of `multi`-providers which belong to the component. + * + * This is needed because when multiple components and directives declare the `multi` provider + * they have to be concatenated in the correct order. + * + * Example: + * + * If we have a component and directive active an a single element as declared here + * ``` + * component: + * provides: [ {provide: String, useValue: 'component', multi: true} ], + * viewProvides: [ {provide: String, useValue: 'componentView', multi: true} ], + * + * directive: + * provides: [ {provide: String, useValue: 'directive', multi: true} ], + * ``` + * + * Then the expected results are: + * + * ``` + * providers: ['component', 'directive'] + * viewProviders: ['component', 'componentView', 'directive'] + * ``` + * + * The way to think about it is that the `viewProviders` have been inserted after the component + * but before the directives, which is why we need to know how many `multi`s have been declared by + * the component. + */ + componentProviders?: number; + + /** + * Current index of the Factory in the `data`. Needed for `viewProviders` and `providers` merging. + * See `providerFactory`. + */ + index?: number; + + /** + * Because the same `multi` provider can be declared in `provides` and `viewProvides` it is + * possible for `viewProvides` to shadow the `provides`. For this reason we store the + * `provideFactory` of the `providers` so that `providers` can be extended with `viewProviders`. + * + * Example: + * + * Given: + * ``` + * provides: [ {provide: String, useValue: 'all', multi: true} ], + * viewProvides: [ {provide: String, useValue: 'viewOnly', multi: true} ], + * ``` + * + * We have to return `['all']` in case of content injection, but `['all', 'viewOnly']` in case + * of view injection. We further have to make sure that the shared instances (in our case + * `all`) are the exact same instance in both the content as well as the view injection. (We + * have to make sure that we don't double instantiate.) For this reason the `viewProvides` + * `Factory` has a pointer to the shadowed `provides` factory so that it can instantiate the + * `providers` (`['all']`) and then extend it with `viewProviders` (`['all'] + ['viewOnly'] = + * ['all', 'viewOnly']`). + */ + providerFactory?: NodeInjectorFactory|null; + + + constructor( + /** + * Factory to invoke in order to create a new instance. + */ + public factory: + (this: NodeInjectorFactory, _: null, + /** + * array where injectables tokens are stored. This is used in + * case of an error reporting to produce friendlier errors. + */ + tData: TData, + /** + * array where existing instances of injectables are stored. This is used in case + * of multi shadow is needed. See `multi` field documentation. + */ + lData: LViewData, + /** + * The TNode of the same element injector. + */ + tNode: TElementNode) => any, + /** + * Set to `true` if the token is declared in `viewProviders` (or if it is component). + */ + isViewProvider: boolean, + injectImplementation: null|((token: Type|InjectionToken, flags: InjectFlags) => T)) { + this.canSeeViewProviders = isViewProvider; + this.injectImpl = injectImplementation; + } +} + +const FactoryPrototype = NodeInjectorFactory.prototype; +export function isFactory(obj: any): obj is NodeInjectorFactory { + // See: https://jsperf.com/instanceof-vs-getprototypeof + return obj != null && typeof obj == 'object' && Object.getPrototypeOf(obj) == FactoryPrototype; +} + // Note: This hack is necessary so we don't erroneously get a circular dependency // failure based on types. export const unusedValueExportToPlacateAjd = 1; diff --git a/packages/core/src/render3/interfaces/node.ts b/packages/core/src/render3/interfaces/node.ts index ee665b9bca..c190e6cb54 100644 --- a/packages/core/src/render3/interfaces/node.ts +++ b/packages/core/src/render3/interfaces/node.ts @@ -46,6 +46,18 @@ export const enum TNodeFlags { DirectiveStartingIndexShift = 16, } +/** + * Corresponds to the TNode.providerIndexes property. + */ +export const enum TNodeProviderIndexes { + /** The index of the first provider on this node is encoded on the least significant bits */ + ProvidersStartIndexMask = 0b00000000000000001111111111111111, + + /** The count of view providers from the component on this node is encoded on the 16 most + significant bits */ + CptViewProvidersCountShift = 16, + CptViewProvidersCountShifter = 0b00000000000000010000000000000000, +} /** * A set of marker values to be used in the attributes arrays. Those markers indicate that some * items are not regular attributes and the processing should be adapted accordingly. @@ -125,6 +137,14 @@ export interface TNode { */ flags: TNodeFlags; + /** + * This number stores two values using its bits: + * + * - the index of the first provider on that node (first 16 bits) + * - the count of view providers from the component on this node (last 16 bits) + */ + providerIndexes: TNodeProviderIndexes; + /** The tag name associated with this node. */ tagName: string|null; diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index 13af334301..61a4c00e8d 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -6,9 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ +import {InjectionToken} from '../../di/injection_token'; import {Injector} from '../../di/injector'; import {QueryList} from '../../linker'; import {Sanitizer} from '../../sanitization/security'; +import {Type} from '../../type'; import {LContainer} from './container'; import {ComponentDef, ComponentQuery, ComponentTemplate, DirectiveDef, DirectiveDefList, HostBindingsFunction, PipeDef, PipeDefList} from './definition'; @@ -335,24 +337,6 @@ export interface TView { */ firstChild: TNode|null; - /** - * Selector matches for a node are temporarily cached on the TView so the - * DI system can eagerly instantiate directives on the same node if they are - * created out of order. They are overwritten after each node. - * - *
- * - * e.g. DirA injects DirB, but DirA is created first. DI should instantiate - * DirB when it finds that it's on the same node, but not yet created. - * - * Even indices: Directive defs - * Odd indices: - * - Null if the associated directive hasn't been instantiated yet - * - Directive index, if associated directive has been created - * - String, temporary 'CIRCULAR' token set while dependencies are being resolved - */ - currentMatches: CurrentMatchesList|null; - /** * Set of instructions used to process host bindings efficiently. * @@ -549,10 +533,9 @@ export type HookData = (number | (() => void))[]; * * Injector bloom filters are also stored here. */ -export type TData = (TNode | PipeDef| DirectiveDef| ComponentDef| number | null)[]; - -/** Type for TView.currentMatches */ -export type CurrentMatchesList = [DirectiveDef, (string | number | null)]; +export type TData = + (TNode | PipeDef| DirectiveDef| ComponentDef| number | Type| + InjectionToken| null)[]; // Note: This hack is necessary so we don't erroneously get a circular dependency // failure based on types. diff --git a/packages/core/src/render3/jit/directive.ts b/packages/core/src/render3/jit/directive.ts index e2db667052..bbc3a023c6 100644 --- a/packages/core/src/render3/jit/directive.ts +++ b/packages/core/src/render3/jit/directive.ts @@ -80,6 +80,8 @@ export function compileComponent(type: Type, metadata: Component): void { wrapDirectivesInClosure: false, styles: metadata.styles || [], encapsulation: metadata.encapsulation || ViewEncapsulation.Emulated, animations, + viewProviders: metadata.viewProviders ? new WrappedNodeExpr(metadata.viewProviders) : + null }, constantPool, makeBindingParser()); const preStatements = [...constantPool.statements, ...res.statements]; @@ -180,6 +182,7 @@ function directiveMetadata(type: Type, metadata: Directive): R3DirectiveMet typeSourceSpan: null !, usesInheritance: !extendsDirectlyFromObject(type), exportAs: metadata.exportAs || null, + providers: metadata.providers ? new WrappedNodeExpr(metadata.providers) : null }; } diff --git a/packages/core/src/render3/jit/environment.ts b/packages/core/src/render3/jit/environment.ts index ecf241e665..12201d2c6e 100644 --- a/packages/core/src/render3/jit/environment.ts +++ b/packages/core/src/render3/jit/environment.ts @@ -32,7 +32,7 @@ export const angularCoreEnv: {[name: string]: Function} = { 'ɵinjectAttribute': r3.injectAttribute, 'ɵtemplateRefExtractor': r3.templateRefExtractor, 'ɵNgOnChangesFeature': r3.NgOnChangesFeature, - 'ɵPublicFeature': r3.PublicFeature, + 'ɵProvidersFeature': r3.ProvidersFeature, 'ɵInheritDefinitionFeature': r3.InheritDefinitionFeature, 'ɵelementAttribute': r3.elementAttribute, 'ɵbind': r3.bind, diff --git a/packages/core/src/render3/pipe.ts b/packages/core/src/render3/pipe.ts index 0ac27f9f5a..d29aae0a63 100644 --- a/packages/core/src/render3/pipe.ts +++ b/packages/core/src/render3/pipe.ts @@ -8,10 +8,11 @@ import {PipeTransform} from '../change_detection/pipe_transform'; -import {getTView, load, store} from './instructions'; +import {load, store} from './instructions'; import {PipeDef, PipeDefList} from './interfaces/definition'; import {HEADER_OFFSET} from './interfaces/view'; import {pureFunction1, pureFunction2, pureFunction3, pureFunction4, pureFunctionV} from './pure_function'; +import {getTView} from './state'; /** * Create a pipe. @@ -36,7 +37,7 @@ export function pipe(index: number, pipeName: string): any { pipeDef = tView.data[adjustedIndex] as PipeDef; } - const pipeInstance = pipeDef.factory(); + const pipeInstance = pipeDef.factory(null); store(index, pipeInstance); return pipeInstance; } diff --git a/packages/core/src/render3/pure_function.ts b/packages/core/src/render3/pure_function.ts index 3e5bd288c4..871042411b 100644 --- a/packages/core/src/render3/pure_function.ts +++ b/packages/core/src/render3/pure_function.ts @@ -6,7 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {bindingUpdated, bindingUpdated2, bindingUpdated4, updateBinding, getBinding, getCreationMode, bindingUpdated3, getBindingRoot, getTView,} from './instructions'; +import {bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4, getBinding, updateBinding} from './instructions'; +import {getBindingRoot, getCreationMode} from './state'; + /** * Bindings for pure functions are stored after regular bindings. diff --git a/packages/core/src/render3/query.ts b/packages/core/src/render3/query.ts index 89f6872d7a..3731756743 100644 --- a/packages/core/src/render3/query.ts +++ b/packages/core/src/render3/query.ts @@ -19,12 +19,13 @@ import {getSymbolIterator} from '../util'; import {assertDefined, assertEqual} from './assert'; import {NG_ELEMENT_ID} from './fields'; -import {_getViewData, assertPreviousIsParent, getOrCreateCurrentQueries, store, storeCleanupWithContext} from './instructions'; +import {store, storeCleanupWithContext} from './instructions'; import {DirectiveDef, unusedValueExportToPlacateAjd as unused1} from './interfaces/definition'; import {unusedValueExportToPlacateAjd as unused2} from './interfaces/injector'; import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeType, unusedValueExportToPlacateAjd as unused3} from './interfaces/node'; import {LQueries, unusedValueExportToPlacateAjd as unused4} from './interfaces/query'; import {LViewData, TVIEW} from './interfaces/view'; +import {assertPreviousIsParent, getOrCreateCurrentQueries, getViewData} from './state'; import {flatten, isContentQueryHost} from './util'; import {createElementRef, createTemplateRef} from './view_engine_compatibility'; @@ -259,7 +260,7 @@ function getIdxOfMatchingDirective(tNode: TNode, currentView: LViewData, type: T const end = start + count; for (let i = start; i < end; i++) { const def = defs[i] as DirectiveDef; - if (def.type === type && def.diPublic) { + if (def.type === type) { return i; } } @@ -293,7 +294,7 @@ function queryReadByTNodeType(tNode: TNode, currentView: LViewData): any { function add( query: LQuery| null, tNode: TElementNode | TContainerNode | TElementContainerNode) { - const currentView = _getViewData(); + const currentView = getViewData(); while (query) { const predicate = query.predicate; diff --git a/packages/core/src/render3/state.ts b/packages/core/src/render3/state.ts new file mode 100644 index 0000000000..baa02dfac7 --- /dev/null +++ b/packages/core/src/render3/state.ts @@ -0,0 +1,432 @@ +/** + * @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 {Sanitizer} from '../sanitization/security'; + +import {assertDefined, assertEqual} from './assert'; +import {executeHooks} from './hooks'; +import {TElementNode, TNode, TNodeFlags, TViewNode} from './interfaces/node'; +import {LQueries} from './interfaces/query'; +import {Renderer3, RendererFactory3} from './interfaces/renderer'; +import {BINDING_INDEX, CLEANUP, CONTEXT, DECLARATION_VIEW, FLAGS, HOST_NODE, LViewData, LViewFlags, OpaqueViewState, QUERIES, RENDERER, SANITIZER, TVIEW, TView} from './interfaces/view'; +import {assertDataInRangeInternal, isContentQueryHost} from './util'; + +/** + * 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; +} + +export function setRenderer(r: Renderer3): void { + renderer = r; +} + +let rendererFactory: RendererFactory3; + +export function getRendererFactory(): RendererFactory3 { + // top level variables should not be exported for performance reasons (PERF_NOTES.md) + return rendererFactory; +} + +export function setRendererFactory(factory: RendererFactory3): void { + rendererFactory = factory; +} + +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; + +export function getElementDepthCount() { + // top level variables should not be exported for performance reasons (PERF_NOTES.md) + return elementDepthCount; +} + +export function increaseElementDepthCount() { + elementDepthCount++; +} + +export function decreaseElementDepthCount() { + elementDepthCount--; +} + +/** + * 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; + +export function getBindingsEnabled(): boolean { + // top level variables should not be exported for performance reasons (PERF_NOTES.md) + return bindingsEnabled; +} + + +/** + * 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; +} + +/** + * 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 setPreviousOrParentTNode(tNode: TNode) { + previousOrParentTNode = tNode; +} + +export function setTNodeAndViewData(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; + +export function getIsParent(): boolean { + // top level variables should not be exported for performance reasons (PERF_NOTES.md) + return isParent; +} + +export function setIsParent(value: boolean): void { + isParent = value; +} + +let tView: TView; + +export function getTView(): TView { + // top level variables should not be exported for performance reasons (PERF_NOTES.md) + return tView; +} + +let currentQueries: LQueries|null; + +export function getCurrentQueries(): LQueries|null { + // top level variables should not be exported for performance reasons (PERF_NOTES.md) + return currentQueries; +} + +export function setCurrentQueries(queries: LQueries | null): void { + currentQueries = queries; +} + +/** + * 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 !; + +export function getContextViewData(): LViewData { + // top level variables should not be exported for performance reasons (PERF_NOTES.md) + return contextViewData; +} + +export function getCleanup(view: LViewData): any[] { + // top level variables should not be exported for performance reasons (PERF_NOTES.md) + return view[CLEANUP] || (view[CLEANUP] = []); +} + +export 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; + +export function getCheckNoChangesMode(): boolean { + // top level variables should not be exported for performance reasons (PERF_NOTES.md) + return checkNoChangesMode; +} + +export function setCheckNoChangesMode(mode: boolean): void { + checkNoChangesMode = mode; +} + +/** Whether or not this is the first time the current view has been processed. */ +let firstTemplatePass = true; + +export function getFirstTemplatePass(): boolean { + return firstTemplatePass; +} + +export function setFirstTemplatePass(value: boolean): void { + firstTemplatePass = value; +} + +/** + * 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; +} + +export function setBindingRoot(value: number) { + bindingRootIndex = value; +} + +/** + * 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; + */ +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; +} + +export function nextContextImpl(level: number = 1): T { + contextViewData = walkUpViews(level, contextViewData !); + return contextViewData[CONTEXT] as T; +} + +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; +} + +/** + * Resets the application state. + */ +export function resetComponentState() { + isParent = false; + previousOrParentTNode = null !; + elementDepthCount = 0; + bindingsEnabled = true; +} + +/** + * 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); +} + +export function assertPreviousIsParent() { + assertEqual(isParent, true, 'previousOrParentTNode should be a parent'); +} + +export function assertHasParent() { + assertDefined(previousOrParentTNode.parent, 'previousOrParentTNode should have a parent'); +} + +export function assertDataInRange(index: number, arr?: any[]) { + if (arr == null) arr = viewData; + assertDataInRangeInternal(index, arr || viewData); +} + +export 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})`); +} diff --git a/packages/core/src/render3/util.ts b/packages/core/src/render3/util.ts index 074f98895f..6805d7c14c 100644 --- a/packages/core/src/render3/util.ts +++ b/packages/core/src/render3/util.ts @@ -11,10 +11,13 @@ import {devModeEqual} from '../change_detection/change_detection_util'; import {assertDefined, assertLessThan} from './assert'; import {ACTIVE_INDEX, LContainer} from './interfaces/container'; import {LContext, MONKEY_PATCH_KEY_NAME} from './interfaces/context'; -import {TNode, TNodeFlags} from './interfaces/node'; +import {ComponentDef, DirectiveDef} from './interfaces/definition'; +import {NO_PARENT_INJECTOR, RelativeInjectorLocation, RelativeInjectorLocationFlags} from './interfaces/injector'; +import {TContainerNode, TElementNode, TNode, TNodeFlags} from './interfaces/node'; import {RComment, RElement, RText} from './interfaces/renderer'; import {StylingContext} from './interfaces/styling'; -import {CONTEXT, FLAGS, HEADER_OFFSET, HOST, LViewData, LViewFlags, PARENT, RootContext, TData, TVIEW} from './interfaces/view'; +import {CONTEXT, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, HOST_NODE, LViewData, LViewFlags, PARENT, RootContext, TData, TVIEW} from './interfaces/view'; + /** @@ -122,6 +125,10 @@ export function isComponent(tNode: TNode): boolean { return (tNode.flags & TNodeFlags.isComponent) === TNodeFlags.isComponent; } +export function isComponentDef(def: DirectiveDef): def is ComponentDef { + return (def as ComponentDef).template !== null; +} + export function isLContainer(value: RElement | RComment | LContainer | StylingContext): boolean { // Styling contexts are also arrays, but their first index contains an element node return Array.isArray(value) && typeof value[ACTIVE_INDEX] === 'number'; @@ -161,3 +168,72 @@ export function readPatchedLViewData(target: any): LViewData|null { } return null; } + +export function hasParentInjector(parentLocation: RelativeInjectorLocation): boolean { + return parentLocation !== NO_PARENT_INJECTOR; +} + +export function getParentInjectorIndex(parentLocation: RelativeInjectorLocation): number { + return (parentLocation as any as number) & RelativeInjectorLocationFlags.InjectorIndexMask; +} + +export function getParentInjectorViewOffset(parentLocation: RelativeInjectorLocation): number { + return (parentLocation as any as number) >> RelativeInjectorLocationFlags.ViewOffsetShift; +} + +/** + * 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: RelativeInjectorLocation, startView: LViewData): LViewData { + let viewOffset = getParentInjectorViewOffset(location); + 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; +} + +/** + * Unwraps a parent injector location number to find the view offset from the current injector, + * then walks up the declaration view tree until the TNode of the parent injector is found. + * + * @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 + * @param startTNode The TNode instance of the starting element + * @returns The TNode of the parent injector + */ +export function getParentInjectorTNode( + location: RelativeInjectorLocation, startView: LViewData, startTNode: TNode): TElementNode| + TContainerNode|null { + if (startTNode.parent && startTNode.parent.injectorIndex !== -1) { + // view offset is 0 + const injectorIndex = startTNode.parent.injectorIndex; + let parentTNode = startTNode.parent; + while (parentTNode.parent != null && injectorIndex == parentTNode.injectorIndex) { + parentTNode = parentTNode.parent; + } + return parentTNode; + } + + let viewOffset = getParentInjectorViewOffset(location); + let parentView = startView; + let parentTNode = startView[HOST_NODE] as TElementNode; + while (viewOffset > 0) { + parentView = parentView[DECLARATION_VIEW] !; + parentTNode = parentView[HOST_NODE] as TElementNode; + viewOffset--; + } + return parentTNode; +} diff --git a/packages/core/src/render3/view_engine_compatibility.ts b/packages/core/src/render3/view_engine_compatibility.ts index ab0674c401..fd8c61dd52 100644 --- a/packages/core/src/render3/view_engine_compatibility.ts +++ b/packages/core/src/render3/view_engine_compatibility.ts @@ -17,18 +17,18 @@ import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, ViewRef as viewEngine_Vie import {Renderer2} from '../render/api'; import {assertDefined, assertGreaterThan, assertLessThan} from './assert'; -import {NodeInjector, getParentInjectorLocation, getParentInjectorView} from './di'; -import {_getViewData, addToViewTree, createEmbeddedViewAndNode, createLContainer, getPreviousOrParentTNode, getRenderer, renderEmbeddedTemplate} from './instructions'; -import {ACTIVE_INDEX, LContainer, NATIVE, RENDER_PARENT, VIEWS} from './interfaces/container'; +import {getOrCreateInjectable, getParentInjectorLocation} from './di'; +import {addToViewTree, createEmbeddedViewAndNode, createLContainer, renderEmbeddedTemplate} from './instructions'; +import {ACTIVE_INDEX, LContainer, NATIVE, VIEWS} from './interfaces/container'; import {RenderFlags} from './interfaces/definition'; -import {InjectorLocationFlags} from './interfaces/injector'; import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeType, TViewNode} from './interfaces/node'; import {LQueries} from './interfaces/query'; import {RComment, RElement, Renderer3, isProceduralRenderer} from './interfaces/renderer'; -import {CONTEXT, HOST_NODE, LViewData, QUERIES, RENDERER, TVIEW, TView} from './interfaces/view'; +import {CONTEXT, HOST_NODE, LViewData, QUERIES, RENDERER, TView} from './interfaces/view'; import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; import {addRemoveViewFromContainer, appendChild, detachView, findComponentView, getBeforeNodeForView, getRenderParent, insertView, removeView} from './node_manipulation'; -import {getComponentViewByIndex, getNativeByTNode, isComponent, isLContainer} from './util'; +import {getPreviousOrParentTNode, getRenderer, getViewData} from './state'; +import {getComponentViewByIndex, getNativeByTNode, getParentInjectorTNode, getParentInjectorView, hasParentInjector, isComponent, isLContainer} from './util'; import {ViewRef} from './view_ref'; @@ -40,7 +40,7 @@ import {ViewRef} from './view_ref'; */ export function injectElementRef(ElementRefToken: typeof ViewEngine_ElementRef): ViewEngine_ElementRef { - return createElementRef(ElementRefToken, getPreviousOrParentTNode(), _getViewData()); + return createElementRef(ElementRefToken, getPreviousOrParentTNode(), getViewData()); } let R3ElementRef: {new (native: RElement | RComment): ViewEngine_ElementRef}; @@ -79,7 +79,7 @@ export function injectTemplateRef( TemplateRefToken: typeof ViewEngine_TemplateRef, ElementRefToken: typeof ViewEngine_ElementRef): ViewEngine_TemplateRef { return createTemplateRef( - TemplateRefToken, ElementRefToken, getPreviousOrParentTNode(), _getViewData()); + TemplateRefToken, ElementRefToken, getPreviousOrParentTNode(), getViewData()); } /** @@ -147,9 +147,18 @@ export function injectViewContainerRef( ElementRefToken: typeof ViewEngine_ElementRef): ViewEngine_ViewContainerRef { const previousTNode = getPreviousOrParentTNode() as TElementNode | TElementContainerNode | TContainerNode; - return createContainerRef(ViewContainerRefToken, ElementRefToken, previousTNode, _getViewData()); + return createContainerRef(ViewContainerRefToken, ElementRefToken, previousTNode, getViewData()); } +export class NodeInjector implements Injector { + constructor( + private _tNode: TElementNode|TContainerNode|TElementContainerNode, + private _hostView: LViewData) {} + + get(token: any, notFoundValue?: any): any { + return getOrCreateInjectable(this._tNode, this._hostView, token, notFoundValue); + } +} /** * Creates a ViewContainerRef and stores it on the injector. @@ -187,11 +196,11 @@ export function createContainerRef( get parentInjector(): Injector { const parentLocation = getParentInjectorLocation(this._hostTNode, this._hostView); const parentView = getParentInjectorView(parentLocation, this._hostView); - const parentIndex = parentLocation & InjectorLocationFlags.InjectorIndexMask; - const parentTNode = parentView[TVIEW].data[parentIndex] as TElementNode | TContainerNode; + const parentTNode = getParentInjectorTNode(parentLocation, this._hostView, this._hostTNode); - return parentLocation === -1 ? new NullInjector() : - new NodeInjector(parentTNode, parentView); + return !hasParentInjector(parentLocation) || parentTNode == null ? + new NullInjector() : + new NodeInjector(parentTNode, parentView); } clear(): void { @@ -310,7 +319,7 @@ export function createContainerRef( /** Returns a ChangeDetectorRef (a.k.a. a ViewRef) */ export function injectChangeDetectorRef(): ViewEngine_ChangeDetectorRef { - return createViewRef(getPreviousOrParentTNode(), _getViewData(), null); + return createViewRef(getPreviousOrParentTNode(), getViewData(), null); } /** @@ -345,5 +354,5 @@ function getOrCreateRenderer2(view: LViewData): Renderer2 { /** Returns a Renderer2 (or throws when application was bootstrapped with Renderer3) */ export function injectRenderer2(): Renderer2 { - return getOrCreateRenderer2(_getViewData()); + return getOrCreateRenderer2(getViewData()); } diff --git a/packages/core/src/render3/view_ref.ts b/packages/core/src/render3/view_ref.ts index 0c9f38da10..e47b32fbcf 100644 --- a/packages/core/src/render3/view_ref.ts +++ b/packages/core/src/render3/view_ref.ts @@ -11,10 +11,11 @@ import {ChangeDetectorRef as viewEngine_ChangeDetectorRef} from '../change_detec import {ViewContainerRef as viewEngine_ViewContainerRef} from '../linker/view_container_ref'; import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, InternalViewRef as viewEngine_InternalViewRef} from '../linker/view_ref'; -import {checkNoChanges, checkNoChangesInRootView, detectChanges, detectChangesInRootView, getRendererFactory, markViewDirty, storeCleanupFn, viewAttached} from './instructions'; +import {checkNoChanges, checkNoChangesInRootView, detectChanges, detectChangesInRootView, markViewDirty, storeCleanupFn, viewAttached} from './instructions'; import {TViewNode} from './interfaces/node'; import {FLAGS, LViewData, LViewFlags, PARENT} from './interfaces/view'; import {destroyLView} from './node_manipulation'; +import {getRendererFactory} from './state'; // Needed due to tsickle downleveling where multiple `implements` with classes creates diff --git a/packages/core/src/sanitization/sanitization.ts b/packages/core/src/sanitization/sanitization.ts index c5fac7314a..21430bd090 100644 --- a/packages/core/src/sanitization/sanitization.ts +++ b/packages/core/src/sanitization/sanitization.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {getCurrentSanitizer} from '../render3/instructions'; +import {getCurrentSanitizer} from '../render3/state'; import {stringify} from '../render3/util'; import {BypassType, allowSanitizationBypass} from './bypass'; diff --git a/packages/core/test/bundling/animation_world/bundle.golden_symbols.json b/packages/core/test/bundling/animation_world/bundle.golden_symbols.json index 0803942ab9..e6cf299e6c 100644 --- a/packages/core/test/bundling/animation_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/animation_world/bundle.golden_symbols.json @@ -17,9 +17,6 @@ { "name": "BoundPlayerFactory" }, - { - "name": "CIRCULAR$1" - }, { "name": "CLEANUP" }, @@ -71,6 +68,9 @@ { "name": "FLAGS" }, + { + "name": "FactoryPrototype" + }, { "name": "HEADER_OFFSET" }, @@ -119,9 +119,15 @@ { "name": "NG_PROJECT_AS_ATTR_NAME" }, + { + "name": "NOT_FOUND" + }, { "name": "NO_CHANGE" }, + { + "name": "NO_PARENT_INJECTOR" + }, { "name": "NgForOf" }, @@ -134,6 +140,9 @@ { "name": "NodeInjector" }, + { + "name": "NodeInjectorFactory" + }, { "name": "NullInjector" }, @@ -152,9 +161,6 @@ { "name": "PARENT_INJECTOR" }, - { - "name": "PublicFeature" - }, { "name": "QUERIES" }, @@ -263,18 +269,12 @@ { "name": "_c4" }, - { - "name": "_currentInjector" - }, { "name": "_currentNamespace" }, { "name": "_devMode" }, - { - "name": "_getViewData" - }, { "name": "_global" }, @@ -315,7 +315,7 @@ "name": "attachPatchData" }, { - "name": "baseDirectiveCreate" + "name": "baseResolveDirective" }, { "name": "bind" @@ -323,24 +323,21 @@ { "name": "bindPlayerFactory" }, - { - "name": "bindingRootIndex" - }, { "name": "bindingUpdated" }, { "name": "bloomAdd" }, + { + "name": "bloomHasToken" + }, { "name": "bloomHashBitOrFactory" }, { "name": "buildAnimationPlayer" }, - { - "name": "cacheMatchingDirectivesForNode" - }, { "name": "cacheMatchingLocalNames" }, @@ -437,6 +434,9 @@ { "name": "createViewQuery" }, + { + "name": "decreaseElementDepthCount" + }, { "name": "defineComponent" }, @@ -464,15 +464,9 @@ { "name": "detectChangesInternal" }, - { - "name": "diPublic" - }, { "name": "diPublicInInjector" }, - { - "name": "directiveCreate" - }, { "name": "directiveInject" }, @@ -558,7 +552,7 @@ "name": "firstTemplatePass" }, { - "name": "generateExpandoBlock" + "name": "generateExpandoInstructionBlock" }, { "name": "generateInitialInputs" @@ -569,6 +563,12 @@ { "name": "getBeforeNodeForView" }, + { + "name": "getBindingsEnabled" + }, + { + "name": "getCheckNoChangesMode" + }, { "name": "getCleanup" }, @@ -590,6 +590,12 @@ { "name": "getContext" }, + { + "name": "getCreationMode" + }, + { + "name": "getCurrentQueries" + }, { "name": "getCurrentSanitizer" }, @@ -602,6 +608,12 @@ { "name": "getDirectiveStartIndex" }, + { + "name": "getElementDepthCount" + }, + { + "name": "getFirstTemplatePass" + }, { "name": "getHighestElementContainer" }, @@ -620,6 +632,9 @@ { "name": "getInjectorIndex" }, + { + "name": "getIsParent" + }, { "name": "getLContainer" }, @@ -639,10 +654,10 @@ "name": "getNativeByTNode" }, { - "name": "getOrCreateInjectable" + "name": "getNodeInjectable" }, { - "name": "getOrCreateNodeInjector" + "name": "getOrCreateInjectable" }, { "name": "getOrCreateNodeInjectorForNode" @@ -653,12 +668,21 @@ { "name": "getOrCreateTView" }, + { + "name": "getParentInjectorIndex" + }, { "name": "getParentInjectorLocation" }, + { + "name": "getParentInjectorTNode" + }, { "name": "getParentInjectorView" }, + { + "name": "getParentInjectorViewOffset" + }, { "name": "getParentNative" }, @@ -705,7 +729,7 @@ "name": "getRootContext" }, { - "name": "getRootContext$2" + "name": "getRootContext$1" }, { "name": "getRootView" @@ -725,6 +749,9 @@ { "name": "getTNode" }, + { + "name": "getTView" + }, { "name": "getTViewCleanup" }, @@ -737,20 +764,35 @@ { "name": "getValue" }, + { + "name": "getViewData" + }, + { + "name": "hasParentInjector" + }, { "name": "hasPlayerBuilderChanged" }, { "name": "hasValueChanged" }, + { + "name": "includeViewProviders" + }, + { + "name": "increaseElementDepthCount" + }, + { + "name": "initNodeFlags" + }, { "name": "initializeTNodeInputs" }, { - "name": "inject" + "name": "injectElementRef" }, { - "name": "injectElementRef" + "name": "injectRootLimpMode" }, { "name": "injectTemplateRef" @@ -759,7 +801,7 @@ "name": "injectViewContainerRef" }, { - "name": "injectorHasToken" + "name": "insertBloom" }, { "name": "insertNewMultiProperty" @@ -768,7 +810,10 @@ "name": "insertView" }, { - "name": "instantiateDirectivesDirectly" + "name": "instantiateAllDirectives" + }, + { + "name": "instantiateRootComponent" }, { "name": "interpolation1" @@ -783,10 +828,10 @@ "name": "isComponent" }, { - "name": "isComponentInstance" + "name": "isComponentDef" }, { - "name": "isContentQueryHost" + "name": "isComponentInstance" }, { "name": "isContextDirty" @@ -806,6 +851,9 @@ { "name": "isDirty" }, + { + "name": "isFactory" + }, { "name": "isJsObject" }, @@ -878,6 +926,9 @@ { "name": "nextContext" }, + { + "name": "nextContextImpl" + }, { "name": "nextNgElementId" }, @@ -887,6 +938,12 @@ { "name": "pointers" }, + { + "name": "postProcessBaseDirective" + }, + { + "name": "postProcessDirective" + }, { "name": "prefillHostVars" }, @@ -960,7 +1017,7 @@ "name": "resetComponentState" }, { - "name": "resolveDirective" + "name": "resolveDirectives" }, { "name": "saveNameToExportMap" @@ -972,14 +1029,17 @@ "name": "scheduleTick" }, { - "name": "searchDirectivesOnInjector" - }, - { - "name": "searchMatchesQueuedForCreation" + "name": "searchTokensOnInjector" }, { "name": "setAnimationPlayState" }, + { + "name": "setBindingRoot" + }, + { + "name": "setCheckNoChangesMode" + }, { "name": "setClass" }, @@ -990,13 +1050,13 @@ "name": "setContextPlayersDirty" }, { - "name": "setCurrentInjector" + "name": "setCurrentQueries" }, { "name": "setDirty" }, { - "name": "setEnvironment" + "name": "setFirstTemplatePass" }, { "name": "setFlag" @@ -1004,35 +1064,50 @@ { "name": "setHostBindings" }, + { + "name": "setIncludeViewProviders" + }, + { + "name": "setInjectImplementation" + }, { "name": "setInputsForProperty" }, { "name": "setInputsFromAttrs" }, + { + "name": "setIsParent" + }, { "name": "setPlayerBuilder" }, { "name": "setPlayerBuilderIndex" }, + { + "name": "setPreviousOrParentTNode" + }, { "name": "setProp" }, + { + "name": "setRendererFactory" + }, { "name": "setStyle" }, { - "name": "setUpAttributes" + "name": "setTNodeAndViewData" }, { - "name": "setUpBloom" + "name": "setUpAttributes" }, { "name": "setValue" }, { - "name": "shouldNotSearchParent" + "name": "shouldSearchParent" }, { "name": "storeCleanupFn" @@ -1061,9 +1136,6 @@ { "name": "textBinding" }, - { - "name": "throwCyclicDependencyError" - }, { "name": "throwErrorIfNoChangesMode" }, diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index 4508d3edc7..fb8c0e6998 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -35,6 +35,9 @@ { "name": "FLAGS" }, + { + "name": "FactoryPrototype" + }, { "name": "HEADER_OFFSET" }, @@ -77,6 +80,12 @@ { "name": "NO_CHANGE" }, + { + "name": "NO_PARENT_INJECTOR" + }, + { + "name": "NodeInjectorFactory" + }, { "name": "ObjectUnsubscribedErrorImpl" }, @@ -86,9 +95,6 @@ { "name": "PARENT_INJECTOR" }, - { - "name": "PublicFeature" - }, { "name": "QUERIES" }, @@ -116,9 +122,6 @@ { "name": "ViewEncapsulation$1" }, - { - "name": "_getViewData" - }, { "name": "_renderCompCount" }, @@ -129,10 +132,7 @@ "name": "attachPatchData" }, { - "name": "baseDirectiveCreate" - }, - { - "name": "bindingRootIndex" + "name": "baseResolveDirective" }, { "name": "bloomAdd" @@ -191,9 +191,6 @@ { "name": "detectChangesInternal" }, - { - "name": "diPublic" - }, { "name": "diPublicInInjector" }, @@ -224,6 +221,9 @@ { "name": "getBeforeNodeForView" }, + { + "name": "getCheckNoChangesMode" + }, { "name": "getClosureSafeProperty" }, @@ -236,9 +236,15 @@ { "name": "getContainerRenderParent" }, + { + "name": "getCreationMode" + }, { "name": "getDirectiveDef" }, + { + "name": "getFirstTemplatePass" + }, { "name": "getHighestElementContainer" }, @@ -248,6 +254,9 @@ { "name": "getInjectorIndex" }, + { + "name": "getIsParent" + }, { "name": "getLContainer" }, @@ -258,7 +267,7 @@ "name": "getNativeByTNode" }, { - "name": "getOrCreateNodeInjector" + "name": "getNodeInjectable" }, { "name": "getOrCreateNodeInjectorForNode" @@ -266,12 +275,18 @@ { "name": "getOrCreateTView" }, + { + "name": "getParentInjectorIndex" + }, { "name": "getParentInjectorLocation" }, { "name": "getParentInjectorView" }, + { + "name": "getParentInjectorViewOffset" + }, { "name": "getParentNative" }, @@ -287,9 +302,42 @@ { "name": "getRenderParent" }, + { + "name": "getRenderer" + }, + { + "name": "getRendererFactory" + }, + { + "name": "getTView" + }, + { + "name": "getViewData" + }, + { + "name": "hasParentInjector" + }, + { + "name": "includeViewProviders" + }, + { + "name": "initNodeFlags" + }, + { + "name": "insertBloom" + }, + { + "name": "instantiateRootComponent" + }, { "name": "invertObject" }, + { + "name": "isComponentDef" + }, + { + "name": "isFactory" + }, { "name": "isProceduralRenderer" }, @@ -311,6 +359,9 @@ { "name": "noSideEffects" }, + { + "name": "postProcessBaseDirective" + }, { "name": "prefillHostVars" }, @@ -350,14 +401,35 @@ { "name": "resetComponentState" }, + { + "name": "setBindingRoot" + }, + { + "name": "setFirstTemplatePass" + }, { "name": "setHostBindings" }, { - "name": "setUpAttributes" + "name": "setIncludeViewProviders" }, { - "name": "setUpBloom" + "name": "setInjectImplementation" + }, + { + "name": "setIsParent" + }, + { + "name": "setPreviousOrParentTNode" + }, + { + "name": "setRendererFactory" + }, + { + "name": "setTNodeAndViewData" + }, + { + "name": "setUpAttributes" }, { "name": "stringify$2" diff --git a/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json index 8105d68e2a..58682de9cb 100644 --- a/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json @@ -1967,6 +1967,9 @@ { "name": "__extends$p" }, + { + "name": "__forward_ref__" + }, { "name": "__metadata" }, @@ -3098,6 +3101,12 @@ { "name": "injectArgs" }, + { + "name": "injectInjectorOnly" + }, + { + "name": "injectRootLimpMode" + }, { "name": "inlineInterpolate" }, diff --git a/packages/core/test/bundling/injection/bundle.golden_symbols.json b/packages/core/test/bundling/injection/bundle.golden_symbols.json index 37ecf740db..3ccdb6f3c1 100644 --- a/packages/core/test/bundling/injection/bundle.golden_symbols.json +++ b/packages/core/test/bundling/injection/bundle.golden_symbols.json @@ -3,7 +3,7 @@ "name": "APP_ROOT" }, { - "name": "CIRCULAR$2" + "name": "CIRCULAR$1" }, { "name": "EMPTY_ARRAY$2" @@ -68,6 +68,9 @@ { "name": "_THROW_IF_NOT_FOUND" }, + { + "name": "__forward_ref__" + }, { "name": "__read" }, @@ -120,7 +123,13 @@ "name": "injectArgs" }, { - "name": "injectableDefRecord" + "name": "injectInjectorOnly" + }, + { + "name": "injectRootLimpMode" + }, + { + "name": "injectableDefFactory" }, { "name": "isExistingProvider" @@ -143,6 +152,9 @@ { "name": "makeRecord" }, + { + "name": "providerToFactory" + }, { "name": "providerToRecord" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 5cec3c613e..e94822a8a0 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -11,9 +11,6 @@ { "name": "BoundPlayerFactory" }, - { - "name": "CIRCULAR$1" - }, { "name": "CLEANUP" }, @@ -59,6 +56,9 @@ { "name": "FLAGS" }, + { + "name": "FactoryPrototype" + }, { "name": "HEADER_OFFSET" }, @@ -107,9 +107,15 @@ { "name": "NG_PROJECT_AS_ATTR_NAME" }, + { + "name": "NOT_FOUND" + }, { "name": "NO_CHANGE" }, + { + "name": "NO_PARENT_INJECTOR" + }, { "name": "NgForOf" }, @@ -128,6 +134,9 @@ { "name": "NodeInjector" }, + { + "name": "NodeInjectorFactory" + }, { "name": "NullInjector" }, @@ -146,9 +155,6 @@ { "name": "PARENT_INJECTOR" }, - { - "name": "PublicFeature" - }, { "name": "QUERIES" }, @@ -329,18 +335,12 @@ { "name": "_c9" }, - { - "name": "_currentInjector" - }, { "name": "_currentNamespace" }, { "name": "_devMode" }, - { - "name": "_getViewData" - }, { "name": "_global" }, @@ -378,14 +378,11 @@ "name": "attachPatchData" }, { - "name": "baseDirectiveCreate" + "name": "baseResolveDirective" }, { "name": "bind" }, - { - "name": "bindingRootIndex" - }, { "name": "bindingUpdated" }, @@ -393,10 +390,10 @@ "name": "bloomAdd" }, { - "name": "bloomHashBitOrFactory" + "name": "bloomHasToken" }, { - "name": "cacheMatchingDirectivesForNode" + "name": "bloomHashBitOrFactory" }, { "name": "cacheMatchingLocalNames" @@ -494,6 +491,9 @@ { "name": "createViewQuery" }, + { + "name": "decreaseElementDepthCount" + }, { "name": "defineComponent" }, @@ -521,15 +521,9 @@ { "name": "detectChangesInternal" }, - { - "name": "diPublic" - }, { "name": "diPublicInInjector" }, - { - "name": "directiveCreate" - }, { "name": "directiveInject" }, @@ -603,7 +597,7 @@ "name": "firstTemplatePass" }, { - "name": "generateExpandoBlock" + "name": "generateExpandoInstructionBlock" }, { "name": "generateInitialInputs" @@ -614,6 +608,12 @@ { "name": "getBeforeNodeForView" }, + { + "name": "getBindingsEnabled" + }, + { + "name": "getCheckNoChangesMode" + }, { "name": "getCleanup" }, @@ -632,6 +632,15 @@ { "name": "getContainerRenderParent" }, + { + "name": "getContextViewData" + }, + { + "name": "getCreationMode" + }, + { + "name": "getCurrentQueries" + }, { "name": "getCurrentSanitizer" }, @@ -641,6 +650,12 @@ { "name": "getDirectiveDef" }, + { + "name": "getElementDepthCount" + }, + { + "name": "getFirstTemplatePass" + }, { "name": "getHighestElementContainer" }, @@ -659,6 +674,9 @@ { "name": "getInjectorIndex" }, + { + "name": "getIsParent" + }, { "name": "getLContainer" }, @@ -678,10 +696,10 @@ "name": "getNativeByTNode" }, { - "name": "getOrCreateInjectable" + "name": "getNodeInjectable" }, { - "name": "getOrCreateNodeInjector" + "name": "getOrCreateInjectable" }, { "name": "getOrCreateNodeInjectorForNode" @@ -689,12 +707,21 @@ { "name": "getOrCreateTView" }, + { + "name": "getParentInjectorIndex" + }, { "name": "getParentInjectorLocation" }, + { + "name": "getParentInjectorTNode" + }, { "name": "getParentInjectorView" }, + { + "name": "getParentInjectorViewOffset" + }, { "name": "getParentNative" }, @@ -755,6 +782,9 @@ { "name": "getTNode" }, + { + "name": "getTView" + }, { "name": "getTViewCleanup" }, @@ -767,20 +797,35 @@ { "name": "getValue" }, + { + "name": "getViewData" + }, + { + "name": "hasParentInjector" + }, { "name": "hasPlayerBuilderChanged" }, { "name": "hasValueChanged" }, + { + "name": "includeViewProviders" + }, + { + "name": "increaseElementDepthCount" + }, + { + "name": "initNodeFlags" + }, { "name": "initializeTNodeInputs" }, { - "name": "inject" + "name": "injectElementRef" }, { - "name": "injectElementRef" + "name": "injectRootLimpMode" }, { "name": "injectTemplateRef" @@ -789,13 +834,16 @@ "name": "injectViewContainerRef" }, { - "name": "injectorHasToken" + "name": "insertBloom" }, { "name": "insertView" }, { - "name": "instantiateDirectivesDirectly" + "name": "instantiateAllDirectives" + }, + { + "name": "instantiateRootComponent" }, { "name": "interpolation1" @@ -807,7 +855,7 @@ "name": "isComponent" }, { - "name": "isContentQueryHost" + "name": "isComponentDef" }, { "name": "isContextDirty" @@ -824,6 +872,9 @@ { "name": "isDirty" }, + { + "name": "isFactory" + }, { "name": "isJsObject" }, @@ -893,6 +944,9 @@ { "name": "nextContext" }, + { + "name": "nextContextImpl" + }, { "name": "nextNgElementId" }, @@ -902,6 +956,12 @@ { "name": "pointers" }, + { + "name": "postProcessBaseDirective" + }, + { + "name": "postProcessDirective" + }, { "name": "prefillHostVars" }, @@ -978,7 +1038,7 @@ "name": "resetComponentState" }, { - "name": "resolveDirective" + "name": "resolveDirectives" }, { "name": "restoreView" @@ -993,10 +1053,13 @@ "name": "scheduleTick" }, { - "name": "searchDirectivesOnInjector" + "name": "searchTokensOnInjector" }, { - "name": "searchMatchesQueuedForCreation" + "name": "setBindingRoot" + }, + { + "name": "setCheckNoChangesMode" }, { "name": "setClass" @@ -1008,13 +1071,13 @@ "name": "setContextPlayersDirty" }, { - "name": "setCurrentInjector" + "name": "setCurrentQueries" }, { "name": "setDirty" }, { - "name": "setEnvironment" + "name": "setFirstTemplatePass" }, { "name": "setFlag" @@ -1022,35 +1085,50 @@ { "name": "setHostBindings" }, + { + "name": "setIncludeViewProviders" + }, + { + "name": "setInjectImplementation" + }, { "name": "setInputsForProperty" }, { "name": "setInputsFromAttrs" }, + { + "name": "setIsParent" + }, { "name": "setPlayerBuilder" }, { "name": "setPlayerBuilderIndex" }, + { + "name": "setPreviousOrParentTNode" + }, { "name": "setProp" }, + { + "name": "setRendererFactory" + }, { "name": "setStyle" }, { - "name": "setUpAttributes" + "name": "setTNodeAndViewData" }, { - "name": "setUpBloom" + "name": "setUpAttributes" }, { "name": "setValue" }, { - "name": "shouldNotSearchParent" + "name": "shouldSearchParent" }, { "name": "storeCleanupFn" @@ -1076,9 +1154,6 @@ { "name": "textBinding" }, - { - "name": "throwCyclicDependencyError" - }, { "name": "throwErrorIfNoChangesMode" }, diff --git a/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json b/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json index 43fe115bf6..69b61b421f 100644 --- a/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json @@ -80,9 +80,6 @@ { "name": "CIRCULAR$1" }, - { - "name": "CIRCULAR$2" - }, { "name": "CLEANUP" }, @@ -308,6 +305,9 @@ { "name": "FLAGS" }, + { + "name": "FactoryPrototype" + }, { "name": "FormStyle" }, @@ -500,6 +500,9 @@ { "name": "NON_ALPHANUMERIC_REGEXP" }, + { + "name": "NOT_FOUND" + }, { "name": "NOT_YET" }, @@ -509,6 +512,9 @@ { "name": "NO_NEW_LINE" }, + { + "name": "NO_PARENT_INJECTOR" + }, { "name": "NULL_INJECTOR" }, @@ -587,6 +593,9 @@ { "name": "NodeInjector" }, + { + "name": "NodeInjectorFactory" + }, { "name": "NoopNgZone" }, @@ -668,9 +677,6 @@ { "name": "Plural" }, - { - "name": "PublicFeature" - }, { "name": "QUERIES" }, @@ -998,6 +1004,9 @@ { "name": "__extends$p" }, + { + "name": "__forward_ref__" + }, { "name": "__read" }, @@ -1115,9 +1124,6 @@ { "name": "_enable_super_gross_mode_that_will_cause_bad_things" }, - { - "name": "_getViewData" - }, { "name": "_global" }, @@ -1184,9 +1190,6 @@ { "name": "addToViewTree" }, - { - "name": "adjustBlueprintForNewNode" - }, { "name": "allocPlayerContext" }, @@ -1208,17 +1211,14 @@ { "name": "attachPatchData" }, - { - "name": "baseDirectiveCreate" - }, { "name": "baseElement" }, { - "name": "bind" + "name": "baseResolveDirective" }, { - "name": "bindingRootIndex" + "name": "bind" }, { "name": "bindingUpdated" @@ -1230,10 +1230,10 @@ "name": "bloomAdd" }, { - "name": "bloomHashBitOrFactory" + "name": "bloomHasToken" }, { - "name": "cacheMatchingDirectivesForNode" + "name": "bloomHashBitOrFactory" }, { "name": "cacheMatchingLocalNames" @@ -1394,6 +1394,9 @@ { "name": "decoratePreventDefault" }, + { + "name": "decreaseElementDepthCount" + }, { "name": "deepForEach" }, @@ -1451,15 +1454,9 @@ { "name": "detectWTF" }, - { - "name": "diPublic" - }, { "name": "diPublicInInjector" }, - { - "name": "directiveCreate" - }, { "name": "directiveInject" }, @@ -1605,7 +1602,7 @@ "name": "fromPromise" }, { - "name": "generateExpandoBlock" + "name": "generateExpandoInstructionBlock" }, { "name": "generateInitialInputs" @@ -1619,6 +1616,12 @@ { "name": "getBeforeNodeForView" }, + { + "name": "getBindingsEnabled" + }, + { + "name": "getCheckNoChangesMode" + }, { "name": "getCleanup" }, @@ -1637,9 +1640,18 @@ { "name": "getContainerRenderParent" }, + { + "name": "getContextViewData" + }, + { + "name": "getCreationMode" + }, { "name": "getCurrencySymbol" }, + { + "name": "getCurrentQueries" + }, { "name": "getCurrentSanitizer" }, @@ -1667,9 +1679,15 @@ { "name": "getDirectiveDef" }, + { + "name": "getElementDepthCount" + }, { "name": "getErrorLogger" }, + { + "name": "getFirstTemplatePass" + }, { "name": "getFirstThursdayOfYear" }, @@ -1694,6 +1712,9 @@ { "name": "getInjectorIndex" }, + { + "name": "getIsParent" + }, { "name": "getLContainer" }, @@ -1766,6 +1787,9 @@ { "name": "getNgZone" }, + { + "name": "getNodeInjectable" + }, { "name": "getNullInjector" }, @@ -1775,9 +1799,6 @@ { "name": "getOrCreateInjectable" }, - { - "name": "getOrCreateNodeInjector" - }, { "name": "getOrCreateNodeInjectorForNode" }, @@ -1790,12 +1811,21 @@ { "name": "getOriginalError" }, + { + "name": "getParentInjectorIndex" + }, { "name": "getParentInjectorLocation" }, + { + "name": "getParentInjectorTNode" + }, { "name": "getParentInjectorView" }, + { + "name": "getParentInjectorViewOffset" + }, { "name": "getParentNative" }, @@ -1868,6 +1898,9 @@ { "name": "getTNode" }, + { + "name": "getTView" + }, { "name": "getTViewCleanup" }, @@ -1886,6 +1919,9 @@ { "name": "getValue" }, + { + "name": "getViewData" + }, { "name": "globalListener" }, @@ -1898,6 +1934,9 @@ { "name": "hasOnDestroy" }, + { + "name": "hasParentInjector" + }, { "name": "hasPlayerBuilderChanged" }, @@ -1910,9 +1949,18 @@ { "name": "identity" }, + { + "name": "includeViewProviders" + }, + { + "name": "increaseElementDepthCount" + }, { "name": "initDomAdapter" }, + { + "name": "initNodeFlags" + }, { "name": "initializeTNodeInputs" }, @@ -1925,15 +1973,24 @@ { "name": "injectAttribute" }, + { + "name": "injectAttributeImpl" + }, { "name": "injectChangeDetectorRef" }, { "name": "injectElementRef" }, + { + "name": "injectInjectorOnly" + }, { "name": "injectRenderer2" }, + { + "name": "injectRootLimpMode" + }, { "name": "injectTemplateRef" }, @@ -1941,10 +1998,10 @@ "name": "injectViewContainerRef" }, { - "name": "injectableDefRecord" + "name": "injectableDefFactory" }, { - "name": "injectorHasToken" + "name": "insertBloom" }, { "name": "insertView" @@ -1953,7 +2010,10 @@ "name": "inspectNativeElement" }, { - "name": "instantiateDirectivesDirectly" + "name": "instantiateAllDirectives" + }, + { + "name": "instantiateRootComponent" }, { "name": "interpolation1" @@ -1977,7 +2037,7 @@ "name": "isComponent" }, { - "name": "isContentQueryHost" + "name": "isComponentDef" }, { "name": "isContextDirty" @@ -2006,6 +2066,9 @@ { "name": "isExistingProvider" }, + { + "name": "isFactory" + }, { "name": "isFactoryProvider" }, @@ -2153,6 +2216,9 @@ { "name": "nextContext" }, + { + "name": "nextContextImpl" + }, { "name": "nextNgElementId" }, @@ -2216,6 +2282,12 @@ { "name": "pointers" }, + { + "name": "postProcessBaseDirective" + }, + { + "name": "postProcessDirective" + }, { "name": "prefillHostVars" }, @@ -2228,6 +2300,9 @@ { "name": "promise" }, + { + "name": "providerToFactory" + }, { "name": "providerToRecord" }, @@ -2307,7 +2382,7 @@ "name": "resetComponentState" }, { - "name": "resolveDirective" + "name": "resolveDirectives" }, { "name": "resolveForwardRef$1" @@ -2343,10 +2418,13 @@ "name": "scheduleTick" }, { - "name": "searchDirectivesOnInjector" + "name": "searchTokensOnInjector" }, { - "name": "searchMatchesQueuedForCreation" + "name": "setBindingRoot" + }, + { + "name": "setCheckNoChangesMode" }, { "name": "setClass" @@ -2360,11 +2438,14 @@ { "name": "setCurrentInjector" }, + { + "name": "setCurrentQueries" + }, { "name": "setDirty" }, { - "name": "setEnvironment" + "name": "setFirstTemplatePass" }, { "name": "setFlag" @@ -2372,36 +2453,51 @@ { "name": "setHostBindings" }, + { + "name": "setIncludeViewProviders" + }, + { + "name": "setInjectImplementation" + }, { "name": "setInputsForProperty" }, { "name": "setInputsFromAttrs" }, + { + "name": "setIsParent" + }, { "name": "setPlayerBuilder" }, { "name": "setPlayerBuilderIndex" }, + { + "name": "setPreviousOrParentTNode" + }, { "name": "setProp" }, + { + "name": "setRendererFactory" + }, { "name": "setRootDomAdapter" }, { "name": "setStyle" }, + { + "name": "setTNodeAndViewData" + }, { "name": "setTestabilityGetter" }, { "name": "setUpAttributes" }, - { - "name": "setUpBloom" - }, { "name": "setValue" }, @@ -2418,7 +2514,7 @@ "name": "shimHostAttribute" }, { - "name": "shouldNotSearchParent" + "name": "shouldSearchParent" }, { "name": "staticError" @@ -2483,9 +2579,6 @@ { "name": "textBinding" }, - { - "name": "throwCyclicDependencyError" - }, { "name": "throwErrorIfNoChangesMode" }, diff --git a/packages/core/test/render3/Inherit_definition_feature_spec.ts b/packages/core/test/render3/Inherit_definition_feature_spec.ts index 39be343916..f0ebacc972 100644 --- a/packages/core/test/render3/Inherit_definition_feature_spec.ts +++ b/packages/core/test/render3/Inherit_definition_feature_spec.ts @@ -397,7 +397,7 @@ describe('InheritDefinitionFeature', () => { const subDef = SubDirective.ngDirectiveDef as DirectiveDef; - subDef.contentQueries !(); + subDef.contentQueries !(0); expect(log).toEqual(['super', 'sub']); }); diff --git a/packages/core/test/render3/change_detection_spec.ts b/packages/core/test/render3/change_detection_spec.ts index 13889c08b0..bb0c3bedbb 100644 --- a/packages/core/test/render3/change_detection_spec.ts +++ b/packages/core/test/render3/change_detection_spec.ts @@ -11,9 +11,8 @@ import {withBody} from '@angular/private/testing'; import {ChangeDetectionStrategy, ChangeDetectorRef, DoCheck, RendererType2} from '../../src/core'; import {getRenderedText, whenRendered} from '../../src/render3/component'; -import {directiveInject} from '../../src/render3/di'; import {LifecycleHooksFeature, defineComponent, defineDirective, templateRefExtractor} from '../../src/render3/index'; -import {bind, container, containerRefreshEnd, containerRefreshStart, detectChanges, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, listener, markDirty, reference, text, template, textBinding, tick} from '../../src/render3/instructions'; +import {bind, container, containerRefreshEnd, containerRefreshStart, detectChanges, directiveInject, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, listener, markDirty, reference, text, template, textBinding, tick} from '../../src/render3/instructions'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {RElement, Renderer3, RendererFactory3} from '../../src/render3/interfaces/renderer'; diff --git a/packages/core/test/render3/common_integration_spec.ts b/packages/core/test/render3/common_integration_spec.ts index 367a3892a3..55a5c5495e 100644 --- a/packages/core/test/render3/common_integration_spec.ts +++ b/packages/core/test/render3/common_integration_spec.ts @@ -10,8 +10,9 @@ import {NgForOfContext} from '@angular/common'; import {ElementRef, TemplateRef} from '@angular/core'; import {AttributeMarker, defineComponent, templateRefExtractor} from '../../src/render3/index'; -import {bind, template, elementEnd, elementProperty, elementStart, getCurrentView, interpolation1, interpolation2, interpolation3, interpolationV, listener, load, nextContext, restoreView, text, textBinding, elementContainerStart, elementContainerEnd, reference} from '../../src/render3/instructions'; +import {bind, template, elementEnd, elementProperty, elementStart, interpolation1, interpolation2, interpolation3, interpolationV, listener, load, nextContext, text, textBinding, elementContainerStart, elementContainerEnd, reference} from '../../src/render3/instructions'; import {RenderFlags} from '../../src/render3/interfaces/definition'; +import {getCurrentView, restoreView} from '../../src/render3/state'; import {NgForOf, NgIf, NgTemplateOutlet} from './common_with_def'; import {ComponentFixture} from './render_util'; diff --git a/packages/core/test/render3/compiler_canonical/component_directives_spec.ts b/packages/core/test/render3/compiler_canonical/component_directives_spec.ts index ec73b8b15f..9e3fcbadcf 100644 --- a/packages/core/test/render3/compiler_canonical/component_directives_spec.ts +++ b/packages/core/test/render3/compiler_canonical/component_directives_spec.ts @@ -31,7 +31,7 @@ describe('components & directives', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: ChildComponent, selectors: [['child']], - factory: function ChildComponent_Factory() { return new ChildComponent(); }, + factory: function ChildComponent_Factory(t) { return new (t || ChildComponent)(); }, consts: 1, vars: 0, template: function ChildComponent_Template(rf: $RenderFlags$, ctx: $ChildComponent$) { @@ -52,7 +52,7 @@ describe('components & directives', () => { static ngDirectiveDef = $r3$.ɵdefineDirective({ type: SomeDirective, selectors: [['', 'some-directive', '']], - factory: () => new SomeDirective(), + factory: function SomeDirective_Factory(t) { return new (t || SomeDirective)(); }, }); // /NORMATIVE } @@ -68,7 +68,7 @@ describe('components & directives', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, selectors: [['my-component']], - factory: () => new MyComponent(), + factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }, consts: 2, vars: 0, template: function(rf: $RenderFlags$, ctx: $MyComponent$) { @@ -101,7 +101,7 @@ describe('components & directives', () => { static ngDirectiveDef = $r3$.ɵdefineDirective({ type: HostBindingDir, selectors: [['', 'hostBindingDir', '']], - factory: function HostBindingDir_Factory() { return new HostBindingDir(); }, + factory: function HostBindingDir_Factory(t) { return new (t || HostBindingDir)(); }, hostVars: 1, hostBindings: function HostBindingDir_HostBindings(dirIndex: $number$, elIndex: $number$) { $r3$.ɵelementProperty( @@ -123,7 +123,7 @@ describe('components & directives', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyApp, selectors: [['my-app']], - factory: function MyApp_Factory() { return new MyApp(); }, + factory: function MyApp_Factory(t) { return new (t || MyApp)(); }, consts: 1, vars: 0, template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) { @@ -175,7 +175,7 @@ describe('components & directives', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyApp, selectors: [['my-app']], - factory: function MyApp_Factory() { return new MyApp(); }, + factory: function MyApp_Factory(t) { return new (t || MyApp)(); }, consts: 2, vars: 0, template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) { @@ -205,7 +205,7 @@ describe('components & directives', () => { static ngDirectiveDef = $r3$.ɵdefineDirective({ selectors: [['', 'hostAttributeDir', '']], type: HostAttributeDir, - factory: function HostAttributeDir_Factory() { return new HostAttributeDir(); }, + factory: function HostAttributeDir_Factory(t) { return new (t || HostAttributeDir)(); }, attributes: ['role', 'listbox'] }); // /NORMATIVE @@ -223,7 +223,7 @@ describe('components & directives', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyApp, selectors: [['my-app']], - factory: function MyApp_Factory() { return new MyApp(); }, + factory: function MyApp_Factory(t) { return new (t || MyApp)(); }, consts: 1, vars: 0, template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) { @@ -252,7 +252,7 @@ describe('components & directives', () => { static ngDirectiveDef = $r3$.ɵdefineDirective({ type: HostBindingDir, selectors: [['', 'hostBindingDir', '']], - factory: function HostBindingDir_Factory() { return new HostBindingDir(); }, + factory: function HostBindingDir_Factory(t) { return new (t || HostBindingDir)(); }, hostVars: 1, hostBindings: function HostBindingDir_HostBindings(dirIndex: $number$, elIndex: $number$) { $r3$.ɵelementAttribute( @@ -274,7 +274,7 @@ describe('components & directives', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyApp, selectors: [['my-app']], - factory: function MyApp_Factory() { return new MyApp(); }, + factory: function MyApp_Factory(t) { return new (t || MyApp)(); }, consts: 1, vars: 0, template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) { @@ -311,7 +311,7 @@ describe('components & directives', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyComp, selectors: [['my-comp']], - factory: function MyComp_Factory() { return new MyComp(); }, + factory: function MyComp_Factory(t) { return new (t || MyComp)(); }, consts: 1, vars: 1, template: function MyComp_Template(rf: $RenderFlags$, ctx: $MyComp$) { @@ -340,7 +340,7 @@ describe('components & directives', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyApp, selectors: [['my-app']], - factory: function MyApp_Factory() { return new MyApp(); }, + factory: function MyApp_Factory(t) { return new (t || MyApp)(); }, consts: 1, vars: 1, template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) { @@ -388,7 +388,9 @@ describe('components & directives', () => { static ngDirectiveDef = $r3$.ɵdefineDirective({ type: IfDirective, selectors: [['', 'if', '']], - factory: () => new IfDirective($r3$.ɵdirectiveInject(TemplateRef as any)), + factory: function IfDirective_Factory(t) { + return new (t || IfDirective)($r3$.ɵdirectiveInject(TemplateRef as any)); + }, }); // /NORMATIVE } @@ -406,7 +408,7 @@ describe('components & directives', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, selectors: [['my-component']], - factory: () => new MyComponent(), + factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }, consts: 3, vars: 0, template: function(rf: $RenderFlags$, ctx: $MyComponent$) { @@ -440,7 +442,7 @@ describe('components & directives', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyArrayComp, selectors: [['my-array-comp']], - factory: function MyArrayComp_Factory() { return new MyArrayComp(); }, + factory: function MyArrayComp_Factory(t) { return new (t || MyArrayComp)(); }, consts: 1, vars: 2, template: function MyArrayComp_Template(rf: $RenderFlags$, ctx: $MyArrayComp$) { @@ -473,7 +475,7 @@ describe('components & directives', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyApp, selectors: [['my-app']], - factory: function MyApp_Factory() { return new MyApp(); }, + factory: function MyApp_Factory(t) { return new (t || MyApp)(); }, consts: 1, vars: 0, template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) { @@ -519,7 +521,7 @@ describe('components & directives', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyApp, selectors: [['my-app']], - factory: function MyApp_Factory() { return new MyApp(); }, + factory: function MyApp_Factory(t) { return new (t || MyApp)(); }, consts: 1, vars: 2, template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) { @@ -555,7 +557,7 @@ describe('components & directives', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyComp, selectors: [['my-comp']], - factory: function MyComp_Factory() { return new MyComp(); }, + factory: function MyComp_Factory(t) { return new (t || MyComp)(); }, consts: 1, vars: 1, template: function MyComp_Template(rf: $RenderFlags$, ctx: $MyComp$) { @@ -589,7 +591,7 @@ describe('components & directives', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyApp, selectors: [['my-app']], - factory: function MyApp_Factory() { return new MyApp(); }, + factory: function MyApp_Factory(t) { return new (t || MyApp)(); }, consts: 1, vars: 2, template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) { @@ -634,7 +636,7 @@ describe('components & directives', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyApp, selectors: [['my-app']], - factory: function MyApp_Factory() { return new MyApp(); }, + factory: function MyApp_Factory(t) { return new (t || MyApp)(); }, consts: 1, vars: 3, template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) { @@ -684,7 +686,7 @@ describe('components & directives', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyComp, selectors: [['my-comp']], - factory: function MyComp_Factory() { return new MyComp(); }, + factory: function MyComp_Factory(t) { return new (t || MyComp)(); }, consts: 12, vars: 12, template: function MyComp_Template(rf: $RenderFlags$, ctx: $MyComp$) { @@ -749,7 +751,7 @@ describe('components & directives', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyApp, selectors: [['my-app']], - factory: function MyApp_Factory() { return new MyApp(); }, + factory: function MyApp_Factory(t) { return new (t || MyApp)(); }, consts: 1, vars: 11, template: function MyApp_Template(rf: $RenderFlags$, c: $any$) { @@ -793,7 +795,7 @@ describe('components & directives', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: ObjectComp, selectors: [['object-comp']], - factory: function ObjectComp_Factory() { return new ObjectComp(); }, + factory: function ObjectComp_Factory(t) { return new (t || ObjectComp)(); }, consts: 4, vars: 2, template: function ObjectComp_Template(rf: $RenderFlags$, ctx: $ObjectComp$) { @@ -831,7 +833,7 @@ describe('components & directives', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyApp, selectors: [['my-app']], - factory: function MyApp_Factory() { return new MyApp(); }, + factory: function MyApp_Factory(t) { return new (t || MyApp)(); }, consts: 1, vars: 3, template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) { @@ -874,7 +876,7 @@ describe('components & directives', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: NestedComp, selectors: [['nested-comp']], - factory: function NestedComp_Factory() { return new NestedComp(); }, + factory: function NestedComp_Factory(t) { return new (t || NestedComp)(); }, consts: 6, vars: 3, template: function NestedComp_Template(rf: $RenderFlags$, ctx: $NestedComp$) { @@ -921,7 +923,7 @@ describe('components & directives', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyApp, selectors: [['my-app']], - factory: function MyApp_Factory() { return new MyApp(); }, + factory: function MyApp_Factory(t) { return new (t || MyApp)(); }, consts: 1, vars: 8, template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) { diff --git a/packages/core/test/render3/compiler_canonical/content_projection_spec.ts b/packages/core/test/render3/compiler_canonical/content_projection_spec.ts index 60b343d54f..246b3adfb3 100644 --- a/packages/core/test/render3/compiler_canonical/content_projection_spec.ts +++ b/packages/core/test/render3/compiler_canonical/content_projection_spec.ts @@ -24,7 +24,7 @@ describe('content projection', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: SimpleComponent, selectors: [['simple']], - factory: () => new SimpleComponent(), + factory: function SimpleComponent_Factory(t) { return new (t || SimpleComponent)(); }, consts: 1, vars: 0, template: function(rf: $RenderFlags$, ctx: $SimpleComponent$) { @@ -55,7 +55,7 @@ describe('content projection', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: ComplexComponent, selectors: [['complex']], - factory: () => new ComplexComponent(), + factory: function ComplexComponent_Factory(t) { return new (t || ComplexComponent)(); }, consts: 4, vars: 0, template: function(rf: $RenderFlags$, ctx: $ComplexComponent$) { @@ -80,7 +80,7 @@ describe('content projection', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyApp, selectors: [['my-app']], - factory: () => new MyApp(), + factory: function MyApp_Factory(t) { return new (t || MyApp)(); }, consts: 2, vars: 0, template: function(rf: $RenderFlags$, ctx: $MyApp$) { diff --git a/packages/core/test/render3/compiler_canonical/elements_spec.ts b/packages/core/test/render3/compiler_canonical/elements_spec.ts index a95809dfda..cc83d43664 100644 --- a/packages/core/test/render3/compiler_canonical/elements_spec.ts +++ b/packages/core/test/render3/compiler_canonical/elements_spec.ts @@ -34,7 +34,7 @@ describe('elements', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, selectors: [['my-component']], - factory: () => new MyComponent(), + factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }, consts: 5, vars: 0, template: function(rf: $RenderFlags$, ctx: $MyComponent$) { @@ -65,7 +65,7 @@ describe('elements', () => { static ngDirectiveDef = $r3$.ɵdefineDirective({ type: Dir, selectors: [['', 'dir', '']], - factory: function DirA_Factory() { return new Dir(); }, + factory: function DirA_Factory(t) { return new (t || Dir)(); }, exportAs: 'dir' }); } @@ -87,7 +87,7 @@ describe('elements', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: LocalRefComp, selectors: [['local-ref-comp']], - factory: function LocalRefComp_Factory() { return new LocalRefComp(); }, + factory: function LocalRefComp_Factory(t) { return new (t || LocalRefComp)(); }, consts: 4, vars: 2, template: function LocalRefComp_Template(rf: $RenderFlags$, ctx: $LocalRefComp$) { @@ -131,7 +131,7 @@ describe('elements', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: ListenerComp, selectors: [['listener-comp']], - factory: function ListenerComp_Factory() { return new ListenerComp(); }, + factory: function ListenerComp_Factory(t) { return new (t || ListenerComp)(); }, consts: 2, vars: 0, template: function ListenerComp_Template(rf: $RenderFlags$, ctx: $ListenerComp$) { @@ -188,7 +188,7 @@ describe('elements', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, selectors: [['my-component']], - factory: () => new MyComponent(), + factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }, consts: 5, vars: 0, template: function(rf: $RenderFlags$, ctx: $MyComponent$) { @@ -222,7 +222,7 @@ describe('elements', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, selectors: [['my-component']], - factory: function MyComponent_Factory() { return new MyComponent(); }, + factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }, consts: 1, vars: 1, template: function MyComponent_Template(rf: $RenderFlags$, ctx: $MyComponent$) { @@ -255,7 +255,7 @@ describe('elements', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, selectors: [['my-component']], - factory: function MyComponent_Factory() { return new MyComponent(); }, + factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }, consts: 1, vars: 1, template: function MyComponent_Template(rf: $RenderFlags$, ctx: $MyComponent$) { @@ -289,7 +289,7 @@ describe('elements', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, selectors: [['my-component']], - factory: function MyComponent_Factory() { return new MyComponent(); }, + factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }, consts: 1, vars: 0, template: function MyComponent_Template(rf: $RenderFlags$, ctx: $MyComponent$) { @@ -334,7 +334,7 @@ describe('elements', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, selectors: [['my-component']], - factory: function MyComponent_Factory() { return new MyComponent(); }, + factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }, consts: 1, vars: 0, template: function MyComponent_Template(rf: $RenderFlags$, ctx: $MyComponent$) { @@ -379,7 +379,7 @@ describe('elements', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, selectors: [['my-component']], - factory: function MyComponent_Factory() { return new MyComponent(); }, + factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }, consts: 1, vars: 1, template: function MyComponent_Template(rf: $RenderFlags$, ctx: $MyComponent$) { @@ -419,7 +419,7 @@ describe('elements', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: StyleComponent, selectors: [['style-comp']], - factory: function StyleComponent_Factory() { return new StyleComponent(); }, + factory: function StyleComponent_Factory(t) { return new (t || StyleComponent)(); }, consts: 1, vars: 0, template: function StyleComponent_Template(rf: $RenderFlags$, ctx: $StyleComponent$) { diff --git a/packages/core/test/render3/compiler_canonical/i18n_spec.ts b/packages/core/test/render3/compiler_canonical/i18n_spec.ts index c0fcc6022a..b912642430 100644 --- a/packages/core/test/render3/compiler_canonical/i18n_spec.ts +++ b/packages/core/test/render3/compiler_canonical/i18n_spec.ts @@ -25,7 +25,7 @@ describe('i18n', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyApp, selectors: [['my-app']], - factory: () => new MyApp(), + factory: function MyApp_Factory(t) { return new (t || MyApp)(); }, consts: 2, vars: 0, template: function(rf: $RenderFlags$, ctx: $MyApp$) { @@ -52,7 +52,7 @@ describe('i18n', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyApp, selectors: [['my-app']], - factory: () => new MyApp(), + factory: function MyApp_Factory(t) { return new (t || MyApp)(); }, consts: 2, vars: 1, template: function(rf: $RenderFlags$, ctx: $MyApp$) { @@ -81,7 +81,7 @@ describe('i18n', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyApp, selectors: [['my-app']], - factory: () => new MyApp(), + factory: function MyApp_Factory(t) { return new (t || MyApp)(); }, consts: 2, vars: 1, template: function(rf: $RenderFlags$, ctx: $MyApp$) { @@ -125,7 +125,7 @@ describe('i18n', () => { items: string[] = ['1', '2']; static ngComponentDef = $r3$.ɵdefineComponent({ type: MyApp, - factory: () => new MyApp(), + factory: function MyApp_Factory(t) { return new (t || MyApp)(); }, selectors: [['my-app']], consts: 2, vars: 1, diff --git a/packages/core/test/render3/compiler_canonical/injection_spec.ts b/packages/core/test/render3/compiler_canonical/injection_spec.ts index a67aec795c..0dadc8e9ec 100644 --- a/packages/core/test/render3/compiler_canonical/injection_spec.ts +++ b/packages/core/test/render3/compiler_canonical/injection_spec.ts @@ -8,6 +8,7 @@ import {Attribute, ChangeDetectorRef, Component, INJECTOR, Inject, InjectFlags, Injectable, Injector, SkipSelf, defineInjectable, inject} from '../../../src/core'; import * as $r3$ from '../../../src/core_render3_private_export'; +import {ProvidersFeature} from '../../../src/render3/features/providers_feature'; import {renderComponent, toHtml} from '../render_util'; @@ -31,8 +32,8 @@ describe('injection', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyComp, selectors: [['my-comp']], - factory: function MyComp_Factory() { - return new MyComp($r3$.ɵdirectiveInject(ChangeDetectorRef as any)); + factory: function MyComp_Factory(t) { + return new (t || MyComp)($r3$.ɵdirectiveInject(ChangeDetectorRef as any)); }, consts: 1, vars: 1, @@ -52,7 +53,7 @@ describe('injection', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyApp, selectors: [['my-app']], - factory: function MyApp_Factory() { return new MyApp(); }, + factory: function MyApp_Factory(t) { return new (t || MyApp)(); }, consts: 1, vars: 0, /** */ @@ -83,7 +84,9 @@ describe('injection', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyComp, selectors: [['my-comp']], - factory: function MyComp_Factory() { return new MyComp($r3$.ɵinjectAttribute('title')); }, + factory: function MyComp_Factory(t) { + return new (t || MyComp)($r3$.ɵinjectAttribute('title')); + }, consts: 1, vars: 1, template: function MyComp_Template(rf: $RenderFlags$, ctx: $MyComp$) { @@ -102,7 +105,7 @@ describe('injection', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyApp, selectors: [['my-app']], - factory: function MyApp_Factory() { return new MyApp(); }, + factory: function MyApp_Factory(t) { return new (t || MyApp)(); }, consts: 1, vars: 0, /** */ @@ -153,15 +156,14 @@ describe('injection', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyApp, selectors: [['my-app']], - factory: function MyApp_Factory() { - return new MyApp( + factory: function MyApp_Factory(t) { + return new (t || MyApp)( $r3$.ɵdirectiveInject(ServiceA), $r3$.ɵdirectiveInject(ServiceB), inject(INJECTOR)); }, consts: 0, vars: 0, template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) {}, - providers: [ServiceA], - viewProviders: [ServiceB], + features: [ProvidersFeature([ServiceA], [ServiceB])] }); } const e0_attrs = ['title', 'WORKS']; diff --git a/packages/core/test/render3/compiler_canonical/life_cycle_spec.ts b/packages/core/test/render3/compiler_canonical/life_cycle_spec.ts index b669bcc3d4..7361b9431f 100644 --- a/packages/core/test/render3/compiler_canonical/life_cycle_spec.ts +++ b/packages/core/test/render3/compiler_canonical/life_cycle_spec.ts @@ -45,7 +45,7 @@ describe('lifecycle hooks', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: LifecycleComp, selectors: [['lifecycle-comp']], - factory: function LifecycleComp_Factory() { return new LifecycleComp(); }, + factory: function LifecycleComp_Factory(t) { return new (t || LifecycleComp)(); }, consts: 0, vars: 0, template: function LifecycleComp_Template(rf: $RenderFlags$, ctx: $LifecycleComp$) {}, @@ -70,7 +70,9 @@ describe('lifecycle hooks', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: SimpleLayout, selectors: [['simple-layout']], - factory: function SimpleLayout_Factory() { return simpleLayout = new SimpleLayout(); }, + factory: function SimpleLayout_Factory(t) { + return simpleLayout = new (t || SimpleLayout)(); + }, consts: 2, vars: 2, template: function SimpleLayout_Template(rf: $RenderFlags$, ctx: $SimpleLayout$) { diff --git a/packages/core/test/render3/compiler_canonical/local_reference_spec.ts b/packages/core/test/render3/compiler_canonical/local_reference_spec.ts index 17e9898383..56ba752412 100644 --- a/packages/core/test/render3/compiler_canonical/local_reference_spec.ts +++ b/packages/core/test/render3/compiler_canonical/local_reference_spec.ts @@ -27,7 +27,7 @@ describe('local references', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, selectors: [['my-component']], - factory: () => new MyComponent, + factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }, consts: 3, vars: 1, template: function(rf: $RenderFlags$, ctx: $MyComponent$) { @@ -64,7 +64,7 @@ describe('local references', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, selectors: [['my-component']], - factory: () => new MyComponent, + factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }, consts: 3, vars: 1, template: function(rf: $RenderFlags$, ctx: $MyComponent$) { diff --git a/packages/core/test/render3/compiler_canonical/ng_module_spec.ts b/packages/core/test/render3/compiler_canonical/ng_module_spec.ts index b585b7707e..32bf7427f8 100644 --- a/packages/core/test/render3/compiler_canonical/ng_module_spec.ts +++ b/packages/core/test/render3/compiler_canonical/ng_module_spec.ts @@ -34,7 +34,7 @@ xdescribe('NgModule', () => { constructor(name: String) {} // NORMATIVE static ngInjectableDef = defineInjectable({ - factory: () => new Toast($r3$.ɵdirectiveInject(String)), + factory: function Toast_Factory() { return new Toast($r3$.ɵdirectiveInject(String)); }, }); // /NORMATIVE } @@ -53,7 +53,7 @@ xdescribe('NgModule', () => { constructor(toast: Toast) {} // NORMATIVE static ngInjectorDef = defineInjector({ - factory: () => new MyModule($r3$.ɵdirectiveInject(Toast)), + factory: function MyModule_Factory() { return new MyModule($r3$.ɵdirectiveInject(Toast)); }, provider: [ {provide: Toast, deps: [String]}, // If Toast has metadata generate this line Toast, // If Toast has no metadata generate this line. @@ -70,9 +70,11 @@ xdescribe('NgModule', () => { // NORMATIVE static ngInjectableDef = defineInjectable({ providedIn: MyModule, - factory: () => new BurntToast( - $r3$.ɵdirectiveInject(Toast, $core$.InjectFlags.Optional), - $r3$.ɵdirectiveInject(String)), + factory: function BurntToast_Factory() { + return new BurntToast( + $r3$.ɵdirectiveInject(Toast, $core$.InjectFlags.Optional), + $r3$.ɵdirectiveInject(String)); + }, }); // /NORMATIVE } diff --git a/packages/core/test/render3/compiler_canonical/pipes_spec.ts b/packages/core/test/render3/compiler_canonical/pipes_spec.ts index 7e06055cef..7d16d938e9 100644 --- a/packages/core/test/render3/compiler_canonical/pipes_spec.ts +++ b/packages/core/test/render3/compiler_canonical/pipes_spec.ts @@ -43,7 +43,7 @@ describe('pipes', () => { static ngPipeDef = $r3$.ɵdefinePipe({ name: 'myPipe', type: MyPipe, - factory: function MyPipe_Factory() { return new MyPipe(); }, + factory: function MyPipe_Factory(t) { return new (t || MyPipe)(); }, pure: false, }); // /NORMATIVE @@ -63,7 +63,7 @@ describe('pipes', () => { static ngPipeDef = $r3$.ɵdefinePipe({ name: 'myPurePipe', type: MyPurePipe, - factory: function MyPurePipe_Factory() { return new MyPurePipe(); }, + factory: function MyPurePipe_Factory(t) { return new (t || MyPurePipe)(); }, pure: true, }); // /NORMATIVE @@ -83,7 +83,7 @@ describe('pipes', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyApp, selectors: [['my-app']], - factory: function MyApp_Factory() { return new MyApp(); }, + factory: function MyApp_Factory(t) { return new (t || MyApp)(); }, consts: 3, vars: 7, template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) { @@ -146,9 +146,11 @@ describe('pipes', () => { static ngDirectiveDef = $r3$.ɵdefineDirective({ type: OneTimeIf, selectors: [['', 'oneTimeIf', '']], - factory: () => new OneTimeIf( - $r3$.ɵdirectiveInject(ViewContainerRef as any), - $r3$.ɵdirectiveInject(TemplateRef as any)), + factory: function OneTimeIf_Factory(t) { + return new (t || OneTimeIf)( + $r3$.ɵdirectiveInject(ViewContainerRef as any), + $r3$.ɵdirectiveInject(TemplateRef as any)); + }, inputs: {oneTimeIf: 'oneTimeIf'} }); // /NORMATIVE @@ -181,7 +183,7 @@ describe('pipes', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyApp, selectors: [['my-app']], - factory: function MyApp_Factory() { return new MyApp(); }, + factory: function MyApp_Factory(t) { return new (t || MyApp)(); }, consts: 5, vars: 9, template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) { diff --git a/packages/core/test/render3/compiler_canonical/query_spec.ts b/packages/core/test/render3/compiler_canonical/query_spec.ts index 6af35bf765..f30b6df537 100644 --- a/packages/core/test/render3/compiler_canonical/query_spec.ts +++ b/packages/core/test/render3/compiler_canonical/query_spec.ts @@ -25,8 +25,7 @@ describe('queries', () => { static ngDirectiveDef = $r3$.ɵdefineDirective({ type: SomeDirective, selectors: [['', 'someDir', '']], - factory: function SomeDirective_Factory() { return someDir = new SomeDirective(); }, - features: [$r3$.ɵPublicFeature] + factory: function SomeDirective_Factory(t) { return someDir = new (t || SomeDirective)(); } }); } @@ -53,7 +52,7 @@ describe('queries', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: ViewQueryComponent, selectors: [['view-query-component']], - factory: function ViewQueryComponent_Factory() { return new ViewQueryComponent(); }, + factory: function ViewQueryComponent_Factory(t) { return new (t || ViewQueryComponent)(); }, consts: 3, vars: 0, template: function ViewQueryComponent_Template( @@ -111,12 +110,14 @@ describe('queries', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: ContentQueryComponent, selectors: [['content-query-component']], - factory: function ContentQueryComponent_Factory() { return new ContentQueryComponent(); }, + factory: function ContentQueryComponent_Factory(t) { + return new (t || ContentQueryComponent)(); + }, consts: 2, vars: 0, - contentQueries: function ContentQueryComponent_ContentQueries() { - $r3$.ɵregisterContentQuery($r3$.ɵquery(null, SomeDirective, false)); - $r3$.ɵregisterContentQuery($r3$.ɵquery(null, SomeDirective, false)); + contentQueries: function ContentQueryComponent_ContentQueries(dirIndex: $number$) { + $r3$.ɵregisterContentQuery($r3$.ɵquery(null, SomeDirective, false), dirIndex); + $r3$.ɵregisterContentQuery($r3$.ɵquery(null, SomeDirective, false), dirIndex); }, contentQueriesRefresh: function ContentQueryComponent_ContentQueriesRefresh( dirIndex: $number$, queryStartIndex: $number$) { @@ -155,7 +156,7 @@ describe('queries', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyApp, selectors: [['my-app']], - factory: function MyApp_Factory() { return new MyApp(); }, + factory: function MyApp_Factory(t) { return new (t || MyApp)(); }, consts: 2, vars: 0, template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) { diff --git a/packages/core/test/render3/compiler_canonical/sanitize_spec.ts b/packages/core/test/render3/compiler_canonical/sanitize_spec.ts index 4ce6440bde..314cb9678b 100644 --- a/packages/core/test/render3/compiler_canonical/sanitize_spec.ts +++ b/packages/core/test/render3/compiler_canonical/sanitize_spec.ts @@ -40,7 +40,7 @@ describe('compiler sanitization', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, selectors: [['my-component']], - factory: function MyComponent_Factory() { return new MyComponent(); }, + factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }, consts: 2, vars: 4, template: function MyComponent_Template(rf: $RenderFlags$, ctx: $MyComponent$) { diff --git a/packages/core/test/render3/compiler_canonical/small_app_spec.ts b/packages/core/test/render3/compiler_canonical/small_app_spec.ts index 94535dc182..683069b836 100644 --- a/packages/core/test/render3/compiler_canonical/small_app_spec.ts +++ b/packages/core/test/render3/compiler_canonical/small_app_spec.ts @@ -74,8 +74,8 @@ class ToDoAppComponent { static ngComponentDef = r3.defineComponent({ type: ToDoAppComponent, selectors: [['todo-app']], - factory: function ToDoAppComponent_Factory() { - return new ToDoAppComponent(r3.directiveInject(AppState)); + factory: function ToDoAppComponent_Factory(t) { + return new (t || ToDoAppComponent)(r3.directiveInject(AppState)); }, consts: 6, vars: 1, @@ -135,7 +135,7 @@ class ToDoItemComponent { static ngComponentDef = r3.defineComponent({ type: ToDoItemComponent, selectors: [['todo']], - factory: function ToDoItemComponent_Factory() { return new ToDoItemComponent(); }, + factory: function ToDoItemComponent_Factory(t) { return new (t || ToDoItemComponent)(); }, consts: 6, vars: 2, template: function ToDoItemComponent_Template(rf: $RenderFlags$, ctx: ToDoItemComponent) { @@ -174,7 +174,7 @@ const e1_attrs = ['type', 'checkbox']; class ToDoAppModule { // NORMATIVE static ngInjectorDef = defineInjector({ - factory: () => new ToDoAppModule(), + factory: function ToDoAppModule_Factory() { return new ToDoAppModule(); }, providers: [AppState], }); // /NORMATIVE diff --git a/packages/core/test/render3/compiler_canonical/template_variables_spec.ts b/packages/core/test/render3/compiler_canonical/template_variables_spec.ts index b0630711e0..81b522260b 100644 --- a/packages/core/test/render3/compiler_canonical/template_variables_spec.ts +++ b/packages/core/test/render3/compiler_canonical/template_variables_spec.ts @@ -69,8 +69,8 @@ describe('template variables', () => { static ngDirectiveDef = $r3$.ɵdefineDirective({ type: ForOfDirective, selectors: [['', 'forOf', '']], - factory: function ForOfDirective_Factory() { - return new ForOfDirective( + factory: function ForOfDirective_Factory(t) { + return new (t || ForOfDirective)( $r3$.ɵdirectiveInject(ViewContainerRef as any), $r3$.ɵdirectiveInject(TemplateRef as any)); }, @@ -111,7 +111,7 @@ describe('template variables', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, selectors: [['my-component']], - factory: function MyComponent_Factory() { return new MyComponent(); }, + factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }, consts: 2, vars: 1, template: function MyComponent_Template(rf: $RenderFlags$, ctx: $MyComponent$) { @@ -205,7 +205,7 @@ describe('template variables', () => { static ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, selectors: [['my-component']], - factory: function MyComponent_Factory() { return new MyComponent(); }, + factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }, consts: 2, vars: 1, template: function MyComponent_Template(rf: $RenderFlags$, ctx: $MyComponent$) { diff --git a/packages/core/test/render3/component_spec.ts b/packages/core/test/render3/component_spec.ts index 86b131b997..d04a78e65d 100644 --- a/packages/core/test/render3/component_spec.ts +++ b/packages/core/test/render3/component_spec.ts @@ -6,13 +6,13 @@ * found in the LICENSE file at https://angular.io/license */ - -import {DoCheck, Input, TemplateRef, ViewContainerRef, ViewEncapsulation, createInjector, defineInjectable, defineInjector} from '../../src/core'; +import {Component, ElementRef, InjectFlags, Injectable, InjectionToken, InjectorType, Provider, ViewEncapsulation, createInjector, defineInjectable, defineInjector, inject} from '../../src/core'; +import {forwardRef} from '../../src/di/forward_ref'; import {getRenderedText} from '../../src/render3/component'; -import {AttributeMarker, ComponentFactory, LifecycleHooksFeature, defineComponent, directiveInject, markDirty, template} from '../../src/render3/index'; -import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, nextContext, text, textBinding, tick} from '../../src/render3/instructions'; -import {ComponentDef, DirectiveDef, RenderFlags} from '../../src/render3/interfaces/definition'; -import {createRendererType2} from '../../src/view/index'; + +import {AttributeMarker, ComponentFactory, LifecycleHooksFeature, defineComponent, defineDirective, directiveInject, markDirty, template, ProvidersFeature, NgModuleType} from '../../src/render3/index'; +import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, nextContext, text, textBinding, tick, projectionDef, projection} from '../../src/render3/instructions'; +import {ComponentDef, RenderFlags} from '../../src/render3/interfaces/definition'; import {NgIf} from './common_with_def'; import {getRendererFactory2} from './imported_renderer2'; @@ -562,3 +562,1134 @@ describe('recursive components', () => { }); }); + +describe('providers', () => { + describe('should support all types of Provider:', () => { + abstract class Greeter { abstract greet: string; } + + const GREETER = new InjectionToken('greeter'); + + class GreeterClass implements Greeter { + greet = 'Class'; + } + + class GreeterDeps implements Greeter { + constructor(public greet: string) {} + } + + class GreeterBuiltInDeps implements Greeter { + public greet: string; + constructor(private message: string, private elementRef: ElementRef) { + this.greet = this.message + ' from ' + this.elementRef.nativeElement.tagName; + } + } + + class GreeterProvider { + provide() { return 'Provided'; } + } + + @Injectable() + class GreeterInj implements Greeter { + public greet: string; + constructor(private provider: GreeterProvider) { this.greet = this.provider.provide(); } + + static ngInjectableDef = + defineInjectable({factory: () => new GreeterInj(inject(GreeterProvider as any))}); + } + + it('TypeProvider', () => { + expectProvidersScenario({ + parent: { + providers: [GreeterClass], + componentAssertion: + () => { expect(directiveInject(GreeterClass).greet).toEqual('Class'); } + } + }); + }); + + it('ValueProvider', () => { + expectProvidersScenario({ + parent: { + providers: [{provide: GREETER, useValue: {greet: 'Value'}}], + componentAssertion: () => { expect(directiveInject(GREETER).greet).toEqual('Value'); } + } + }); + }); + + it('ClassProvider', () => { + expectProvidersScenario({ + parent: { + providers: [{provide: GREETER, useClass: GreeterClass}], + componentAssertion: () => { expect(directiveInject(GREETER).greet).toEqual('Class'); } + } + }); + }); + + it('ExistingProvider', () => { + expectProvidersScenario({ + parent: { + providers: [GreeterClass, {provide: GREETER, useExisting: GreeterClass}], + componentAssertion: () => { expect(directiveInject(GREETER).greet).toEqual('Class'); } + } + }); + }); + + it('FactoryProvider', () => { + expectProvidersScenario({ + parent: { + providers: [GreeterClass, {provide: GREETER, useFactory: () => new GreeterClass()}], + componentAssertion: () => { expect(directiveInject(GREETER).greet).toEqual('Class'); } + } + }); + }); + + const MESSAGE = new InjectionToken('message'); + + it('ClassProvider with deps', () => { + expectProvidersScenario({ + parent: { + providers: [ + {provide: MESSAGE, useValue: 'Message'}, + {provide: GREETER, useClass: GreeterDeps, deps: [MESSAGE]} + ], + componentAssertion: () => { expect(directiveInject(GREETER).greet).toEqual('Message'); } + } + }); + }); + + it('ClassProvider with built-in deps', () => { + expectProvidersScenario({ + parent: { + providers: [ + {provide: MESSAGE, useValue: 'Message'}, + {provide: GREETER, useClass: GreeterBuiltInDeps, deps: [MESSAGE, ElementRef]} + ], + componentAssertion: + () => { expect(directiveInject(GREETER).greet).toEqual('Message from PARENT'); } + } + }); + }); + + it('FactoryProvider with deps', () => { + expectProvidersScenario({ + parent: { + providers: [ + {provide: MESSAGE, useValue: 'Message'}, + {provide: GREETER, useFactory: (msg: string) => new GreeterDeps(msg), deps: [MESSAGE]} + ], + componentAssertion: () => { expect(directiveInject(GREETER).greet).toEqual('Message'); } + } + }); + }); + + it('FactoryProvider with built-in deps', () => { + expectProvidersScenario({ + parent: { + providers: [ + {provide: MESSAGE, useValue: 'Message'}, { + provide: GREETER, + useFactory: (msg: string, elementRef: ElementRef) => + new GreeterBuiltInDeps(msg, elementRef), + deps: [MESSAGE, ElementRef] + } + ], + componentAssertion: + () => { expect(directiveInject(GREETER).greet).toEqual('Message from PARENT'); } + } + }); + }); + + it('ClassProvider with injectable', () => { + expectProvidersScenario({ + parent: { + providers: [GreeterProvider, {provide: GREETER, useClass: GreeterInj}], + componentAssertion: () => { expect(directiveInject(GREETER).greet).toEqual('Provided'); } + } + }); + }); + + it('forwardRef', (done) => { + setTimeout(() => { + expectProvidersScenario({ + parent: { + providers: [forwardRef(() => ForLater)], + componentAssertion: + () => { expect(directiveInject(ForLater) instanceof ForLater).toBeTruthy(); } + } + }); + done(); + }, 0); + + class ForLater {} + }); + }); + + /* + * All tests below assume this structure: + * ``` + * + * <#VIEW#> + * + * + * + * + * + * + * ``` + */ + + describe('override rules:', () => { + it('directiveProviders should override providers', () => { + expectProvidersScenario({ + parent: { + providers: [{provide: String, useValue: 'Message 1'}], + directiveProviders: [{provide: String, useValue: 'Message 2'}], + componentAssertion: () => { expect(directiveInject(String)).toEqual('Message 2'); } + } + }); + }); + + it('viewProviders should override providers', () => { + expectProvidersScenario({ + parent: { + providers: [{provide: String, useValue: 'Message 1'}], + viewProviders: [{provide: String, useValue: 'Message 2'}], + componentAssertion: () => { expect(directiveInject(String)).toEqual('Message 2'); } + } + }); + }); + + it('viewProviders should override directiveProviders', () => { + expectProvidersScenario({ + parent: { + directiveProviders: [{provide: String, useValue: 'Message 1'}], + viewProviders: [{provide: String, useValue: 'Message 2'}], + componentAssertion: () => { expect(directiveInject(String)).toEqual('Message 2'); } + } + }); + }); + + it('last declared directive should override other directives', () => { + expectProvidersScenario({ + parent: { + directive2Providers: [{provide: String, useValue: 'Message 1'}], + directiveProviders: [{provide: String, useValue: 'Message 2'}], + componentAssertion: () => { expect(directiveInject(String)).toEqual('Message 2'); } + } + }); + }); + + it('last provider should override previous one in component providers', () => { + expectProvidersScenario({ + parent: { + providers: + [{provide: String, useValue: 'Message 1'}, {provide: String, useValue: 'Message 2'}], + componentAssertion: () => { expect(directiveInject(String)).toEqual('Message 2'); } + } + }); + }); + + it('last provider should override previous one in component view providers', () => { + expectProvidersScenario({ + parent: { + viewProviders: + [{provide: String, useValue: 'Message 1'}, {provide: String, useValue: 'Message 2'}], + componentAssertion: () => { expect(directiveInject(String)).toEqual('Message 2'); } + } + }); + }); + + it('last provider should override previous one in directive providers', () => { + expectProvidersScenario({ + parent: { + directiveProviders: + [{provide: String, useValue: 'Message 1'}, {provide: String, useValue: 'Message 2'}], + componentAssertion: () => { expect(directiveInject(String)).toEqual('Message 2'); } + } + }); + }); + }); + + describe('single', () => { + class MyModule { + static ngInjectorDef = defineInjector( + {factory: () => new MyModule(), providers: [{provide: String, useValue: 'From module'}]}); + } + + describe('without directives', () => { + it('should work without providers nor viewProviders in component', () => { + expectProvidersScenario({ + parent: { + componentAssertion: () => { expect(directiveInject(String)).toEqual('From module'); }, + directiveAssertion: () => { expect(directiveInject(String)).toEqual('From module'); } + }, + viewChild: { + componentAssertion: () => { expect(directiveInject(String)).toEqual('From module'); }, + directiveAssertion: () => { expect(directiveInject(String)).toEqual('From module'); } + }, + contentChild: { + componentAssertion: () => { expect(directiveInject(String)).toEqual('From module'); }, + directiveAssertion: () => { expect(directiveInject(String)).toEqual('From module'); } + }, + ngModule: MyModule + }); + }); + + it('should work with only providers in component', () => { + expectProvidersScenario({ + parent: { + providers: [{provide: String, useValue: 'From providers'}], + componentAssertion: + () => { expect(directiveInject(String)).toEqual('From providers'); }, + directiveAssertion: () => { expect(directiveInject(String)).toEqual('From providers'); } + }, + viewChild: { + componentAssertion: + () => { expect(directiveInject(String)).toEqual('From providers'); }, + directiveAssertion: () => { expect(directiveInject(String)).toEqual('From providers'); } + }, + contentChild: { + componentAssertion: + () => { expect(directiveInject(String)).toEqual('From providers'); }, + directiveAssertion: () => { expect(directiveInject(String)).toEqual('From providers'); } + }, + ngModule: MyModule + }); + }); + + it('should work with only viewProviders in component', () => { + expectProvidersScenario({ + parent: { + viewProviders: [{provide: String, useValue: 'From viewProviders'}], + componentAssertion: + () => { expect(directiveInject(String)).toEqual('From viewProviders'); }, + directiveAssertion: () => { expect(directiveInject(String)).toEqual('From module'); } + }, + viewChild: { + componentAssertion: + () => { expect(directiveInject(String)).toEqual('From viewProviders'); }, + directiveAssertion: + () => { expect(directiveInject(String)).toEqual('From viewProviders'); } + }, + contentChild: { + componentAssertion: () => { expect(directiveInject(String)).toEqual('From module'); }, + directiveAssertion: () => { expect(directiveInject(String)).toEqual('From module'); } + }, + ngModule: MyModule + }); + }); + + it('should work with both providers and viewProviders in component', () => { + expectProvidersScenario({ + parent: { + providers: [{provide: String, useValue: 'From providers'}], + viewProviders: [{provide: String, useValue: 'From viewProviders'}], + componentAssertion: + () => { expect(directiveInject(String)).toEqual('From viewProviders'); }, + directiveAssertion: () => { expect(directiveInject(String)).toEqual('From providers'); } + }, + viewChild: { + componentAssertion: + () => { expect(directiveInject(String)).toEqual('From viewProviders'); }, + directiveAssertion: + () => { expect(directiveInject(String)).toEqual('From viewProviders'); } + }, + contentChild: { + componentAssertion: + () => { expect(directiveInject(String)).toEqual('From providers'); }, + directiveAssertion: () => { expect(directiveInject(String)).toEqual('From providers'); } + }, + ngModule: MyModule + }); + }); + }); + + describe('with directives (order in ngComponentDef.directives matters)', () => { + it('should work without providers nor viewProviders in component', () => { + expectProvidersScenario({ + parent: { + directiveProviders: [{provide: String, useValue: 'From directive'}], + directive2Providers: [{provide: String, useValue: 'Never'}], + componentAssertion: + () => { expect(directiveInject(String)).toEqual('From directive'); }, + directiveAssertion: () => { expect(directiveInject(String)).toEqual('From directive'); } + }, + viewChild: { + componentAssertion: + () => { expect(directiveInject(String)).toEqual('From directive'); }, + directiveAssertion: () => { expect(directiveInject(String)).toEqual('From directive'); } + }, + contentChild: { + componentAssertion: + () => { expect(directiveInject(String)).toEqual('From directive'); }, + directiveAssertion: () => { expect(directiveInject(String)).toEqual('From directive'); } + }, + ngModule: MyModule + }); + }); + + it('should work with only providers in component', () => { + expectProvidersScenario({ + parent: { + providers: [{provide: String, useValue: 'From providers'}], + directiveProviders: [{provide: String, useValue: 'From directive'}], + directive2Providers: [{provide: String, useValue: 'Never'}], + componentAssertion: + () => { expect(directiveInject(String)).toEqual('From directive'); }, + directiveAssertion: () => { expect(directiveInject(String)).toEqual('From directive'); } + }, + viewChild: { + componentAssertion: + () => { expect(directiveInject(String)).toEqual('From directive'); }, + directiveAssertion: () => { expect(directiveInject(String)).toEqual('From directive'); } + }, + contentChild: { + componentAssertion: + () => { expect(directiveInject(String)).toEqual('From directive'); }, + directiveAssertion: () => { expect(directiveInject(String)).toEqual('From directive'); } + }, + ngModule: MyModule + }); + }); + + it('should work with only viewProviders in component', () => { + expectProvidersScenario({ + parent: { + viewProviders: [{provide: String, useValue: 'From viewProviders'}], + directiveProviders: [{provide: String, useValue: 'From directive'}], + directive2Providers: [{provide: String, useValue: 'Never'}], + componentAssertion: + () => { expect(directiveInject(String)).toEqual('From viewProviders'); }, + directiveAssertion: () => { expect(directiveInject(String)).toEqual('From directive'); } + }, + viewChild: { + componentAssertion: + () => { expect(directiveInject(String)).toEqual('From viewProviders'); }, + directiveAssertion: + () => { expect(directiveInject(String)).toEqual('From viewProviders'); } + }, + contentChild: { + componentAssertion: + () => { expect(directiveInject(String)).toEqual('From directive'); }, + directiveAssertion: () => { expect(directiveInject(String)).toEqual('From directive'); } + }, + ngModule: MyModule + }); + }); + + it('should work with both providers and viewProviders in component', () => { + expectProvidersScenario({ + parent: { + providers: [{provide: String, useValue: 'From providers'}], + viewProviders: [{provide: String, useValue: 'From viewProviders'}], + directiveProviders: [{provide: String, useValue: 'From directive'}], + directive2Providers: [{provide: String, useValue: 'Never'}], + componentAssertion: + () => { expect(directiveInject(String)).toEqual('From viewProviders'); }, + directiveAssertion: () => { expect(directiveInject(String)).toEqual('From directive'); } + }, + viewChild: { + componentAssertion: + () => { expect(directiveInject(String)).toEqual('From viewProviders'); }, + directiveAssertion: + () => { expect(directiveInject(String)).toEqual('From viewProviders'); } + }, + contentChild: { + componentAssertion: + () => { expect(directiveInject(String)).toEqual('From directive'); }, + directiveAssertion: () => { expect(directiveInject(String)).toEqual('From directive'); } + }, + ngModule: MyModule + }); + }); + }); + }); + + describe('multi', () => { + class MyModule { + static ngInjectorDef = defineInjector({ + factory: () => new MyModule(), + providers: [{provide: String, useValue: 'From module', multi: true}] + }); + } + + describe('without directives', () => { + it('should work without providers nor viewProviders in component', () => { + expectProvidersScenario({ + parent: { + componentAssertion: () => { expect(directiveInject(String)).toEqual(['From module']); }, + directiveAssertion: () => { expect(directiveInject(String)).toEqual(['From module']); } + }, + viewChild: { + componentAssertion: () => { expect(directiveInject(String)).toEqual(['From module']); }, + directiveAssertion: () => { expect(directiveInject(String)).toEqual(['From module']); } + }, + contentChild: { + componentAssertion: () => { expect(directiveInject(String)).toEqual(['From module']); }, + directiveAssertion: () => { expect(directiveInject(String)).toEqual(['From module']); } + }, + ngModule: MyModule + }); + }); + + it('should work with only providers in component', () => { + expectProvidersScenario({ + parent: { + providers: [{provide: String, useValue: 'From providers', multi: true}], + componentAssertion: + () => { expect(directiveInject(String)).toEqual(['From providers']); }, + directiveAssertion: + () => { expect(directiveInject(String)).toEqual(['From providers']); } + }, + viewChild: { + componentAssertion: + () => { expect(directiveInject(String)).toEqual(['From providers']); }, + directiveAssertion: + () => { expect(directiveInject(String)).toEqual(['From providers']); } + }, + contentChild: { + componentAssertion: + () => { expect(directiveInject(String)).toEqual(['From providers']); }, + directiveAssertion: + () => { expect(directiveInject(String)).toEqual(['From providers']); } + }, + ngModule: MyModule + }); + }); + + it('should work with only viewProviders in component', () => { + expectProvidersScenario({ + parent: { + viewProviders: [{provide: String, useValue: 'From viewProviders', multi: true}], + componentAssertion: + () => { expect(directiveInject(String)).toEqual(['From viewProviders']); }, + directiveAssertion: () => { expect(directiveInject(String)).toEqual(['From module']); } + }, + viewChild: { + componentAssertion: + () => { expect(directiveInject(String)).toEqual(['From viewProviders']); }, + directiveAssertion: + () => { expect(directiveInject(String)).toEqual(['From viewProviders']); } + }, + contentChild: { + componentAssertion: () => { expect(directiveInject(String)).toEqual(['From module']); }, + directiveAssertion: () => { expect(directiveInject(String)).toEqual(['From module']); } + }, + ngModule: MyModule + }); + }); + + it('should work with both providers and viewProviders in component', () => { + expectProvidersScenario({ + parent: { + providers: [{provide: String, useValue: 'From providers', multi: true}], + viewProviders: [{provide: String, useValue: 'From viewProviders', multi: true}], + componentAssertion: () => { + expect(directiveInject(String)).toEqual(['From providers', 'From viewProviders']); + }, + directiveAssertion: + () => { expect(directiveInject(String)).toEqual(['From providers']); } + }, + viewChild: { + componentAssertion: () => { + expect(directiveInject(String)).toEqual(['From providers', 'From viewProviders']); + }, + directiveAssertion: () => { + expect(directiveInject(String)).toEqual(['From providers', 'From viewProviders']); + } + }, + contentChild: { + componentAssertion: + () => { expect(directiveInject(String)).toEqual(['From providers']); }, + directiveAssertion: + () => { expect(directiveInject(String)).toEqual(['From providers']); } + }, + ngModule: MyModule + }); + }); + }); + + describe('with directives (order in ngComponentDef.directives matters)', () => { + it('should work without providers nor viewProviders in component', () => { + expectProvidersScenario({ + parent: { + directiveProviders: [{provide: String, useValue: 'From directive 1', multi: true}], + directive2Providers: [{provide: String, useValue: 'From directive 2', multi: true}], + componentAssertion: () => { + expect(directiveInject(String)).toEqual(['From directive 2', 'From directive 1']); + }, + directiveAssertion: () => { + expect(directiveInject(String)).toEqual(['From directive 2', 'From directive 1']); + } + }, + viewChild: { + componentAssertion: () => { + expect(directiveInject(String)).toEqual(['From directive 2', 'From directive 1']); + }, + directiveAssertion: () => { + expect(directiveInject(String)).toEqual(['From directive 2', 'From directive 1']); + } + }, + contentChild: { + componentAssertion: () => { + expect(directiveInject(String)).toEqual(['From directive 2', 'From directive 1']); + }, + directiveAssertion: () => { + expect(directiveInject(String)).toEqual(['From directive 2', 'From directive 1']); + } + }, + ngModule: MyModule + }); + }); + + it('should work with only providers in component', () => { + expectProvidersScenario({ + parent: { + providers: [{provide: String, useValue: 'From providers', multi: true}], + directiveProviders: [{provide: String, useValue: 'From directive 1', multi: true}], + directive2Providers: [{provide: String, useValue: 'From directive 2', multi: true}], + componentAssertion: () => { + expect(directiveInject(String)).toEqual([ + 'From providers', 'From directive 2', 'From directive 1' + ]); + }, + directiveAssertion: () => { + expect(directiveInject(String)).toEqual([ + 'From providers', 'From directive 2', 'From directive 1' + ]); + } + }, + viewChild: { + componentAssertion: () => { + expect(directiveInject(String)).toEqual([ + 'From providers', 'From directive 2', 'From directive 1' + ]); + }, + directiveAssertion: () => { + expect(directiveInject(String)).toEqual([ + 'From providers', 'From directive 2', 'From directive 1' + ]); + } + }, + contentChild: { + componentAssertion: () => { + expect(directiveInject(String)).toEqual([ + 'From providers', 'From directive 2', 'From directive 1' + ]); + }, + directiveAssertion: () => { + expect(directiveInject(String)).toEqual([ + 'From providers', 'From directive 2', 'From directive 1' + ]); + } + }, + ngModule: MyModule + }); + }); + + it('should work with only viewProviders in component', () => { + expectProvidersScenario({ + parent: { + viewProviders: [{provide: String, useValue: 'From viewProviders', multi: true}], + directiveProviders: [{provide: String, useValue: 'From directive 1', multi: true}], + directive2Providers: [{provide: String, useValue: 'From directive 2', multi: true}], + componentAssertion: () => { + expect(directiveInject(String)).toEqual([ + 'From viewProviders', 'From directive 2', 'From directive 1' + ]); + }, + directiveAssertion: () => { + expect(directiveInject(String)).toEqual(['From directive 2', 'From directive 1']); + } + }, + viewChild: { + componentAssertion: () => { + expect(directiveInject(String)).toEqual([ + 'From viewProviders', 'From directive 2', 'From directive 1' + ]); + }, + directiveAssertion: () => { + expect(directiveInject(String)).toEqual([ + 'From viewProviders', 'From directive 2', 'From directive 1' + ]); + } + }, + contentChild: { + componentAssertion: () => { + expect(directiveInject(String)).toEqual(['From directive 2', 'From directive 1']); + }, + directiveAssertion: () => { + expect(directiveInject(String)).toEqual(['From directive 2', 'From directive 1']); + } + }, + ngModule: MyModule + }); + }); + + it('should work with both providers and viewProviders in component', () => { + expectProvidersScenario({ + parent: { + providers: [{provide: String, useValue: 'From providers', multi: true}], + viewProviders: [{provide: String, useValue: 'From viewProviders', multi: true}], + directiveProviders: [{provide: String, useValue: 'From directive 1', multi: true}], + directive2Providers: [{provide: String, useValue: 'From directive 2', multi: true}], + componentAssertion: () => { + expect(directiveInject(String)).toEqual([ + 'From providers', 'From viewProviders', 'From directive 2', 'From directive 1' + ]); + }, + directiveAssertion: () => { + expect(directiveInject(String)).toEqual([ + 'From providers', 'From directive 2', 'From directive 1' + ]); + } + }, + viewChild: { + componentAssertion: () => { + expect(directiveInject(String)).toEqual([ + 'From providers', 'From viewProviders', 'From directive 2', 'From directive 1' + ]); + }, + directiveAssertion: () => { + expect(directiveInject(String)).toEqual([ + 'From providers', 'From viewProviders', 'From directive 2', 'From directive 1' + ]); + } + }, + contentChild: { + componentAssertion: () => { + expect(directiveInject(String)).toEqual([ + 'From providers', 'From directive 2', 'From directive 1' + ]); + }, + directiveAssertion: () => { + expect(directiveInject(String)).toEqual([ + 'From providers', 'From directive 2', 'From directive 1' + ]); + } + }, + ngModule: MyModule + }); + }); + }); + }); + + describe('tree-shakable injectables', () => { + it('should work with root', () => { + @Injectable({providedIn: 'root'}) + class FooForRoot { + static ngInjectableDef = + defineInjectable({factory: () => new FooForRoot(), providedIn: 'root'}); + } + + expectProvidersScenario({ + parent: { + componentAssertion: + () => { expect(directiveInject(FooForRoot) instanceof FooForRoot).toBeTruthy(); } + } + }); + }); + + it('should work with a module', () => { + class MyModule { + static ngInjectorDef = defineInjector({ + factory: () => new MyModule(), + providers: [{provide: String, useValue: 'From module'}] + }); + } + + @Injectable({providedIn: MyModule}) + class FooForModule { + static ngInjectableDef = + defineInjectable({factory: () => new FooForModule(), providedIn: MyModule}); + } + + expectProvidersScenario({ + parent: { + componentAssertion: + () => { expect(directiveInject(FooForModule) instanceof FooForModule).toBeTruthy(); } + }, + ngModule: MyModule + }); + }); + }); + + describe('- embedded views', () => { + it('should have access to viewProviders of the host component', () => { + @Component({ + template: '{{s}}{{n}}', + }) + class Repeated { + constructor(private s: String, private n: Number) {} + + static ngComponentDef = defineComponent({ + type: Repeated, + selectors: [['repeated']], + factory: () => new Repeated(directiveInject(String), directiveInject(Number)), + consts: 2, + vars: 2, + template: function(fs: RenderFlags, ctx: Repeated) { + if (fs & RenderFlags.Create) { + text(0); + text(1); + } + if (fs & RenderFlags.Update) { + textBinding(0, bind(ctx.s)); + textBinding(1, bind(ctx.n)); + } + } + }); + } + + @Component({ + template: `
+ % for (let i = 0; i < 3; i++) { + + % } +
`, + providers: [{provide: Number, useValue: 1, multi: true}], + viewProviders: + [{provide: String, useValue: 'foo'}, {provide: Number, useValue: 2, multi: true}], + }) + class ComponentWithProviders { + static ngComponentDef = defineComponent({ + type: ComponentWithProviders, + selectors: [['component-with-providers']], + factory: () => new ComponentWithProviders(), + consts: 2, + vars: 0, + template: function(fs: RenderFlags, ctx: ComponentWithProviders) { + if (fs & RenderFlags.Create) { + elementStart(0, 'div'); + { container(1); } + elementEnd(); + } + if (fs & RenderFlags.Update) { + containerRefreshStart(1); + { + for (let i = 0; i < 3; i++) { + let rf1 = embeddedViewStart(1, 1, 0); + { + if (rf1 & RenderFlags.Create) { + element(0, 'repeated'); + } + } + embeddedViewEnd(); + } + } + containerRefreshEnd(); + } + }, + features: [ + ProvidersFeature( + [{provide: Number, useValue: 1, multi: true}], + [{provide: String, useValue: 'foo'}, {provide: Number, useValue: 2, multi: true}]), + ], + directives: [Repeated] + }); + } + + const fixture = new ComponentFixture(ComponentWithProviders); + expect(fixture.html) + .toEqual( + '
foo1,2foo1,2foo1,2
'); + }); + + it('should have access to viewProviders of the repeated component', () => { + @Component({ + template: '{{s}}{{n}}', + providers: [{provide: Number, useValue: 1, multi: true}], + viewProviders: + [{provide: String, useValue: 'bar'}, {provide: Number, useValue: 2, multi: true}] + }) + class Repeated { + constructor(private s: String, private n: Number) {} + + static ngComponentDef = defineComponent({ + type: Repeated, + selectors: [['repeated']], + factory: () => new Repeated(directiveInject(String), directiveInject(Number)), + consts: 2, + vars: 2, + template: function(fs: RenderFlags, ctx: Repeated) { + if (fs & RenderFlags.Create) { + text(0); + text(1); + } + if (fs & RenderFlags.Update) { + textBinding(0, bind(ctx.s)); + textBinding(1, bind(ctx.n)); + } + }, + features: [ + ProvidersFeature( + [{provide: Number, useValue: 1, multi: true}], + [{provide: String, useValue: 'bar'}, {provide: Number, useValue: 2, multi: true}]), + ], + }); + } + + @Component({ + template: `
+ % for (let i = 0; i < 3; i++) { + + % } +
`, + viewProviders: [{provide: toString, useValue: 'foo'}], + }) + class ComponentWithProviders { + static ngComponentDef = defineComponent({ + type: ComponentWithProviders, + selectors: [['component-with-providers']], + factory: () => new ComponentWithProviders(), + consts: 2, + vars: 0, + template: function(fs: RenderFlags, ctx: ComponentWithProviders) { + if (fs & RenderFlags.Create) { + elementStart(0, 'div'); + { container(1); } + elementEnd(); + } + if (fs & RenderFlags.Update) { + containerRefreshStart(1); + { + for (let i = 0; i < 3; i++) { + let rf1 = embeddedViewStart(1, 1, 0); + { + if (rf1 & RenderFlags.Create) { + element(0, 'repeated'); + } + } + embeddedViewEnd(); + } + } + containerRefreshEnd(); + } + }, + features: [ProvidersFeature([], [{provide: String, useValue: 'foo'}])], + directives: [Repeated] + }); + } + + const fixture = new ComponentFixture(ComponentWithProviders); + expect(fixture.html) + .toEqual( + '
bar1,2bar1,2bar1,2
'); + }); + }); + + describe('deps boundary:', () => { + it('the deps of a token declared in providers should not be resolved with tokens from viewProviders', + () => { + @Injectable() + class MyService { + constructor(public value: String) {} + + static ngInjectableDef = + defineInjectable({factory: () => new MyService(inject(String))}); + } + + expectProvidersScenario({ + parent: { + providers: [MyService, {provide: String, useValue: 'providers'}], + viewProviders: [{provide: String, useValue: 'viewProviders'}], + componentAssertion: () => { + expect(directiveInject(String)).toEqual('viewProviders'); + expect(directiveInject(MyService).value).toEqual('providers'); + } + } + }); + }); + + it('should make sure that parent service does not see overrides in child directives', () => { + class Greeter { + static ngInjectableDef = defineInjectable({factory: () => new Greeter(inject(String))}); + constructor(public greeting: String) {} + } + + expectProvidersScenario({ + parent: { + providers: [Greeter, {provide: String, useValue: 'parent'}], + }, + viewChild: { + providers: [{provide: String, useValue: 'view'}], + componentAssertion: + () => { expect(directiveInject(Greeter).greeting).toEqual('parent'); }, + }, + }); + }); + }); + + describe('injection flags', () => { + class MyModule { + static ngInjectorDef = defineInjector( + {factory: () => new MyModule(), providers: [{provide: String, useValue: 'Module'}]}); + } + it('should not fall through to ModuleInjector if flags limit the scope', () => { + expectProvidersScenario({ + ngModule: MyModule, + parent: { + componentAssertion: () => { + expect(directiveInject(String)).toEqual('Module'); + expect(directiveInject(String, InjectFlags.Optional | InjectFlags.Self)) + .toEqual(undefined as any); + expect(directiveInject(String, InjectFlags.Optional | InjectFlags.Host)) + .toEqual(undefined as any); + } + } + }); + }); + }); + +}); +interface ComponentTest { + providers?: Provider[]; + viewProviders?: Provider[]; + directiveProviders?: Provider[]; + directive2Providers?: Provider[]; + directiveAssertion?: () => void; + componentAssertion?: () => void; +} + +function expectProvidersScenario(defs: { + app?: ComponentTest, + parent?: ComponentTest, + viewChild?: ComponentTest, + contentChild?: ComponentTest, + ngModule?: InjectorType, +}): void { + function testComponentInjection(def: ComponentTest | undefined, instance: T): T { + if (def) { + def.componentAssertion && def.componentAssertion(); + } + return instance; + } + + function testDirectiveInjection(def: ComponentTest | undefined, instance: T): T { + if (def) { + def.directiveAssertion && def.directiveAssertion(); + } + return instance; + } + + class ViewChildComponent { + static ngComponentDef = defineComponent({ + type: ViewChildComponent, + selectors: [['view-child']], + consts: 1, + vars: 0, + factory: () => testComponentInjection(defs.viewChild, new ViewChildComponent()), + template: function(fs: RenderFlags, ctx: ViewChildComponent) { + if (fs & RenderFlags.Create) { + text(0, 'view-child'); + } + }, + features: defs.viewChild && + [ + ProvidersFeature(defs.viewChild.providers || [], defs.viewChild.viewProviders || []), + ], + }); + } + + class ViewChildDirective { + static ngComponentDef = defineDirective({ + type: ViewChildDirective, + selectors: [['view-child']], + factory: () => testDirectiveInjection(defs.viewChild, new ViewChildDirective()), + features: defs.viewChild && [ProvidersFeature(defs.viewChild.directiveProviders || [])], + }); + } + + class ContentChildComponent { + static ngComponentDef = defineComponent({ + type: ContentChildComponent, + selectors: [['content-child']], + consts: 1, + vars: 0, + factory: () => testComponentInjection(defs.contentChild, new ContentChildComponent()), + template: function(fs: RenderFlags, ctx: ParentComponent) { + if (fs & RenderFlags.Create) { + text(0, 'content-child'); + } + }, + features: defs.contentChild && + [ProvidersFeature( + defs.contentChild.providers || [], defs.contentChild.viewProviders || [])], + }); + } + + class ContentChildDirective { + static ngComponentDef = defineDirective({ + type: ContentChildDirective, + selectors: [['content-child']], + factory: () => testDirectiveInjection(defs.contentChild, new ContentChildDirective()), + features: defs.contentChild && [ProvidersFeature(defs.contentChild.directiveProviders || [])], + }); + } + + + class ParentComponent { + static ngComponentDef = defineComponent({ + type: ParentComponent, + selectors: [['parent']], + consts: 1, + vars: 0, + factory: () => testComponentInjection(defs.parent, new ParentComponent()), + template: function(fs: RenderFlags, ctx: ParentComponent) { + if (fs & RenderFlags.Create) { + element(0, 'view-child'); + } + }, + features: defs.parent && + [ProvidersFeature(defs.parent.providers || [], defs.parent.viewProviders || [])], + directives: [ViewChildComponent, ViewChildDirective] + }); + } + + class ParentDirective { + static ngComponentDef = defineDirective({ + type: ParentDirective, + selectors: [['parent']], + factory: () => testDirectiveInjection(defs.parent, new ParentDirective()), + features: defs.parent && [ProvidersFeature(defs.parent.directiveProviders || [])], + }); + } + + class ParentDirective2 { + static ngComponentDef = defineDirective({ + type: ParentDirective2, + selectors: [['parent']], + factory: () => testDirectiveInjection(defs.parent, new ParentDirective2()), + features: defs.parent && [ProvidersFeature(defs.parent.directive2Providers || [])], + }); + } + + + class App { + static ngComponentDef = defineComponent({ + type: App, + selectors: [['app']], + consts: 2, + vars: 0, + factory: () => testComponentInjection(defs.app, new App()), + template: function(fs: RenderFlags, ctx: App) { + if (fs & RenderFlags.Create) { + elementStart(0, 'parent'); + element(1, 'content-child'); + elementEnd(); + } + }, + features: + defs.app && [ProvidersFeature(defs.app.providers || [], defs.app.viewProviders || [])], + directives: [ + ParentComponent, ParentDirective2, ParentDirective, ContentChildComponent, + ContentChildDirective + ] + }); + } + + + const fixture = new ComponentFixture( + App, {injector: defs.ngModule ? createInjector(defs.ngModule) : undefined}); + expect(fixture.html).toEqual('view-child'); +} \ No newline at end of file diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index 35933507f3..2c1e29733b 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -10,14 +10,15 @@ import {Attribute, ChangeDetectorRef, ElementRef, Host, InjectFlags, Injector, O import {RenderFlags} from '@angular/core/src/render3/interfaces/definition'; import {defineComponent} from '../../src/render3/definition'; -import {bloomAdd, bloomHashBitOrFactory as bloomHash, getOrCreateNodeInjector, injectAttribute, injectorHasToken} from '../../src/render3/di'; -import {PublicFeature, defineDirective, directiveInject, elementProperty, load, templateRefExtractor} from '../../src/render3/index'; +import {bloomAdd, bloomHasToken, bloomHashBitOrFactory as bloomHash, getOrCreateNodeInjectorForNode} from '../../src/render3/di'; +import {defineDirective, elementProperty, load, templateRefExtractor} from '../../src/render3/index'; -import {bind, container, containerRefreshEnd, containerRefreshStart, createNodeAtIndex, createLViewData, createTView, element, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, projection, projectionDef, reference, template, text, textBinding, elementContainerStart, elementContainerEnd, _getViewData} from '../../src/render3/instructions'; +import {bind, container, containerRefreshEnd, containerRefreshStart, createNodeAtIndex, createLViewData, createTView, directiveInject, element, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, injectAttribute, interpolation2, projection, projectionDef, reference, template, text, textBinding, elementContainerStart, elementContainerEnd} from '../../src/render3/instructions'; import {isProceduralRenderer, RElement} from '../../src/render3/interfaces/renderer'; import {AttributeMarker, TNodeType} from '../../src/render3/interfaces/node'; import {getNativeByIndex} from '../../src/render3/util'; import {LViewFlags} from '../../src/render3/interfaces/view'; +import {getViewData, enterView, leaveView} from '../../src/render3/state'; import {ViewRef} from '../../src/render3/view_ref'; import {getRendererFactory2} from './imported_renderer2'; @@ -68,7 +69,6 @@ describe('di', () => { selectors: [['', 'dirB', '']], type: DirB, factory: () => new DirB(), - features: [PublicFeature], inputs: {value: 'value'} }); } @@ -78,12 +78,8 @@ describe('di', () => { it('should create directive with intra view dependencies', () => { class DirA { value: string = 'DirA'; - static ngDirectiveDef = defineDirective({ - type: DirA, - selectors: [['', 'dirA', '']], - factory: () => new DirA(), - features: [PublicFeature] - }); + static ngDirectiveDef = + defineDirective({type: DirA, selectors: [['', 'dirA', '']], factory: () => new DirA()}); } class DirC { @@ -246,12 +242,8 @@ describe('di', () => { value = 'DirA'; constructor() { log.push(this.value); } - static ngDirectiveDef = defineDirective({ - selectors: [['', 'dirA', '']], - type: DirA, - factory: () => new DirA(), - features: [PublicFeature] - }); + static ngDirectiveDef = + defineDirective({selectors: [['', 'dirA', '']], type: DirA, factory: () => new DirA()}); } class DirB { @@ -270,12 +262,8 @@ describe('di', () => { value = 'DirC'; constructor() { log.push(this.value); } - static ngDirectiveDef = defineDirective({ - selectors: [['', 'dirC', '']], - type: DirC, - factory: () => new DirC(), - features: [PublicFeature] - }); + static ngDirectiveDef = + defineDirective({selectors: [['', 'dirC', '']], type: DirC, factory: () => new DirC()}); } /**
*/ @@ -310,8 +298,7 @@ describe('di', () => { static ngDirectiveDef = defineDirective({ selectors: [['', 'dirA', '']], type: DirA, - factory: () => new DirA(directiveInject(DirC)), - features: [PublicFeature] + factory: () => new DirA(directiveInject(DirC)) }); } @@ -322,8 +309,7 @@ describe('di', () => { static ngDirectiveDef = defineDirective({ selectors: [['', 'dirC', '']], type: DirC, - factory: () => new DirC(directiveInject(DirB)), - features: [PublicFeature] + factory: () => new DirC(directiveInject(DirB)) }); } @@ -334,8 +320,7 @@ describe('di', () => { static ngDirectiveDef = defineDirective({ selectors: [['', 'dirD', '']], type: DirD, - factory: () => new DirD(directiveInject(DirA)), - features: [PublicFeature] + factory: () => new DirD(directiveInject(DirA)) }); } @@ -379,8 +364,7 @@ describe('di', () => { element(0, 'div', ['dirA', '', 'dirB', '', 'dirC', 'dirC']); } }, - directives: [DirA, DirB], - features: [PublicFeature], + directives: [DirA, DirB] }); } @@ -409,12 +393,8 @@ describe('di', () => { this.count = count++; } - static ngDirectiveDef = defineDirective({ - selectors: [['', 'dirB', '']], - type: DirB, - factory: () => new DirB(), - features: [PublicFeature], - }); + static ngDirectiveDef = + defineDirective({selectors: [['', 'dirB', '']], type: DirB, factory: () => new DirB()}); } /**
*/ @@ -447,7 +427,6 @@ describe('di', () => { type: DirA, selectors: [['', 'dirA', '']], factory: () => new DirA(directiveInject(DirB), directiveInject(ViewContainerRef as any)), - features: [PublicFeature], exportAs: 'dirA' }); } @@ -599,8 +578,7 @@ describe('di', () => { selectors: [['', 'structuralDir', '']], factory: () => structuralDir = new StructuralDir(directiveInject(ViewContainerRef as any)), - inputs: {tmp: 'tmp'}, - features: [PublicFeature] + inputs: {tmp: 'tmp'} }); } @@ -763,18 +741,13 @@ describe('di', () => { static ngDirectiveDef = defineDirective({ selectors: [['', 'dir', '']], type: Dir, - factory: () => new Dir(directiveInject(OtherDir)), - features: [PublicFeature] + factory: () => new Dir(directiveInject(OtherDir)) }); } class OtherDir { - static ngDirectiveDef = defineDirective({ - selectors: [['', 'other', '']], - type: OtherDir, - factory: () => new OtherDir(), - features: [PublicFeature] - }); + static ngDirectiveDef = defineDirective( + {selectors: [['', 'other', '']], type: OtherDir, factory: () => new OtherDir()}); } /**
*/ @@ -794,18 +767,13 @@ describe('di', () => { static ngDirectiveDef = defineDirective({ selectors: [['', 'dir', '']], type: Dir, - factory: () => new Dir(directiveInject(OtherDir)), - features: [PublicFeature] + factory: () => new Dir(directiveInject(OtherDir)) }); } class OtherDir { - static ngDirectiveDef = defineDirective({ - selectors: [['', 'other', '']], - type: OtherDir, - factory: () => new OtherDir(), - features: [PublicFeature] - }); + static ngDirectiveDef = defineDirective( + {selectors: [['', 'other', '']], type: OtherDir, factory: () => new OtherDir()}); } /** @@ -830,8 +798,7 @@ describe('di', () => { static ngDirectiveDef = defineDirective({ selectors: [['', 'dirA', '']], type: DirA, - factory: () => new DirA(directiveInject(DirB)), - features: [PublicFeature] + factory: () => new DirA(directiveInject(DirB)) }); } @@ -841,8 +808,7 @@ describe('di', () => { static ngDirectiveDef = defineDirective({ selectors: [['', 'dirB', '']], type: DirB, - factory: () => new DirB(directiveInject(DirA)), - features: [PublicFeature] + factory: () => new DirB(directiveInject(DirA)) }); } @@ -853,7 +819,7 @@ describe('di', () => { } }, 1, 0, [DirA, DirB]); - expect(() => new ComponentFixture(App)).toThrowError(/Cannot instantiate cyclic dependency!/); + expect(() => new ComponentFixture(App)).toThrowError(/Circular dep for/); }); it('should throw if directive tries to inject itself', () => { @@ -863,8 +829,7 @@ describe('di', () => { static ngDirectiveDef = defineDirective({ selectors: [['', 'dir', '']], type: Dir, - factory: () => new Dir(directiveInject(Dir)), - features: [PublicFeature] + factory: () => new Dir(directiveInject(Dir)) }); } @@ -875,7 +840,7 @@ describe('di', () => { } }, 1, 0, [Dir]); - expect(() => new ComponentFixture(App)).toThrowError(/Cannot instantiate cyclic dependency!/); + expect(() => new ComponentFixture(App)).toThrowError(/Circular dep for/); }); describe('flags', () => { @@ -888,8 +853,7 @@ describe('di', () => { type: DirB, selectors: [['', 'dirB', '']], factory: () => new DirB(), - inputs: {value: 'dirB'}, - features: [PublicFeature] + inputs: {value: 'dirB'} }); } @@ -1006,7 +970,9 @@ describe('di', () => { } }, 2, 0, [DirA, DirB]); - expect(() => { new ComponentFixture(App); }).toThrowError(/Injector: NOT_FOUND \[DirB\]/); + expect(() => { + new ComponentFixture(App); + }).toThrowError(/NodeInjector: NOT_FOUND \[DirB\]/); }); it('should check only the current node with @Self even with false positive', () => { @@ -1041,7 +1007,7 @@ describe('di', () => { (DirA as any)['__NG_ELEMENT_ID__'] = 1; (DirC as any)['__NG_ELEMENT_ID__'] = 257; new ComponentFixture(App); - }).toThrowError(/Injector: NOT_FOUND \[DirB\]/); + }).toThrowError(/NodeInjector: NOT_FOUND \[DirB\]/); }); it('should not pass component boundary with @Host', () => { @@ -1071,7 +1037,9 @@ describe('di', () => { } }, 1, 0, [Comp, DirB]); - expect(() => { new ComponentFixture(App); }).toThrowError(/Injector: NOT_FOUND \[DirB\]/); + expect(() => { + new ComponentFixture(App); + }).toThrowError(/NodeInjector: NOT_FOUND \[DirB\]/); }); @@ -1097,7 +1065,6 @@ describe('di', () => { type: Directive, selectors: [['', 'dir', '']], factory: () => dir = new Directive(directiveInject(ElementRef)), - features: [PublicFeature], exportAs: 'dir' }); } @@ -1121,7 +1088,7 @@ describe('di', () => { if (rf & RenderFlags.Create) { elementStart(0, 'div', ['dir', '', 'dirSame', '']); elementEnd(); - div = getNativeByIndex(0, _getViewData()); + div = getNativeByIndex(0, getViewData()); } }, 1, 0, [Directive, DirectiveSameInstance]); @@ -1148,7 +1115,6 @@ describe('di', () => { type: Directive, selectors: [['', 'dir', '']], factory: () => dir = new Directive(directiveInject(ElementRef)), - features: [PublicFeature], exportAs: 'dir' }); } @@ -1179,7 +1145,6 @@ describe('di', () => { type: Directive, selectors: [['', 'dir', '']], factory: () => new Directive(directiveInject(TemplateRef as any)), - features: [PublicFeature], exportAs: 'dir' }); } @@ -1234,7 +1199,6 @@ describe('di', () => { type: Directive, selectors: [['', 'dir', '']], factory: () => new Directive(directiveInject(ViewContainerRef as any)), - features: [PublicFeature], exportAs: 'dir' }); } @@ -1298,8 +1262,7 @@ describe('di', () => { projectionDef(); projection(0); } - }, - features: [PublicFeature] + } }); } @@ -1312,7 +1275,6 @@ describe('di', () => { type: Directive, selectors: [['', 'dir', '']], factory: () => dir = new Directive(directiveInject(ChangeDetectorRef as any)), - features: [PublicFeature], exportAs: 'dir' }); } @@ -1324,8 +1286,7 @@ describe('di', () => { type: DirectiveSameInstance, selectors: [['', 'dirSame', '']], factory: () => dirSameInstance = - new DirectiveSameInstance(directiveInject(ChangeDetectorRef as any)), - features: [PublicFeature] + new DirectiveSameInstance(directiveInject(ChangeDetectorRef as any)) }); } @@ -1421,8 +1382,7 @@ describe('di', () => { textBinding(3, bind(tmp.value)); } }, - directives: directives, - features: [PublicFeature] + directives: directives }); } @@ -1749,15 +1709,15 @@ describe('di', () => { bloomAdd(0, mockTView, Dir198); bloomAdd(0, mockTView, Dir231); - expect(injectorHasToken(bloomHash(Dir0) as number, 0, mockTView.data)).toEqual(true); - expect(injectorHasToken(bloomHash(Dir1) as number, 0, mockTView.data)).toEqual(false); - expect(injectorHasToken(bloomHash(Dir33) as number, 0, mockTView.data)).toEqual(true); - expect(injectorHasToken(bloomHash(Dir66) as number, 0, mockTView.data)).toEqual(true); - expect(injectorHasToken(bloomHash(Dir99) as number, 0, mockTView.data)).toEqual(true); - expect(injectorHasToken(bloomHash(Dir132) as number, 0, mockTView.data)).toEqual(true); - expect(injectorHasToken(bloomHash(Dir165) as number, 0, mockTView.data)).toEqual(true); - expect(injectorHasToken(bloomHash(Dir198) as number, 0, mockTView.data)).toEqual(true); - expect(injectorHasToken(bloomHash(Dir231) as number, 0, mockTView.data)).toEqual(true); + expect(bloomHasToken(bloomHash(Dir0) as number, 0, mockTView.data)).toEqual(true); + expect(bloomHasToken(bloomHash(Dir1) as number, 0, mockTView.data)).toEqual(false); + expect(bloomHasToken(bloomHash(Dir33) as number, 0, mockTView.data)).toEqual(true); + expect(bloomHasToken(bloomHash(Dir66) as number, 0, mockTView.data)).toEqual(true); + expect(bloomHasToken(bloomHash(Dir99) as number, 0, mockTView.data)).toEqual(true); + expect(bloomHasToken(bloomHash(Dir132) as number, 0, mockTView.data)).toEqual(true); + expect(bloomHasToken(bloomHash(Dir165) as number, 0, mockTView.data)).toEqual(true); + expect(bloomHasToken(bloomHash(Dir198) as number, 0, mockTView.data)).toEqual(true); + expect(bloomHasToken(bloomHash(Dir231) as number, 0, mockTView.data)).toEqual(true); }); }); @@ -1771,7 +1731,6 @@ describe('di', () => { type: ChildDirective, selectors: [['', 'childDir', '']], factory: () => new ChildDirective(directiveInject(ParentDirective)), - features: [PublicFeature], exportAs: 'childDir' }); } @@ -1847,7 +1806,7 @@ describe('di', () => { // so that we have smaller HelloWorld. (parentTNode as{parent: any}).parent = undefined; - const injector: any = getOrCreateNodeInjector(); // TODO: Review use of `any` here (#19904) + const injector = getOrCreateNodeInjectorForNode(parentTNode, contentView); expect(injector).not.toEqual(-1); } finally { leaveView(oldView); diff --git a/packages/core/test/render3/discovery_utils_spec.ts b/packages/core/test/render3/discovery_utils_spec.ts index c9061d9fe3..89a7fc1a96 100644 --- a/packages/core/test/render3/discovery_utils_spec.ts +++ b/packages/core/test/render3/discovery_utils_spec.ts @@ -7,7 +7,7 @@ */ import {StaticInjector} from '../../src/di/injector'; import {getComponent, getDirectives, getHostComponent, getInjector, getLocalRefs, getRootComponents} from '../../src/render3/discovery_utils'; -import {PublicFeature, RenderFlags, defineComponent, defineDirective} from '../../src/render3/index'; +import {RenderFlags, defineComponent, defineDirective} from '../../src/render3/index'; import {element, elementEnd, elementStart, elementStyling, elementStylingApply} from '../../src/render3/instructions'; import {ComponentFixture} from './render_util'; @@ -231,8 +231,7 @@ describe('discovery utils', () => { factory: () => new Comp(), consts: 0, vars: 0, - template: (rf: RenderFlags, ctx: Comp) => {}, - features: [PublicFeature] + template: (rf: RenderFlags, ctx: Comp) => {} }); } @@ -252,8 +251,7 @@ describe('discovery utils', () => { factory: () => new Comp(), consts: 0, vars: 0, - template: (rf: RenderFlags, ctx: Comp) => {}, - features: [PublicFeature] + template: (rf: RenderFlags, ctx: Comp) => {} }); } diff --git a/packages/core/test/render3/integration_spec.ts b/packages/core/test/render3/integration_spec.ts index e6fb1499ec..44efa5ed83 100644 --- a/packages/core/test/render3/integration_spec.ts +++ b/packages/core/test/render3/integration_spec.ts @@ -10,11 +10,12 @@ import {ElementRef, TemplateRef, ViewContainerRef} from '@angular/core'; import {RendererStyleFlags2, RendererType2} from '../../src/render/api'; import {AttributeMarker, defineComponent, defineDirective, templateRefExtractor} from '../../src/render3/index'; -import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementAttribute, elementClassProp, elementContainerEnd, elementContainerStart, elementEnd, elementProperty, elementStart, elementStyleProp, elementStyling, elementStylingApply, embeddedViewEnd, embeddedViewStart, enableBindings, disableBindings, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, load, projection, projectionDef, reference, text, textBinding, template, elementStylingMap} from '../../src/render3/instructions'; +import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementAttribute, elementClassProp, elementContainerEnd, elementContainerStart, elementEnd, elementProperty, elementStart, elementStyleProp, elementStyling, elementStylingApply, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, load, projection, projectionDef, reference, text, textBinding, template, elementStylingMap, directiveInject} from '../../src/render3/instructions'; import {InitialStylingFlags, RenderFlags} from '../../src/render3/interfaces/definition'; import {RElement, Renderer3, RendererFactory3, domRendererFactory3, RText, RComment, RNode, RendererStyleFlags3, ProceduralRenderer3} from '../../src/render3/interfaces/renderer'; import {NO_CHANGE} from '../../src/render3/tokens'; import {HEADER_OFFSET, CONTEXT} from '../../src/render3/interfaces/view'; +import {enableBindings, disableBindings} from '../../src/render3/state'; import {sanitizeUrl} from '../../src/sanitization/sanitization'; import {Sanitizer, SecurityContext} from '../../src/sanitization/security'; @@ -23,7 +24,6 @@ import {ComponentFixture, TemplateFixture, createComponent, renderToHtml} from ' import {getContext} from '../../src/render3/context_discovery'; import {StylingIndex} from '../../src/render3/interfaces/styling'; import {MONKEY_PATCH_KEY_NAME} from '../../src/render3/interfaces/context'; -import {directiveInject} from '../../src/render3/di'; describe('render3 integration test', () => { diff --git a/packages/core/test/render3/ivy/jit_spec.ts b/packages/core/test/render3/ivy/jit_spec.ts index f72fcf0504..51b1f35e9f 100644 --- a/packages/core/test/render3/ivy/jit_spec.ts +++ b/packages/core/test/render3/ivy/jit_spec.ts @@ -239,7 +239,7 @@ ivyEnabled && describe('render3 jit', () => { const pipeDef = (P as any).ngPipeDef as PipeDef

; expect(pipeDef.name).toBe('test-pipe'); expect(pipeDef.pure).toBe(false, 'pipe should not be pure'); - expect(pipeDef.factory() instanceof P) + expect(pipeDef.factory(null) instanceof P) .toBe(true, 'factory() should create an instance of the pipe'); }); diff --git a/packages/core/test/render3/ng_on_changes_feature_spec.ts b/packages/core/test/render3/ng_on_changes_feature_spec.ts index c0034bf37c..69e8012c79 100644 --- a/packages/core/test/render3/ng_on_changes_feature_spec.ts +++ b/packages/core/test/render3/ng_on_changes_feature_spec.ts @@ -36,7 +36,7 @@ describe('NgOnChangesFeature', () => { } const myDir = - (MyDirective.ngDirectiveDef as DirectiveDef).factory() as MyDirective; + (MyDirective.ngDirectiveDef as DirectiveDef).factory(null) as MyDirective; myDir.valA = 'first'; expect(myDir.valA).toEqual('first'); myDir.valB = 'second'; @@ -89,7 +89,7 @@ describe('NgOnChangesFeature', () => { } const myDir = - (SubDirective.ngDirectiveDef as DirectiveDef).factory() as SubDirective; + (SubDirective.ngDirectiveDef as DirectiveDef).factory(null) as SubDirective; myDir.valA = 'first'; expect(myDir.valA).toEqual('first'); @@ -142,7 +142,7 @@ describe('NgOnChangesFeature', () => { } const myDir = - (SubDirective.ngDirectiveDef as DirectiveDef).factory() as SubDirective; + (SubDirective.ngDirectiveDef as DirectiveDef).factory(null) as SubDirective; myDir.valA = 'first'; myDir.valB = 'second'; @@ -183,7 +183,7 @@ describe('NgOnChangesFeature', () => { } const myDir = - (SubDirective.ngDirectiveDef as DirectiveDef).factory() as SubDirective; + (SubDirective.ngDirectiveDef as DirectiveDef).factory(null) as SubDirective; myDir.valA = 'first'; myDir.valB = 'second'; @@ -237,7 +237,7 @@ describe('NgOnChangesFeature', () => { } const myDir = - (SubDirective.ngDirectiveDef as DirectiveDef).factory() as SubDirective; + (SubDirective.ngDirectiveDef as DirectiveDef).factory(null) as SubDirective; myDir.valA = 'first'; expect(myDir.valA).toEqual('first'); @@ -279,7 +279,7 @@ describe('NgOnChangesFeature', () => { } const myDir = - (MyDirective.ngDirectiveDef as DirectiveDef).factory() as MyDirective; + (MyDirective.ngDirectiveDef as DirectiveDef).factory(null) as MyDirective; myDir.valA = 'first'; myDir.valB = 'second'; (MyDirective.ngDirectiveDef as DirectiveDef).doCheck !.call(myDir); @@ -315,7 +315,7 @@ describe('NgOnChangesFeature', () => { } const myDir = - (MyDirective.ngDirectiveDef as DirectiveDef).factory() as MyDirective; + (MyDirective.ngDirectiveDef as DirectiveDef).factory(null) as MyDirective; myDir.onlySetter = 'someValue'; expect(myDir.onlySetter).toBeUndefined(); (MyDirective.ngDirectiveDef as DirectiveDef).doCheck !.call(myDir); diff --git a/packages/core/test/render3/node_selector_matcher_spec.ts b/packages/core/test/render3/node_selector_matcher_spec.ts index 6ebce5672a..3095bc2bcf 100644 --- a/packages/core/test/render3/node_selector_matcher_spec.ts +++ b/packages/core/test/render3/node_selector_matcher_spec.ts @@ -11,15 +11,16 @@ import {AttributeMarker, TAttributes, TNode, TNodeType} from '../../src/render3/ import {CssSelector, CssSelectorList, NG_PROJECT_AS_ATTR_NAME, SelectorFlags,} from '../../src/render3/interfaces/projection'; import {getProjectAsAttrValue, isNodeMatchingSelectorList, isNodeMatchingSelector} from '../../src/render3/node_selector_matcher'; import {createTNode} from '@angular/core/src/render3/instructions'; +import {getViewData} from '@angular/core/src/render3/state'; function testLStaticData(tagName: string, attrs: TAttributes | null): TNode { - return createTNode(TNodeType.Element, 0, tagName, attrs, null); + return createTNode(getViewData(), TNodeType.Element, 0, tagName, attrs, null); } describe('css selector matching', () => { function isMatching(tagName: string, attrs: TAttributes | null, selector: CssSelector): boolean { return isNodeMatchingSelector( - createTNode(TNodeType.Element, 0, tagName, attrs, null), selector); + createTNode(getViewData(), TNodeType.Element, 0, tagName, attrs, null), selector); } describe('isNodeMatchingSimpleSelector', () => { diff --git a/packages/core/test/render3/pipe_spec.ts b/packages/core/test/render3/pipe_spec.ts index 4ea9a820dc..293b44cae5 100644 --- a/packages/core/test/render3/pipe_spec.ts +++ b/packages/core/test/render3/pipe_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Directive, InjectionToken, OnChanges, OnDestroy, Pipe, PipeTransform, createInjector, defineInjectable, defineInjector, ɵNgModuleDef as NgModuleDef, ɵPublicFeature as PublicFeature, ɵdefineComponent as defineComponent, ɵdirectiveInject as directiveInject} from '@angular/core'; +import {Directive, InjectionToken, OnChanges, OnDestroy, Pipe, PipeTransform, createInjector, defineInjectable, defineInjector, ɵNgModuleDef as NgModuleDef, ɵdefineComponent as defineComponent, ɵdirectiveInject as directiveInject} from '@angular/core'; import {expect} from '@angular/platform-browser/testing/src/matchers'; import {defineDirective, definePipe} from '../../src/render3/definition'; @@ -367,7 +367,6 @@ describe('pipe', () => { static ngComponentDef = defineComponent({ type: MyComponent, selectors: [['my-app']], - features: [PublicFeature], factory: function MyComponent_Factory() { return new MyComponent(); }, consts: 2, vars: 3, diff --git a/packages/core/test/render3/properties_spec.ts b/packages/core/test/render3/properties_spec.ts index 8c8f678851..1f92743c86 100644 --- a/packages/core/test/render3/properties_spec.ts +++ b/packages/core/test/render3/properties_spec.ts @@ -8,7 +8,7 @@ import {EventEmitter} from '@angular/core'; -import {AttributeMarker, PublicFeature, defineComponent, template, defineDirective} from '../../src/render3/index'; +import {AttributeMarker, defineComponent, template, defineDirective} from '../../src/render3/index'; import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, listener, load, reference, text, textBinding} from '../../src/render3/instructions'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {NO_CHANGE} from '../../src/render3/tokens'; @@ -168,8 +168,7 @@ describe('elementProperty', () => { hostVars: 1, hostBindings: (directiveIndex: number, elementIndex: number) => { elementProperty(elementIndex, 'id', bind(load(directiveIndex).id)); - }, - features: [PublicFeature] + } }); } @@ -190,8 +189,7 @@ describe('elementProperty', () => { const ctx = load(dirIndex) as HostBindingComp; elementProperty(elIndex, 'title', bind(ctx.title)); }, - template: (rf: RenderFlags, ctx: HostBindingComp) => {}, - features: [PublicFeature] + template: (rf: RenderFlags, ctx: HostBindingComp) => {} }); } @@ -231,8 +229,7 @@ describe('elementProperty', () => { hostVars: 1, hostBindings: (directiveIndex: number, elementIndex: number) => { elementProperty(elementIndex, 'id', bind(load(directiveIndex).id)); - }, - features: [PublicFeature] + } }); } @@ -272,8 +269,7 @@ describe('elementProperty', () => { hostVars: 1, hostBindings: (directiveIndex: number, elementIndex: number) => { elementProperty(elementIndex, 'id', bind(load(directiveIndex).id)); - }, - features: [PublicFeature] + } }); } diff --git a/packages/core/test/render3/query_spec.ts b/packages/core/test/render3/query_spec.ts index 456c1b3450..762756138a 100644 --- a/packages/core/test/render3/query_spec.ts +++ b/packages/core/test/render3/query_spec.ts @@ -10,13 +10,13 @@ import {NgForOfContext} from '@angular/common'; import {ElementRef, TemplateRef, ViewContainerRef} from '@angular/core'; import {EventEmitter} from '../..'; -import {directiveInject} from '../../src/render3/di'; import {AttributeMarker, QueryList, defineComponent, defineDirective, detectChanges} from '../../src/render3/index'; import {getNativeByIndex} from '../../src/render3/util'; -import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementContainerEnd, elementContainerStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, load, loadQueryList, reference, registerContentQuery, template, _getViewData} from '../../src/render3/instructions'; +import {bind, container, containerRefreshEnd, containerRefreshStart, directiveInject, element, elementContainerEnd, elementContainerStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, load, loadQueryList, reference, registerContentQuery, template} from '../../src/render3/instructions'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {query, queryRefresh} from '../../src/render3/query'; +import {getViewData} from '../../src/render3/state'; import {templateRefExtractor} from '../../src/render3/view_engine_compatibility_prebound'; import {NgForOf, NgIf, NgTemplateOutlet} from './common_with_def'; @@ -115,7 +115,7 @@ describe('query', () => { function(rf: RenderFlags, ctx: any) { if (rf & RenderFlags.Create) { element(1, 'div', ['child', '']); - elToQuery = getNativeByIndex(1, _getViewData()); + elToQuery = getNativeByIndex(1, getViewData()); } }, 2, 0, [Child], [], @@ -222,7 +222,7 @@ describe('query', () => { function(rf: RenderFlags, ctx: any) { if (rf & RenderFlags.Create) { element(1, 'div', null, ['foo', '']); - elToQuery = getNativeByIndex(1, _getViewData()); + elToQuery = getNativeByIndex(1, getViewData()); element(3, 'div'); } }, @@ -259,7 +259,7 @@ describe('query', () => { function(rf: RenderFlags, ctx: any) { if (rf & RenderFlags.Create) { element(2, 'div', null, ['foo', '', 'bar', '']); - elToQuery = getNativeByIndex(2, _getViewData()); + elToQuery = getNativeByIndex(2, getViewData()); element(5, 'div'); } }, @@ -306,10 +306,10 @@ describe('query', () => { function(rf: RenderFlags, ctx: any) { if (rf & RenderFlags.Create) { element(1, 'div', null, ['foo', '']); - el1ToQuery = getNativeByIndex(1, _getViewData()); + el1ToQuery = getNativeByIndex(1, getViewData()); element(3, 'div'); element(4, 'div', null, ['bar', '']); - el2ToQuery = getNativeByIndex(4, _getViewData()); + el2ToQuery = getNativeByIndex(4, getViewData()); } }, 6, 0, [], [], @@ -345,7 +345,7 @@ describe('query', () => { function(rf: RenderFlags, ctx: any) { if (rf & RenderFlags.Create) { element(1, 'div', null, ['foo', '']); - elToQuery = getNativeByIndex(1, _getViewData()); + elToQuery = getNativeByIndex(1, getViewData()); element(3, 'div'); } }, @@ -381,7 +381,7 @@ describe('query', () => { function(rf: RenderFlags, ctx: any) { if (rf & RenderFlags.Create) { elementContainerStart(1, null, ['foo', '']); - elToQuery = getNativeByIndex(1, _getViewData()); + elToQuery = getNativeByIndex(1, getViewData()); elementContainerEnd(); } }, @@ -417,7 +417,7 @@ describe('query', () => { function(rf: RenderFlags, ctx: any) { if (rf & RenderFlags.Create) { elementContainerStart(1, null, ['foo', '']); - elToQuery = getNativeByIndex(1, _getViewData()); + elToQuery = getNativeByIndex(1, getViewData()); elementContainerEnd(); } }, @@ -480,7 +480,7 @@ describe('query', () => { elementContainerStart(2); { element(3, 'div', null, ['foo', '']); - elToQuery = getNativeByIndex(3, _getViewData()); + elToQuery = getNativeByIndex(3, getViewData()); } elementContainerEnd(); } @@ -890,7 +890,7 @@ describe('query', () => { function(rf: RenderFlags, ctx: any) { if (rf & RenderFlags.Create) { element(1, 'div', ['child', ''], ['foo', 'child']); - div = getNativeByIndex(1, _getViewData()); + div = getNativeByIndex(1, getViewData()); } }, 3, 0, [Child], [], @@ -925,7 +925,7 @@ describe('query', () => { function(rf: RenderFlags, ctx: any) { if (rf & RenderFlags.Create) { element(1, 'div', ['child', ''], ['foo', '', 'bar', 'child']); - div = getNativeByIndex(1, _getViewData()); + div = getNativeByIndex(1, getViewData()); } if (rf & RenderFlags.Update) { childInstance = getDirectiveOnNode(1); @@ -1409,7 +1409,7 @@ describe('query', () => { { if (rf1 & RenderFlags.Create) { element(0, 'div', null, ['foo', '']); - firstEl = getNativeByIndex(0, _getViewData()); + firstEl = getNativeByIndex(0, getViewData()); } } embeddedViewEnd(); @@ -1461,10 +1461,10 @@ describe('query', () => { function(rf: RenderFlags, ctx: any) { if (rf & RenderFlags.Create) { element(1, 'span', null, ['foo', '']); - firstEl = getNativeByIndex(1, _getViewData()); + firstEl = getNativeByIndex(1, getViewData()); container(3); element(4, 'span', null, ['foo', '']); - lastEl = getNativeByIndex(4, _getViewData()); + lastEl = getNativeByIndex(4, getViewData()); } if (rf & RenderFlags.Update) { containerRefreshStart(3); @@ -1474,7 +1474,7 @@ describe('query', () => { { if (rf1 & RenderFlags.Create) { element(0, 'div', null, ['foo', '']); - viewEl = getNativeByIndex(0, _getViewData()); + viewEl = getNativeByIndex(0, getViewData()); } } embeddedViewEnd(); @@ -1541,7 +1541,7 @@ describe('query', () => { { if (rf0 & RenderFlags.Create) { element(0, 'div', null, ['foo', '']); - firstEl = getNativeByIndex(0, _getViewData()); + firstEl = getNativeByIndex(0, getViewData()); } } embeddedViewEnd(); @@ -1551,7 +1551,7 @@ describe('query', () => { { if (rf1 & RenderFlags.Create) { element(0, 'span', null, ['foo', '']); - lastEl = getNativeByIndex(0, _getViewData()); + lastEl = getNativeByIndex(0, getViewData()); } } embeddedViewEnd(); @@ -1614,7 +1614,7 @@ describe('query', () => { { if (rf0 & RenderFlags.Create) { element(0, 'div', null, ['foo', '']); - firstEl = getNativeByIndex(0, _getViewData()); + firstEl = getNativeByIndex(0, getViewData()); container(2); } if (rf0 & RenderFlags.Update) { @@ -1625,7 +1625,7 @@ describe('query', () => { { if (rf2) { element(0, 'span', null, ['foo', '']); - lastEl = getNativeByIndex(0, _getViewData()); + lastEl = getNativeByIndex(0, getViewData()); } } embeddedViewEnd(); @@ -1911,7 +1911,8 @@ describe('query', () => { type: WithContentDirective, selectors: [['', 'with-content', '']], factory: () => new WithContentDirective(), - contentQueries: () => { registerContentQuery(query(null, ['foo'], true)); }, + contentQueries: + (dirIndex) => { registerContentQuery(query(null, ['foo'], true), dirIndex); }, contentQueriesRefresh: (dirIndex: number, queryStartIdx: number) => { let tmp: any; withContentInstance = load(dirIndex); @@ -1932,7 +1933,8 @@ describe('query', () => { template: function(rf: RenderFlags, ctx: any) {}, consts: 0, vars: 0, - contentQueries: () => { registerContentQuery(query(null, ['foo'], false)); }, + contentQueries: + (dirIndex) => { registerContentQuery(query(null, ['foo'], false), dirIndex); }, contentQueriesRefresh: (dirIndex: number, queryStartIdx: number) => { let tmp: any; shallowCompInstance = load(dirIndex); @@ -1971,6 +1973,51 @@ describe('query', () => { `Expected content query results to be available when ngAfterContentChecked was called.`); }); + it('should support content queries for directives within repeated embedded views', () => { + /** + * % for (let i = 0; i < 3; i++) { + *

+ * + *
+ * % } + */ + const AppComponent = createComponent('app-component', function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + container(0); + } + if (rf & RenderFlags.Update) { + containerRefreshStart(0); + { + for (let i = 0; i < 3; i++) { + let rf = embeddedViewStart(1, 3, 0); + if (rf & RenderFlags.Create) { + elementStart(0, 'div', [AttributeMarker.SelectOnly, 'with-content']); + { element(1, 'span', null, ['foo', '']); } + elementEnd(); + } + embeddedViewEnd(); + } + } + + containerRefreshEnd(); + } + }, 1, 0, [WithContentDirective]); + + const fixture = new ComponentFixture(AppComponent); + expect(withContentInstance !.foos.length) + .toBe(1, `Expected content query to match .`); + + expect(withContentInstance !.contentInitQuerySnapshot) + .toBe( + 1, + `Expected content query results to be available when ngAfterContentInit was called.`); + + expect(withContentInstance !.contentCheckedQuerySnapshot) + .toBe( + 1, + `Expected content query results to be available when ngAfterContentChecked was called.`); + }); + it('should support content query matches on directive hosts', () => { /** *
@@ -2109,10 +2156,10 @@ describe('query', () => { selectors: [['', 'query', '']], exportAs: 'query', factory: () => new QueryDirective(), - contentQueries: () => { + contentQueries: (dirIndex) => { // @ContentChildren('foo, bar, baz', {descendants: true}) fooBars: // QueryList; - registerContentQuery(query(null, ['foo', 'bar', 'baz'], true)); + registerContentQuery(query(null, ['foo', 'bar', 'baz'], true), dirIndex); }, contentQueriesRefresh: (dirIndex: number, queryStartIdx: number) => { let tmp: any; @@ -2173,10 +2220,10 @@ describe('query', () => { selectors: [['', 'query', '']], exportAs: 'query', factory: () => new QueryDirective(), - contentQueries: () => { + contentQueries: (dirIndex) => { // @ContentChildren('foo, bar, baz', {descendants: true}) fooBars: // QueryList; - registerContentQuery(query(null, ['foo'], false)); + registerContentQuery(query(null, ['foo'], false), dirIndex); }, contentQueriesRefresh: (dirIndex: number, queryStartIdx: number) => { let tmp: any; @@ -2227,9 +2274,9 @@ describe('query', () => { selectors: [['', 'shallow-query', '']], exportAs: 'shallow-query', factory: () => new ShallowQueryDirective(), - contentQueries: () => { + contentQueries: (dirIndex) => { // @ContentChildren('foo', {descendants: false}) foos: QueryList; - registerContentQuery(query(null, ['foo'], false)); + registerContentQuery(query(null, ['foo'], false), dirIndex); }, contentQueriesRefresh: (dirIndex: number, queryStartIdx: number) => { let tmp: any; @@ -2247,9 +2294,9 @@ describe('query', () => { selectors: [['', 'deep-query', '']], exportAs: 'deep-query', factory: () => new DeepQueryDirective(), - contentQueries: () => { + contentQueries: (dirIndex) => { // @ContentChildren('foo', {descendants: false}) foos: QueryList; - registerContentQuery(query(null, ['foo'], true)); + registerContentQuery(query(null, ['foo'], true), dirIndex); }, contentQueriesRefresh: (dirIndex: number, queryStartIdx: number) => { let tmp: any; diff --git a/packages/core/test/render3/render_util.ts b/packages/core/test/render3/render_util.ts index f7ad5c8fe5..cc0dd52ac9 100644 --- a/packages/core/test/render3/render_util.ts +++ b/packages/core/test/render3/render_util.ts @@ -23,12 +23,13 @@ import {CreateComponentOptions} from '../../src/render3/component'; import {discoverDirectives, getContext, isComponentInstance} from '../../src/render3/context_discovery'; import {extractDirectiveDef, extractPipeDef} from '../../src/render3/definition'; import {NG_ELEMENT_ID} from '../../src/render3/fields'; -import {ComponentTemplate, ComponentType, DirectiveDef, DirectiveType, PublicFeature, RenderFlags, defineComponent, defineDirective, renderComponent as _renderComponent, tick} from '../../src/render3/index'; -import {_getViewData, renderTemplate} from '../../src/render3/instructions'; +import {ComponentTemplate, ComponentType, DirectiveDef, DirectiveType, RenderFlags, defineComponent, defineDirective, renderComponent as _renderComponent, tick} from '../../src/render3/index'; +import {renderTemplate} from '../../src/render3/instructions'; import {DirectiveDefList, DirectiveTypesOrFactory, PipeDef, PipeDefList, PipeTypesOrFactory} from '../../src/render3/interfaces/definition'; import {PlayerHandler} from '../../src/render3/interfaces/player'; import {RElement, RText, Renderer3, RendererFactory3, domRendererFactory3} from '../../src/render3/interfaces/renderer'; import {HEADER_OFFSET, LViewData} from '../../src/render3/interfaces/view'; +import {getViewData} from '../../src/render3/state'; import {Sanitizer} from '../../src/sanitization/security'; import {Type} from '../../src/type'; @@ -275,7 +276,6 @@ export function createComponent( factory: () => new Component, template: template, viewQuery: viewQuery, - features: [PublicFeature], directives: directives, pipes: pipes }); @@ -289,7 +289,6 @@ export function createDirective( type: Directive, selectors: [['', name, '']], factory: () => new Directive(), - features: [PublicFeature], exportAs: exportAs, }); }; @@ -297,7 +296,7 @@ export function createDirective( /** Gets the directive on the given node at the given index */ export function getDirectiveOnNode(nodeIndex: number, dirIndex: number = 0) { - const directives = discoverDirectives(nodeIndex + HEADER_OFFSET, _getViewData(), true); + const directives = discoverDirectives(nodeIndex + HEADER_OFFSET, getViewData(), true); if (directives == null) { throw new Error(`No directives exist on node in slot ${nodeIndex}`); } diff --git a/packages/core/test/render3/view_container_ref_spec.ts b/packages/core/test/render3/view_container_ref_spec.ts index 7bcf6102fe..f234806735 100644 --- a/packages/core/test/render3/view_container_ref_spec.ts +++ b/packages/core/test/render3/view_container_ref_spec.ts @@ -8,10 +8,9 @@ import {Component, ComponentFactoryResolver, ElementRef, EmbeddedViewRef, NgModuleRef, Pipe, PipeTransform, RendererFactory2, TemplateRef, ViewContainerRef, createInjector, defineInjector, ɵAPP_ROOT as APP_ROOT, ɵNgModuleDef as NgModuleDef} from '../../src/core'; import {ViewEncapsulation} from '../../src/metadata'; -import {directiveInject} from '../../src/render3/di'; import {AttributeMarker, NgOnChangesFeature, defineComponent, defineDirective, definePipe, injectComponentFactoryResolver, load} from '../../src/render3/index'; -import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation3, nextContext, projection, projectionDef, reference, template, text, textBinding} from '../../src/render3/instructions'; +import {bind, container, containerRefreshEnd, containerRefreshStart, directiveInject, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation3, nextContext, projection, projectionDef, reference, template, text, textBinding} from '../../src/render3/instructions'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {templateRefExtractor} from '../../src/render3/view_engine_compatibility_prebound'; import {NgModuleFactory} from '../../src/render3/ng_module_ref'; diff --git a/tools/public_api_guard/core/core.d.ts b/tools/public_api_guard/core/core.d.ts index 3cd68fc4ac..0692ddf3e1 100644 --- a/tools/public_api_guard/core/core.d.ts +++ b/tools/public_api_guard/core/core.d.ts @@ -717,7 +717,7 @@ export interface ResolvedReflectiveProvider { resolvedFactories: ResolvedReflectiveFactory[]; } -export declare function resolveForwardRef(type: any): any; +export declare function resolveForwardRef(type: T): T; /** @deprecated */ export declare abstract class RootRenderer {