refactor(ivy): move hostVars/hostAttrs from instruction to DirectiveDef (#34683)

This change moves information from instructions to declarative position:
- `ɵɵallocHostVars(vars)` => `DirectiveDef.hostVars`
- `ɵɵelementHostAttrs(attrs)` => `DirectiveDef.hostAttrs`

When merging directives it is necessary to know about `hostVars` and `hostAttrs`. Before this change the information was stored in the `hostBindings` function. This was problematic, because in order to get to the information the `hostBindings` would have to be executed. In order for `hostBindings` to be executed the directives would have to be instantiated. This means that the directive instantiation would happen before we had knowledge about the `hostAttrs` and as a result the directive could observe in the constructor that not all of the `hostAttrs` have been applied. This further complicates the runtime as we have to apply `hostAttrs` in parts over many invocations.

`ɵɵallocHostVars` was unnecessarily complicated because it would have to update the `LView` (and Blueprint) while existing directives are already executing. By moving it out of `hostBindings` function we can access it statically and we can create correct `LView` (and Blueprint) in a single pass.

This change only changes how the instructions are generated, but does not change the runtime much. (We cheat by emulating the old behavior by calling `ɵɵallocHostVars` and `ɵɵelementHostAttrs`) Subsequent change will refactor the runtime to take advantage of the static information.

PR Close #34683
This commit is contained in:
Miško Hevery 2020-01-08 11:32:33 -08:00
parent 94504ff5c8
commit 2961bf06c6
33 changed files with 751 additions and 185 deletions

View File

@ -21,7 +21,7 @@
"master": { "master": {
"uncompressed": { "uncompressed": {
"runtime-es2015": 3097, "runtime-es2015": 3097,
"main-es2015": 439352, "main-es2015": 438671,
"polyfills-es2015": 52503 "polyfills-es2015": 52503
} }
} }

View File

@ -12,7 +12,7 @@
"master": { "master": {
"uncompressed": { "uncompressed": {
"runtime-es2015": 1485, "runtime-es2015": 1485,
"main-es2015": 15783, "main-es2015": 18496,
"polyfills-es2015": 36808 "polyfills-es2015": 36808
} }
} }

View File

@ -5,7 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import {Directive, DoCheck, Input, ɵRenderFlags, ɵɵallocHostVars, ɵɵclassMap, ɵɵdefineDirective} from '@angular/core'; import {Directive, DoCheck, Input, ɵRenderFlags, ɵɵclassMap, ɵɵdefineDirective} from '@angular/core';
import {NgClassImpl, NgClassImplProvider} from './ng_class_impl'; import {NgClassImpl, NgClassImplProvider} from './ng_class_impl';
@ -32,10 +32,8 @@ export const ngClassDirectiveDef__PRE_R3__ = undefined;
export const ngClassDirectiveDef__POST_R3__ = ɵɵdefineDirective({ export const ngClassDirectiveDef__POST_R3__ = ɵɵdefineDirective({
type: function() {} as any, type: function() {} as any,
selectors: null as any, selectors: null as any,
hostVars: 2,
hostBindings: function(rf: ɵRenderFlags, ctx: any, elIndex: number) { hostBindings: function(rf: ɵRenderFlags, ctx: any, elIndex: number) {
if (rf & ɵRenderFlags.Create) {
ɵɵallocHostVars(2);
}
if (rf & ɵRenderFlags.Update) { if (rf & ɵRenderFlags.Update) {
ɵɵclassMap(ctx.getValue()); ɵɵclassMap(ctx.getValue());
} }

View File

@ -5,7 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import {Directive, DoCheck, Input, ɵRenderFlags, ɵɵallocHostVars, ɵɵdefineDirective, ɵɵstyleMap} from '@angular/core'; import {Directive, DoCheck, Input, ɵRenderFlags, ɵɵdefineDirective, ɵɵstyleMap} from '@angular/core';
import {NgStyleImpl, NgStyleImplProvider} from './ng_style_impl'; import {NgStyleImpl, NgStyleImplProvider} from './ng_style_impl';
@ -33,10 +33,8 @@ export const ngStyleFactoryDef__PRE_R3__ = undefined;
export const ngStyleDirectiveDef__POST_R3__ = ɵɵdefineDirective({ export const ngStyleDirectiveDef__POST_R3__ = ɵɵdefineDirective({
type: function() {} as any, type: function() {} as any,
selectors: null as any, selectors: null as any,
hostVars: 2,
hostBindings: function(rf: ɵRenderFlags, ctx: any, elIndex: number) { hostBindings: function(rf: ɵRenderFlags, ctx: any, elIndex: number) {
if (rf & ɵRenderFlags.Create) {
ɵɵallocHostVars(2);
}
if (rf & ɵRenderFlags.Update) { if (rf & ɵRenderFlags.Update) {
ɵɵstyleMap(ctx.getValue()); ɵɵstyleMap(ctx.getValue());
} }

View File

@ -216,7 +216,6 @@ runInEachFileSystem(() => {
const jsContents = fs.readFile(_(`/node_modules/test-package/index.js`)); const jsContents = fs.readFile(_(`/node_modules/test-package/index.js`));
expect(jsContents).not.toMatch(/\bconst \w+\s*=/); expect(jsContents).not.toMatch(/\bconst \w+\s*=/);
expect(jsContents).toMatch(/\bvar _c0 =/);
}); });
it('should add ɵfac but not duplicate ɵprov properties on injectables', () => { it('should add ɵfac but not duplicate ɵprov properties on injectables', () => {

View File

@ -431,10 +431,8 @@ describe('compiler compliance', () => {
const $_c1$ = function (a0, a1) { return { value: a0, params: a1 }; }; const $_c1$ = function (a0, a1) { return { value: a0, params: a1 }; };
const $_c2$ = function (a0, a1) { return { collapsedWidth: a0, expandedWidth: a1 }; }; const $_c2$ = function (a0, a1) { return { collapsedWidth: a0, expandedWidth: a1 }; };
hostVars: 14,
hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) { hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) {
if (rf & 1) {
$r3$.ɵɵallocHostVars(14);
}
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵupdateSyntheticHostBinding("@expansionHeight", $r3$.ɵɵupdateSyntheticHostBinding("@expansionHeight",
$r3$.ɵɵpureFunction2(5, $_c1$, ctx.getExpandedState(), $r3$.ɵɵpureFunction2(5, $_c1$, ctx.getExpandedState(),
@ -3499,10 +3497,8 @@ describe('compiler compliance', () => {
// ... // ...
BaseClass.ɵdir = $r3$.ɵɵdefineDirective({ BaseClass.ɵdir = $r3$.ɵɵdefineDirective({
type: BaseClass, type: BaseClass,
hostVars: 1,
hostBindings: function BaseClass_HostBindings(rf, ctx, elIndex) { hostBindings: function BaseClass_HostBindings(rf, ctx, elIndex) {
if (rf & 1) {
$r3$.ɵɵallocHostVars(1);
}
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵattribute("tabindex", ctx.tabindex); $r3$.ɵɵattribute("tabindex", ctx.tabindex);
} }

View File

@ -668,13 +668,11 @@ describe('compiler compliance: bindings', () => {
}; };
const HostBindingDirDeclaration = ` const HostBindingDirDeclaration = `
HostBindingDir.ɵdir = $r3$.ɵɵdefineDirective({ HostBindingDir.ɵdir = $r3$.ɵɵdefineDirective({
type: HostBindingDir, type: HostBindingDir,
selectors: [["", "hostBindingDir", ""]], selectors: [["", "hostBindingDir", ""]],
hostVars: 1,
hostBindings: function HostBindingDir_HostBindings(rf, ctx, elIndex) { hostBindings: function HostBindingDir_HostBindings(rf, ctx, elIndex) {
if (rf & 1) {
$r3$.ɵɵallocHostVars(1);
}
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵhostProperty("id", ctx.dirId); $r3$.ɵɵhostProperty("id", ctx.dirId);
} }
@ -717,10 +715,8 @@ describe('compiler compliance: bindings', () => {
HostBindingComp.ɵcmp = $r3$.ɵɵdefineComponent({ HostBindingComp.ɵcmp = $r3$.ɵɵdefineComponent({
type: HostBindingComp, type: HostBindingComp,
selectors: [["host-binding-comp"]], selectors: [["host-binding-comp"]],
hostVars: 3,
hostBindings: function HostBindingComp_HostBindings(rf, ctx, elIndex) { hostBindings: function HostBindingComp_HostBindings(rf, ctx, elIndex) {
if (rf & 1) {
$r3$.ɵɵallocHostVars(3);
}
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵhostProperty("id", $r3$.ɵɵpureFunction1(1, $ff$, ctx.id)); $r3$.ɵɵhostProperty("id", $r3$.ɵɵpureFunction1(1, $ff$, ctx.id));
} }
@ -764,10 +760,8 @@ describe('compiler compliance: bindings', () => {
HostAttributeDir.ɵdir = $r3$.ɵɵdefineDirective({ HostAttributeDir.ɵdir = $r3$.ɵɵdefineDirective({
type: HostAttributeDir, type: HostAttributeDir,
selectors: [["", "hostAttributeDir", ""]], selectors: [["", "hostAttributeDir", ""]],
hostVars: 1,
hostBindings: function HostAttributeDir_HostBindings(rf, ctx, elIndex) { hostBindings: function HostAttributeDir_HostBindings(rf, ctx, elIndex) {
if (rf & 1) {
$r3$.ɵɵallocHostVars(1);
}
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵattribute("required", ctx.required); $r3$.ɵɵattribute("required", ctx.required);
} }
@ -803,16 +797,10 @@ describe('compiler compliance: bindings', () => {
}; };
const HostAttributeDirDeclaration = ` const HostAttributeDirDeclaration = `
const $c0$ = ["aria-label", "label"];
HostAttributeDir.ɵdir = $r3$.ɵɵdefineDirective({ HostAttributeDir.ɵdir = $r3$.ɵɵdefineDirective({
type: HostAttributeDir, type: HostAttributeDir,
selectors: [["", "hostAttributeDir", ""]], selectors: [["", "hostAttributeDir", ""]],
hostBindings: function HostAttributeDir_HostBindings(rf, ctx, elIndex) { hostAttrs: ["aria-label", "label"]
if (rf & 1) {
$r3$.ɵɵelementHostAttrs($c0$);
}
}
}); });
`; `;
@ -859,32 +847,20 @@ describe('compiler compliance: bindings', () => {
}; };
const CompAndDirDeclaration = ` const CompAndDirDeclaration = `
const $c0$ = ["title", "hello there from component", ${AttributeMarker.Styles}, "opacity", "1"];
const $c1$ = ["title", "hello there from directive", ${AttributeMarker.Classes}, "one", "two", ${AttributeMarker.Styles}, "width", "200px", "height", "500px"];
HostAttributeComp.ɵcmp = $r3$.ɵɵdefineComponent({ HostAttributeComp.ɵcmp = $r3$.ɵɵdefineComponent({
type: HostAttributeComp, type: HostAttributeComp,
selectors: [["my-host-attribute-component"]], selectors: [["my-host-attribute-component"]],
hostBindings: function HostAttributeComp_HostBindings(rf, ctx, elIndex) { hostAttrs: ["title", "hello there from component", ${AttributeMarker.Styles}, "opacity", "1"],
if (rf & 1) {
$r3$.ɵɵelementHostAttrs($c0$);
}
}
HostAttributeDir.ɵdir = $r3$.ɵɵdefineDirective({ HostAttributeDir.ɵdir = $r3$.ɵɵdefineDirective({
type: HostAttributeDir, type: HostAttributeDir,
selectors: [["", "hostAttributeDir", ""]], selectors: [["", "hostAttributeDir", ""]],
hostAttrs: ["title", "hello there from directive", ${AttributeMarker.Classes}, "one", "two", ${AttributeMarker.Styles}, "width", "200px", "height", "500px"],
hostVars: 2,
hostBindings: function HostAttributeDir_HostBindings(rf, ctx, elIndex) { hostBindings: function HostAttributeDir_HostBindings(rf, ctx, elIndex) {
if (rf & 1) {
$r3$.ɵɵallocHostVars(2);
$r3$.ɵɵelementHostAttrs($c1$);
}
} }
`; `;
const result = compile(files, angularFiles); const result = compile(files, angularFiles);
const source = result.source; const source = result.source;

View File

@ -333,9 +333,9 @@ describe('compiler compliance: styling', () => {
const template = ` const template = `
MyAnimDir.ɵdir = $r3$.ɵɵdefineDirective({ MyAnimDir.ɵdir = $r3$.ɵɵdefineDirective({
hostVars: 1,
hostBindings: function MyAnimDir_HostBindings(rf, ctx, elIndex) { hostBindings: function MyAnimDir_HostBindings(rf, ctx, elIndex) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵɵallocHostVars(1);
$r3$.ɵɵcomponentHostSyntheticListener("@myAnim.start", function MyAnimDir_animation_myAnim_start_HostBindingHandler($event) { return ctx.onStart(); })("@myAnim.done", function MyAnimDir_animation_myAnim_done_HostBindingHandler($event) { return ctx.onDone(); }); $r3$.ɵɵcomponentHostSyntheticListener("@myAnim.start", function MyAnimDir_animation_myAnim_start_HostBindingHandler($event) { return ctx.onStart(); })("@myAnim.done", function MyAnimDir_animation_myAnim_done_HostBindingHandler($event) { return ctx.onDone(); });
} if (rf & 2) { } if (rf & 2) {
$r3$.ɵɵupdateSyntheticHostBinding("@myAnim", ctx.myAnimState); $r3$.ɵɵupdateSyntheticHostBinding("@myAnim", ctx.myAnimState);
@ -1011,11 +1011,9 @@ describe('compiler compliance: styling', () => {
}; };
const template = ` const template = `
hostAttrs: [${AttributeMarker.Classes}, "foo", "baz", ${AttributeMarker.Styles}, "width", "200px", "height", "500px"],
hostVars: 6,
hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) { hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) {
if (rf & 1) {
$r3$.ɵɵallocHostVars(6);
$r3$.ɵɵelementHostAttrs($e0_attrs$);
}
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer); $r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵstyleMap(ctx.myStyle); $r3$.ɵɵstyleMap(ctx.myStyle);
@ -1070,10 +1068,8 @@ describe('compiler compliance: styling', () => {
}; };
const template = ` const template = `
hostVars: 8,
hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) { hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) {
if (rf & 1) {
$r3$.ɵɵallocHostVars(8);
}
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer); $r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵstyleMap(ctx.myStyle); $r3$.ɵɵstyleMap(ctx.myStyle);
@ -1143,10 +1139,8 @@ describe('compiler compliance: styling', () => {
`; `;
const hostBindings = ` const hostBindings = `
hostVars: 6,
hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) { hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) {
if (rf & 1) {
$r3$.ɵɵallocHostVars(6);
}
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer); $r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵstyleMap(ctx.myStyleExp); $r3$.ɵɵstyleMap(ctx.myStyleExp);
@ -1209,29 +1203,23 @@ describe('compiler compliance: styling', () => {
// NOTE: IF YOU ARE CHANGING THIS COMPILER SPEC, YOU MAY NEED TO CHANGE THE DIRECTIVE // NOTE: IF YOU ARE CHANGING THIS COMPILER SPEC, YOU MAY NEED TO CHANGE THE DIRECTIVE
// DEF THAT'S HARD-CODED IN `ng_class.ts`. // DEF THAT'S HARD-CODED IN `ng_class.ts`.
const template = ` const template = `
function ClassDirective_HostBindings(rf, ctx, elIndex) { hostVars: 2,
if (rf & 1) { hostBindings: function ClassDirective_HostBindings(rf, ctx, elIndex) {
$r3$.ɵɵallocHostVars(2);
}
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵclassMap(ctx.myClassMap); $r3$.ɵɵclassMap(ctx.myClassMap);
} }
} }
function WidthDirective_HostBindings(rf, ctx, elIndex) { hostVars: 2,
if (rf & 1) { hostBindings: function WidthDirective_HostBindings(rf, ctx, elIndex) {
$r3$.ɵɵallocHostVars(2);
}
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵstyleProp("width", ctx.myWidth); $r3$.ɵɵstyleProp("width", ctx.myWidth);
$r3$.ɵɵclassProp("foo", ctx.myFooClass); $r3$.ɵɵclassProp("foo", ctx.myFooClass);
} }
} }
function HeightDirective_HostBindings(rf, ctx, elIndex) { hostVars: 2,
if (rf & 1) { hostBindings: function HeightDirective_HostBindings(rf, ctx, elIndex) {
$r3$.ɵɵallocHostVars(2);
}
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵstyleProp("height", ctx.myHeight); $r3$.ɵɵstyleProp("height", ctx.myHeight);
$r3$.ɵɵclassProp("bar", ctx.myBarClass); $r3$.ɵɵclassProp("bar", ctx.myBarClass);
@ -1840,13 +1828,9 @@ describe('compiler compliance: styling', () => {
}; };
const template = ` const template = `
const $_c0$ = ["title", "foo title", ${AttributeMarker.Classes}, "foo", "baz", ${AttributeMarker.Styles}, "width", "200px", "height", "500px"]; hostAttrs: ["title", "foo title", ${AttributeMarker.Classes}, "foo", "baz", ${AttributeMarker.Styles}, "width", "200px", "height", "500px"],
hostVars: 6,
hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) { hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) {
if (rf & 1) {
$r3$.ɵɵallocHostVars(6);
$r3$.ɵɵelementHostAttrs($_c0$);
}
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵhostProperty("id", ctx.id)("title", ctx.title); $r3$.ɵɵhostProperty("id", ctx.id)("title", ctx.title);
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer); $r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
@ -1885,10 +1869,8 @@ describe('compiler compliance: styling', () => {
}; };
const template = ` const template = `
hostBindings: function WidthDirective_HostBindings(rf, ctx, elIndex) { hostVars: 4,
if (rf & 1) { hostBindings: function WidthDirective_HostBindings(rf, ctx, elIndex) {
$r3$.ɵɵallocHostVars(4);
}
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵhostProperty("id", ctx.id)("title", ctx.title); $r3$.ɵɵhostProperty("id", ctx.id)("title", ctx.title);
$r3$.ɵɵstyleProp("width", ctx.myWidth); $r3$.ɵɵstyleProp("width", ctx.myWidth);
@ -2051,10 +2033,8 @@ describe('compiler compliance: styling', () => {
}; };
const template = ` const template = `
hostVars: 9,
hostBindings: function MyDir_HostBindings(rf, ctx, elIndex) { hostBindings: function MyDir_HostBindings(rf, ctx, elIndex) {
$r3$.ɵɵallocHostVars(9);
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵhostProperty("title", ctx.title); $r3$.ɵɵhostProperty("title", ctx.title);
$r3$.ɵɵupdateSyntheticHostBinding("@anim", $r3$.ɵɵupdateSyntheticHostBinding("@anim",

View File

@ -2340,9 +2340,9 @@ runInEachFileSystem(os => {
env.driveMain(); env.driveMain();
const jsContents = env.getContents('test.js'); const jsContents = env.getContents('test.js');
const hostBindingsFn = ` const hostBindingsFn = `
hostVars: 3,
hostBindings: function FooCmp_HostBindings(rf, ctx, elIndex) { hostBindings: function FooCmp_HostBindings(rf, ctx, elIndex) {
if (rf & 1) { if (rf & 1) {
i0.ɵɵallocHostVars(3);
i0.ɵɵlistener("click", function FooCmp_click_HostBindingHandler($event) { return ctx.onClick($event); })("click", function FooCmp_click_HostBindingHandler($event) { return ctx.onBodyClick($event); }, false, i0.ɵɵresolveBody)("change", function FooCmp_change_HostBindingHandler($event) { return ctx.onChange(ctx.arg1, ctx.arg2, ctx.arg3); }); i0.ɵɵlistener("click", function FooCmp_click_HostBindingHandler($event) { return ctx.onClick($event); })("click", function FooCmp_click_HostBindingHandler($event) { return ctx.onBodyClick($event); }, false, i0.ɵɵresolveBody)("change", function FooCmp_change_HostBindingHandler($event) { return ctx.onChange(ctx.arg1, ctx.arg2, ctx.arg3); });
} }
if (rf & 2) { if (rf & 2) {
@ -2376,7 +2376,7 @@ runInEachFileSystem(os => {
`); `);
env.driveMain(); env.driveMain();
const jsContents = env.getContents('test.js'); const jsContents = env.getContents('test.js');
expect(jsContents).toContain('i0.ɵɵelementHostAttrs(["test", test])'); expect(jsContents).toContain('hostAttrs: ["test", test]');
}); });
it('should accept enum values as host bindings', () => { it('should accept enum values as host bindings', () => {
@ -4262,10 +4262,8 @@ runInEachFileSystem(os => {
env.driveMain(); env.driveMain();
const jsContents = env.getContents('test.js'); const jsContents = env.getContents('test.js');
const hostBindingsFn = ` const hostBindingsFn = `
hostVars: 6,
hostBindings: function UnsafeAttrsDirective_HostBindings(rf, ctx, elIndex) { hostBindings: function UnsafeAttrsDirective_HostBindings(rf, ctx, elIndex) {
if (rf & 1) {
i0.ɵɵallocHostVars(6);
}
if (rf & 2) { if (rf & 2) {
i0.ɵɵattribute("href", ctx.attrHref, i0.ɵɵsanitizeUrlOrResourceUrl)("src", ctx.attrSrc, i0.ɵɵsanitizeUrlOrResourceUrl)("action", ctx.attrAction, i0.ɵɵsanitizeUrl)("profile", ctx.attrProfile, i0.ɵɵsanitizeResourceUrl)("innerHTML", ctx.attrInnerHTML, i0.ɵɵsanitizeHtml)("title", ctx.attrSafeTitle); i0.ɵɵattribute("href", ctx.attrHref, i0.ɵɵsanitizeUrlOrResourceUrl)("src", ctx.attrSrc, i0.ɵɵsanitizeUrlOrResourceUrl)("action", ctx.attrAction, i0.ɵɵsanitizeUrl)("profile", ctx.attrProfile, i0.ɵɵsanitizeResourceUrl)("innerHTML", ctx.attrInnerHTML, i0.ɵɵsanitizeHtml)("title", ctx.attrSafeTitle);
} }
@ -4312,10 +4310,8 @@ runInEachFileSystem(os => {
env.driveMain(); env.driveMain();
const jsContents = env.getContents('test.js'); const jsContents = env.getContents('test.js');
const hostBindingsFn = ` const hostBindingsFn = `
hostVars: 6,
hostBindings: function UnsafePropsDirective_HostBindings(rf, ctx, elIndex) { hostBindings: function UnsafePropsDirective_HostBindings(rf, ctx, elIndex) {
if (rf & 1) {
i0.ɵɵallocHostVars(6);
}
if (rf & 2) { if (rf & 2) {
i0.ɵɵhostProperty("href", ctx.propHref, i0.ɵɵsanitizeUrlOrResourceUrl)("src", ctx.propSrc, i0.ɵɵsanitizeUrlOrResourceUrl)("action", ctx.propAction, i0.ɵɵsanitizeUrl)("profile", ctx.propProfile, i0.ɵɵsanitizeResourceUrl)("innerHTML", ctx.propInnerHTML, i0.ɵɵsanitizeHtml)("title", ctx.propSafeTitle); i0.ɵɵhostProperty("href", ctx.propHref, i0.ɵɵsanitizeUrlOrResourceUrl)("src", ctx.propSrc, i0.ɵɵsanitizeUrlOrResourceUrl)("action", ctx.propAction, i0.ɵɵsanitizeUrl)("profile", ctx.propProfile, i0.ɵɵsanitizeResourceUrl)("innerHTML", ctx.propInnerHTML, i0.ɵɵsanitizeHtml)("title", ctx.propSafeTitle);
} }
@ -4347,10 +4343,8 @@ runInEachFileSystem(os => {
env.driveMain(); env.driveMain();
const jsContents = env.getContents('test.js'); const jsContents = env.getContents('test.js');
const hostBindingsFn = ` const hostBindingsFn = `
hostVars: 6,
hostBindings: function FooCmp_HostBindings(rf, ctx, elIndex) { hostBindings: function FooCmp_HostBindings(rf, ctx, elIndex) {
if (rf & 1) {
i0.ɵɵallocHostVars(6);
}
if (rf & 2) { if (rf & 2) {
i0.ɵɵhostProperty("src", ctx.srcProp)("href", ctx.hrefProp)("title", ctx.titleProp); i0.ɵɵhostProperty("src", ctx.srcProp)("href", ctx.hrefProp)("title", ctx.titleProp);
i0.ɵɵattribute("src", ctx.srcAttr)("href", ctx.hrefAttr)("title", ctx.titleAttr); i0.ɵɵattribute("src", ctx.srcAttr)("href", ctx.hrefAttr)("title", ctx.titleAttr);

View File

@ -115,8 +115,6 @@ export class Identifiers {
static styleSanitizer: o.ExternalReference = {name: 'ɵɵstyleSanitizer', moduleName: CORE}; static styleSanitizer: o.ExternalReference = {name: 'ɵɵstyleSanitizer', moduleName: CORE};
static elementHostAttrs: o.ExternalReference = {name: 'ɵɵelementHostAttrs', moduleName: CORE};
static containerCreate: o.ExternalReference = {name: 'ɵɵcontainer', moduleName: CORE}; static containerCreate: o.ExternalReference = {name: 'ɵɵcontainer', moduleName: CORE};
static nextContext: o.ExternalReference = {name: 'ɵɵnextContext', moduleName: CORE}; static nextContext: o.ExternalReference = {name: 'ɵɵnextContext', moduleName: CORE};
@ -129,8 +127,6 @@ export class Identifiers {
static disableBindings: o.ExternalReference = {name: 'ɵɵdisableBindings', moduleName: CORE}; static disableBindings: o.ExternalReference = {name: 'ɵɵdisableBindings', moduleName: CORE};
static allocHostVars: o.ExternalReference = {name: 'ɵɵallocHostVars', moduleName: CORE};
static getCurrentView: o.ExternalReference = {name: 'ɵɵgetCurrentView', moduleName: CORE}; static getCurrentView: o.ExternalReference = {name: 'ɵɵgetCurrentView', moduleName: CORE};
static textInterpolate: o.ExternalReference = {name: 'ɵɵtextInterpolate', moduleName: CORE}; static textInterpolate: o.ExternalReference = {name: 'ɵɵtextInterpolate', moduleName: CORE};

View File

@ -67,7 +67,7 @@ function baseDirectiveFields(
definitionMap.set( definitionMap.set(
'hostBindings', createHostBindingsFunction( 'hostBindings', createHostBindingsFunction(
meta.host, meta.typeSourceSpan, bindingParser, constantPool, meta.host, meta.typeSourceSpan, bindingParser, constantPool,
meta.selector || '', meta.name)); meta.selector || '', meta.name, definitionMap));
// e.g 'inputs: {a: 'a'}` // e.g 'inputs: {a: 'a'}`
definitionMap.set('inputs', conditionallyCreateMapObjectLiteral(meta.inputs, true)); definitionMap.set('inputs', conditionallyCreateMapObjectLiteral(meta.inputs, true));
@ -528,8 +528,8 @@ function createViewQueriesFunction(
// Return a host binding function or null if one is not necessary. // Return a host binding function or null if one is not necessary.
function createHostBindingsFunction( function createHostBindingsFunction(
hostBindingsMetadata: R3HostMetadata, typeSourceSpan: ParseSourceSpan, hostBindingsMetadata: R3HostMetadata, typeSourceSpan: ParseSourceSpan,
bindingParser: BindingParser, constantPool: ConstantPool, selector: string, bindingParser: BindingParser, constantPool: ConstantPool, selector: string, name: string,
name?: string): o.Expression|null { definitionMap: DefinitionMap): o.Expression|null {
// Initialize hostVarsCount to number of bound host properties (interpolations illegal) // Initialize hostVarsCount to number of bound host properties (interpolations illegal)
const hostVarsCount = Object.keys(hostBindingsMetadata.properties).length; const hostVarsCount = Object.keys(hostBindingsMetadata.properties).length;
const elVarExp = o.variable('elIndex'); const elVarExp = o.variable('elIndex');
@ -651,14 +651,7 @@ function createHostBindingsFunction(
// to the host element alongside any of the provided host attributes that were // to the host element alongside any of the provided host attributes that were
// collected earlier. // collected earlier.
const hostAttrs = convertAttributesToExpressions(hostBindingsMetadata.attributes); const hostAttrs = convertAttributesToExpressions(hostBindingsMetadata.attributes);
const hostInstruction = styleBuilder.buildHostAttrsInstruction(null, hostAttrs, constantPool); styleBuilder.assignHostAttrs(hostAttrs, definitionMap);
if (hostInstruction && hostInstruction.calls.length > 0) {
createStatements.push(
chainedInstruction(
hostInstruction.reference,
hostInstruction.calls.map(call => convertStylingCall(call, bindingContext, bindingFn)))
.toStmt());
}
if (styleBuilder.hasBindings) { if (styleBuilder.hasBindings) {
// finally each binding that was registered in the statement above will need to be added to // finally each binding that was registered in the statement above will need to be added to
@ -681,8 +674,7 @@ function createHostBindingsFunction(
} }
if (totalHostVarsCount) { if (totalHostVarsCount) {
createStatements.unshift( definitionMap.set('hostVars', o.literal(totalHostVarsCount));
o.importExpr(R3.allocHostVars).callFn([o.literal(totalHostVarsCount)]).toStmt());
} }
if (createStatements.length > 0 || updateStatements.length > 0) { if (createStatements.length > 0 || updateStatements.length > 0) {

View File

@ -16,7 +16,7 @@ import {Identifiers as R3} from '../r3_identifiers';
import {hyphenate, parse as parseStyle} from './style_parser'; import {hyphenate, parse as parseStyle} from './style_parser';
import {ValueConverter} from './template'; import {ValueConverter} from './template';
import {getInterpolationArgsLength} from './util'; import {DefinitionMap, getInterpolationArgsLength} from './util';
const IMPORTANT_FLAG = '!important'; const IMPORTANT_FLAG = '!important';
@ -279,27 +279,11 @@ export class StylingBuilder {
* responsible for registering initial styles (within a directive hostBindings' creation block), * responsible for registering initial styles (within a directive hostBindings' creation block),
* as well as any of the provided attribute values, to the directive host element. * as well as any of the provided attribute values, to the directive host element.
*/ */
buildHostAttrsInstruction( assignHostAttrs(attrs: o.Expression[], definitionMap: DefinitionMap): void {
sourceSpan: ParseSourceSpan|null, attrs: o.Expression[],
constantPool: ConstantPool): StylingInstruction|null {
if (this._directiveExpr && (attrs.length || this._hasInitialValues)) { if (this._directiveExpr && (attrs.length || this._hasInitialValues)) {
return { this.populateInitialStylingAttrs(attrs);
reference: R3.elementHostAttrs, definitionMap.set('hostAttrs', o.literalArr(attrs));
calls: [{
sourceSpan,
allocateBindingSlots: 0,
params: () => {
// params => elementHostAttrs(attrs)
this.populateInitialStylingAttrs(attrs);
const attrArray = !attrs.some(attr => attr instanceof o.WrappedNodeExpr) ?
getConstantLiteralFromArray(constantPool, attrs) :
o.literalArr(attrs);
return [attrArray];
}
}]
};
} }
return null;
} }
/** /**

View File

@ -117,7 +117,6 @@ export {
ɵɵreference, ɵɵreference,
ɵɵenableBindings, ɵɵenableBindings,
ɵɵdisableBindings, ɵɵdisableBindings,
ɵɵallocHostVars,
ɵɵelementContainerStart, ɵɵelementContainerStart,
ɵɵelementContainerEnd, ɵɵelementContainerEnd,
ɵɵelementContainer, ɵɵelementContainer,
@ -144,7 +143,6 @@ export {
ɵɵstylePropInterpolate8, ɵɵstylePropInterpolate8,
ɵɵstylePropInterpolateV, ɵɵstylePropInterpolateV,
ɵɵclassProp, ɵɵclassProp,
ɵɵelementHostAttrs,
ɵɵselect, ɵɵselect,
ɵɵadvance, ɵɵadvance,

View File

@ -37,3 +37,32 @@ export interface Type<T> extends Function { new (...args: any[]): T; }
export type Mutable<T extends{[x: string]: any}, K extends string> = { export type Mutable<T extends{[x: string]: any}, K extends string> = {
[P in K]: T[P]; [P in K]: T[P];
}; };
/**
* Returns a writable type version of type.
*
* USAGE:
* Given:
* ```
* interface Person {readonly name: string}
* ```
*
* We would like to get a read/write version of `Person`.
* ```
* const WritablePerson = Writable<Person>;
* ```
*
* The result is that you can do:
*
* ```
* const readonlyPerson: Person = {name: 'Marry'};
* readonlyPerson.name = 'John'; // TypeError
* (readonlyPerson as WritablePerson).name = 'John'; // OK
*
* // Error: Correctly detects that `Person` did not have `age` property.
* (readonlyPerson as WritablePerson).age = 30;
* ```
*/
export type Writable<T> = {
-readonly[K in keyof T]: T[K];
};

View File

@ -114,7 +114,7 @@ export function renderComponent<T>(
const rendererFactory = opts.rendererFactory || domRendererFactory3; const rendererFactory = opts.rendererFactory || domRendererFactory3;
const sanitizer = opts.sanitizer || null; const sanitizer = opts.sanitizer || null;
const componentDef = getComponentDef<T>(componentType) !; const componentDef = getComponentDef<T>(componentType) !;
if (componentDef.type != componentType) componentDef.type = componentType; if (componentDef.type != componentType) (componentDef as{type: Type<any>}).type = componentType;
// The first index of the first selector is the tag name. // The first index of the first selector is the tag name.
const componentTag = componentDef.selectors ![0] ![0] as string; const componentTag = componentDef.selectors ![0] ![0] as string;
@ -211,7 +211,13 @@ export function createRootComponent<T>(
} }
const rootTNode = getPreviousOrParentTNode(); const rootTNode = getPreviousOrParentTNode();
if (tView.firstCreatePass && componentDef.hostBindings) { // TODO(misko-next): This is a temporary work around for the fact that we moved the information
// from instruction to declaration. The workaround is to just call the instruction as if it was
// part of the `hostAttrs`.
// The check for componentDef.hostBindings is wrong since now some directives may not
// have componentDef.hostBindings but they still need to process hostVars and hostAttrs
if (tView.firstCreatePass && (componentDef.hostBindings || componentDef.hostVars !== 0 ||
componentDef.hostAttrs !== null)) {
const elementIndex = rootTNode.index - HEADER_OFFSET; const elementIndex = rootTNode.index - HEADER_OFFSET;
setActiveHostElement(elementIndex); setActiveHostElement(elementIndex);
incrementActiveDirectiveId(); incrementActiveDirectiveId();

View File

@ -18,14 +18,17 @@ import {stringify} from '../util/stringify';
import {EMPTY_ARRAY, EMPTY_OBJ} from './empty'; import {EMPTY_ARRAY, EMPTY_OBJ} from './empty';
import {NG_COMP_DEF, NG_DIR_DEF, NG_FACTORY_DEF, NG_LOC_ID_DEF, NG_MOD_DEF, NG_PIPE_DEF} from './fields'; import {NG_COMP_DEF, NG_DIR_DEF, NG_FACTORY_DEF, NG_LOC_ID_DEF, NG_MOD_DEF, NG_PIPE_DEF} from './fields';
import {ComponentDef, ComponentDefFeature, ComponentTemplate, ComponentType, ContentQueriesFunction, DirectiveDef, DirectiveDefFeature, DirectiveTypesOrFactory, FactoryFn, HostBindingsFunction, PipeDef, PipeType, PipeTypesOrFactory, ViewQueriesFunction} from './interfaces/definition'; import {ComponentDef, ComponentDefFeature, ComponentTemplate, ComponentType, ContentQueriesFunction, DirectiveDef, DirectiveDefFeature, DirectiveTypesOrFactory, FactoryFn, HostBindingsFunction, PipeDef, PipeType, PipeTypesOrFactory, ViewQueriesFunction} from './interfaces/definition';
import {TConstants} from './interfaces/node'; import {AttributeMarker, TAttributes, TConstants} from './interfaces/node';
// while SelectorFlags is unused here, it's required so that types don't get resolved lazily
// see: https://github.com/Microsoft/web-build-tools/issues/1050
import {CssSelectorList, SelectorFlags} from './interfaces/projection'; import {CssSelectorList, SelectorFlags} from './interfaces/projection';
import {NgModuleType} from './ng_module_ref'; import {NgModuleType} from './ng_module_ref';
let _renderCompCount = 0; let _renderCompCount = 0;
// While these types are unused here, they are required so that types don't
// get resolved lazily. see: https://github.com/Microsoft/web-build-tools/issues/1050
type _web_build_tools_issue_1050_SelectorFlags = SelectorFlags;
type _web_build_tools_issue_1050_AttributeMarker = AttributeMarker;
/** /**
* Create a component definition object. * Create a component definition object.
* *
@ -130,6 +133,46 @@ export function ɵɵdefineComponent<T>(componentDefinition: {
*/ */
hostBindings?: HostBindingsFunction<T>; hostBindings?: HostBindingsFunction<T>;
/**
* The number of bindings in this directive `hostBindings` (including pure fn bindings).
*
* Used to calculate the length of the component's LView array, so we
* can pre-fill the array and set the host binding start index.
*/
hostVars?: number;
/**
* Assign static attribute values to a host element.
*
* This property will assign static attribute values as well as class and style
* values to a host element. Since attribute values can consist of different types of values, the
* `hostAttrs` array must include the values in the following format:
*
* attrs = [
* // static attributes (like `title`, `name`, `id`...)
* attr1, value1, attr2, value,
*
* // a single namespace value (like `x:id`)
* NAMESPACE_MARKER, namespaceUri1, name1, value1,
*
* // another single namespace value (like `x:name`)
* NAMESPACE_MARKER, namespaceUri2, name2, value2,
*
* // a series of CSS classes that will be applied to the element (no spaces)
* CLASSES_MARKER, class1, class2, class3,
*
* // a series of CSS styles (property + value) that will be applied to the element
* STYLES_MARKER, prop1, value1, prop2, value2
* ]
*
* All non-class and non-style attributes must be defined at the start of the list
* first before all class and style values are set. When there is a change in value
* type (like when classes and styles are introduced) a marker must be used to separate
* the entries. The marker values themselves are set via entries found in the
* [AttributeMarker] enum.
*/
hostAttrs?: TAttributes;
/** /**
* Function to create instances of content queries associated with a given directive. * Function to create instances of content queries associated with a given directive.
*/ */
@ -263,6 +306,8 @@ export function ɵɵdefineComponent<T>(componentDefinition: {
consts: componentDefinition.consts || null, consts: componentDefinition.consts || null,
ngContentSelectors: componentDefinition.ngContentSelectors, ngContentSelectors: componentDefinition.ngContentSelectors,
hostBindings: componentDefinition.hostBindings || null, hostBindings: componentDefinition.hostBindings || null,
hostVars: componentDefinition.hostVars || 0,
hostAttrs: componentDefinition.hostAttrs || null,
contentQueries: componentDefinition.contentQueries || null, contentQueries: componentDefinition.contentQueries || null,
declaredInputs: declaredInputs, declaredInputs: declaredInputs,
inputs: null !, // assigned in noSideEffects inputs: null !, // assigned in noSideEffects
@ -588,6 +633,46 @@ export const ɵɵdefineDirective = ɵɵdefineComponent as any as<T>(directiveDef
*/ */
hostBindings?: HostBindingsFunction<T>; hostBindings?: HostBindingsFunction<T>;
/**
* The number of bindings in this directive `hostBindings` (including pure fn bindings).
*
* Used to calculate the length of the component's LView array, so we
* can pre-fill the array and set the host binding start index.
*/
hostVars?: number;
/**
* Assign static attribute values to a host element.
*
* This property will assign static attribute values as well as class and style
* values to a host element. Since attribute values can consist of different types of values, the
* `hostAttrs` array must include the values in the following format:
*
* attrs = [
* // static attributes (like `title`, `name`, `id`...)
* attr1, value1, attr2, value,
*
* // a single namespace value (like `x:id`)
* NAMESPACE_MARKER, namespaceUri1, name1, value1,
*
* // another single namespace value (like `x:name`)
* NAMESPACE_MARKER, namespaceUri2, name2, value2,
*
* // a series of CSS classes that will be applied to the element (no spaces)
* CLASSES_MARKER, class1, class2, class3,
*
* // a series of CSS styles (property + value) that will be applied to the element
* STYLES_MARKER, prop1, value1, prop2, value2
* ]
*
* All non-class and non-style attributes must be defined at the start of the list
* first before all class and style values are set. When there is a change in value
* type (like when classes and styles are introduced) a marker must be used to separate
* the entries. The marker values themselves are set via entries found in the
* [AttributeMarker] enum.
*/
hostAttrs?: TAttributes;
/** /**
* Function to create instances of content queries associated with a given directive. * Function to create instances of content queries associated with a given directive.
*/ */

View File

@ -55,6 +55,8 @@ export function throwErrorIfNoChangesMode(
} }
// TODO: include debug context, see `viewDebugError` function in // TODO: include debug context, see `viewDebugError` function in
// `packages/core/src/view/errors.ts` for reference. // `packages/core/src/view/errors.ts` for reference.
// tslint:disable-next-line
debugger; // Left intentionally for better debugger experience.
throw new Error(msg); throw new Error(msg);
} }

View File

@ -6,17 +6,22 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Type} from '../../interface/type'; import {Type, Writable} from '../../interface/type';
import {assertEqual} from '../../util/assert';
import {fillProperties} from '../../util/property'; import {fillProperties} from '../../util/property';
import {EMPTY_ARRAY, EMPTY_OBJ} from '../empty'; import {EMPTY_ARRAY, EMPTY_OBJ} from '../empty';
import {ComponentDef, ContentQueriesFunction, DirectiveDef, DirectiveDefFeature, HostBindingsFunction, RenderFlags, ViewQueriesFunction} from '../interfaces/definition'; import {ComponentDef, ContentQueriesFunction, DirectiveDef, DirectiveDefFeature, HostBindingsFunction, RenderFlags, ViewQueriesFunction} from '../interfaces/definition';
import {AttributeMarker, TAttributes} from '../interfaces/node';
import {isComponentDef} from '../interfaces/type_checks'; import {isComponentDef} from '../interfaces/type_checks';
import {mergeHostAttrs} from '../util/attrs_utils';
export function getSuperType(type: Type<any>): Type<any>& export function getSuperType(type: Type<any>): Type<any>&
{ɵcmp?: ComponentDef<any>, ɵdir?: DirectiveDef<any>} { {ɵcmp?: ComponentDef<any>, ɵdir?: DirectiveDef<any>} {
return Object.getPrototypeOf(type.prototype).constructor; return Object.getPrototypeOf(type.prototype).constructor;
} }
type WritableDef = Writable<DirectiveDef<any>|ComponentDef<any>>;
/** /**
* Merges the definition from a super class to a sub class. * Merges the definition from a super class to a sub class.
* @param definition The definition that is a SubClass of another directive of component * @param definition The definition that is a SubClass of another directive of component
@ -26,6 +31,7 @@ export function getSuperType(type: Type<any>): Type<any>&
export function ɵɵInheritDefinitionFeature(definition: DirectiveDef<any>| ComponentDef<any>): void { export function ɵɵInheritDefinitionFeature(definition: DirectiveDef<any>| ComponentDef<any>): void {
let superType = getSuperType(definition.type); let superType = getSuperType(definition.type);
let shouldInheritFields = true; let shouldInheritFields = true;
const inheritanceChain: WritableDef[] = [definition];
while (superType) { while (superType) {
let superDef: DirectiveDef<any>|ComponentDef<any>|undefined = undefined; let superDef: DirectiveDef<any>|ComponentDef<any>|undefined = undefined;
@ -42,9 +48,10 @@ export function ɵɵInheritDefinitionFeature(definition: DirectiveDef<any>| Comp
if (superDef) { if (superDef) {
if (shouldInheritFields) { if (shouldInheritFields) {
inheritanceChain.push(superDef);
// Some fields in the definition may be empty, if there were no values to put in them that // Some fields in the definition may be empty, if there were no values to put in them that
// would've justified object creation. Unwrap them if necessary. // would've justified object creation. Unwrap them if necessary.
const writeableDef = definition as any; const writeableDef = definition as WritableDef;
writeableDef.inputs = maybeUnwrapEmpty(definition.inputs); writeableDef.inputs = maybeUnwrapEmpty(definition.inputs);
writeableDef.declaredInputs = maybeUnwrapEmpty(definition.declaredInputs); writeableDef.declaredInputs = maybeUnwrapEmpty(definition.declaredInputs);
writeableDef.outputs = maybeUnwrapEmpty(definition.outputs); writeableDef.outputs = maybeUnwrapEmpty(definition.outputs);
@ -66,14 +73,14 @@ export function ɵɵInheritDefinitionFeature(definition: DirectiveDef<any>| Comp
// Inherit hooks // Inherit hooks
// Assume super class inheritance feature has already run. // Assume super class inheritance feature has already run.
definition.afterContentChecked = writeableDef.afterContentChecked =
definition.afterContentChecked || superDef.afterContentChecked; writeableDef.afterContentChecked || superDef.afterContentChecked;
definition.afterContentInit = definition.afterContentInit || superDef.afterContentInit; writeableDef.afterContentInit = definition.afterContentInit || superDef.afterContentInit;
definition.afterViewChecked = definition.afterViewChecked || superDef.afterViewChecked; writeableDef.afterViewChecked = definition.afterViewChecked || superDef.afterViewChecked;
definition.afterViewInit = definition.afterViewInit || superDef.afterViewInit; writeableDef.afterViewInit = definition.afterViewInit || superDef.afterViewInit;
definition.doCheck = definition.doCheck || superDef.doCheck; writeableDef.doCheck = definition.doCheck || superDef.doCheck;
definition.onDestroy = definition.onDestroy || superDef.onDestroy; writeableDef.onDestroy = definition.onDestroy || superDef.onDestroy;
definition.onInit = definition.onInit || superDef.onInit; writeableDef.onInit = definition.onInit || superDef.onInit;
} }
// Run parent features // Run parent features
@ -100,6 +107,28 @@ export function ɵɵInheritDefinitionFeature(definition: DirectiveDef<any>| Comp
superType = Object.getPrototypeOf(superType); superType = Object.getPrototypeOf(superType);
} }
mergeHostAttrsAcrossInheritance(inheritanceChain);
}
/**
* Merge the `hostAttrs` and `hostVars` from the inherited parent to the base class.
*
* @param inheritanceChain A list of `WritableDefs` starting at the top most type and listing
* sub-types in order. For each type take the `hostAttrs` and `hostVars` and merge it with the child
* type.
*/
function mergeHostAttrsAcrossInheritance(inheritanceChain: WritableDef[]) {
let hostVars: number = 0;
let hostAttrs: TAttributes|null = null;
// We process the inheritance order from the base to the leaves here.
for (let i = inheritanceChain.length - 1; i >= 0; i--) {
const def = inheritanceChain[i];
// For each `hostVars`, we need to add the superclass amount.
def.hostVars = (hostVars += def.hostVars);
// for each `hostAttrs` we need to merge it with superclass.
def.hostAttrs =
mergeHostAttrs(def.hostAttrs, hostAttrs = mergeHostAttrs(hostAttrs, def.hostAttrs));
}
} }
function maybeUnwrapEmpty<T>(value: T[]): T[]; function maybeUnwrapEmpty<T>(value: T[]): T[];
@ -114,8 +143,7 @@ function maybeUnwrapEmpty(value: any): any {
} }
} }
function inheritViewQuery( function inheritViewQuery(definition: WritableDef, superViewQuery: ViewQueriesFunction<any>) {
definition: DirectiveDef<any>| ComponentDef<any>, superViewQuery: ViewQueriesFunction<any>) {
const prevViewQuery = definition.viewQuery; const prevViewQuery = definition.viewQuery;
if (prevViewQuery) { if (prevViewQuery) {
definition.viewQuery = (rf, ctx) => { definition.viewQuery = (rf, ctx) => {
@ -128,8 +156,7 @@ function inheritViewQuery(
} }
function inheritContentQueries( function inheritContentQueries(
definition: DirectiveDef<any>| ComponentDef<any>, definition: WritableDef, superContentQueries: ContentQueriesFunction<any>) {
superContentQueries: ContentQueriesFunction<any>) {
const prevContentQueries = definition.contentQueries; const prevContentQueries = definition.contentQueries;
if (prevContentQueries) { if (prevContentQueries) {
definition.contentQueries = (rf, ctx, directiveIndex) => { definition.contentQueries = (rf, ctx, directiveIndex) => {
@ -142,8 +169,7 @@ function inheritContentQueries(
} }
function inheritHostBindings( function inheritHostBindings(
definition: DirectiveDef<any>| ComponentDef<any>, definition: WritableDef, superHostBindings: HostBindingsFunction<any>) {
superHostBindings: HostBindingsFunction<any>) {
const prevHostBindings = definition.hostBindings; const prevHostBindings = definition.hostBindings;
if (prevHostBindings) { if (prevHostBindings) {
definition.hostBindings = (rf: RenderFlags, ctx: any, elementIndex: number) => { definition.hostBindings = (rf: RenderFlags, ctx: any, elementIndex: number) => {

View File

@ -51,7 +51,7 @@ export function ɵɵNgOnChangesFeature<T>(): DirectiveDefFeature {
function NgOnChangesFeatureImpl<T>(definition: DirectiveDef<T>): void { function NgOnChangesFeatureImpl<T>(definition: DirectiveDef<T>): void {
if (definition.type.prototype.ngOnChanges) { if (definition.type.prototype.ngOnChanges) {
definition.setInput = ngOnChangesSetInput; definition.setInput = ngOnChangesSetInput;
definition.onChanges = wrapOnChanges(); (definition as{onChanges: Function}).onChanges = wrapOnChanges();
} }
} }

View File

@ -24,8 +24,6 @@ export {
store, store,
tick, tick,
ɵɵallocHostVars,
ɵɵattribute, ɵɵattribute,
ɵɵattributeInterpolate1, ɵɵattributeInterpolate1,
ɵɵattributeInterpolate2, ɵɵattributeInterpolate2,
@ -65,7 +63,6 @@ export {
ɵɵelementContainerStart, ɵɵelementContainerStart,
ɵɵelementEnd, ɵɵelementEnd,
ɵɵelementHostAttrs,
ɵɵelementStart, ɵɵelementStart,
ɵɵembeddedViewEnd, ɵɵembeddedViewEnd,

View File

@ -12,6 +12,9 @@ import {LView, TVIEW, TView} from '../interfaces/view';
import {getCurrentDirectiveDef, getLView} from '../state'; import {getCurrentDirectiveDef, getLView} from '../state';
import {NO_CHANGE} from '../tokens'; import {NO_CHANGE} from '../tokens';
// TODO(misko-next): delete alloc_host_vars.ts file.
// TODO(misko-next): delete `ɵɵallocHostVars`
/** /**
* Allocates the necessary amount of slots for host vars. * Allocates the necessary amount of slots for host vars.
* *

View File

@ -219,6 +219,7 @@ export function ɵɵelementHostAttrs(attrs: TAttributes) {
// errors... // errors...
if (tNode.type === TNodeType.Element) { if (tNode.type === TNodeType.Element) {
const native = getNativeByTNode(tNode, lView) as RElement; const native = getNativeByTNode(tNode, lView) as RElement;
// TODO(misko-next): setup attributes need to be moved out of `ɵɵelementHostAttrs`
const lastAttrIndex = setUpAttributes(lView[RENDERER], native, attrs); const lastAttrIndex = setUpAttributes(lView[RENDERER], native, attrs);
if (tView.firstCreatePass) { if (tView.firstCreatePass) {
const stylingNeedsToBeRendered = registerInitialStylingOnTNode(tNode, attrs, lastAttrIndex); const stylingNeedsToBeRendered = registerInitialStylingOnTNode(tNode, attrs, lastAttrIndex);
@ -233,6 +234,7 @@ export function ɵɵelementHostAttrs(attrs: TAttributes) {
// attribute values to the element. // attribute values to the element.
if (stylingNeedsToBeRendered) { if (stylingNeedsToBeRendered) {
const renderer = lView[RENDERER]; const renderer = lView[RENDERER];
// TODO(misko-next): Styling initialization should move out of `ɵɵelementHostAttrs`
renderInitialStyling(renderer, native, tNode, true); renderInitialStyling(renderer, native, tNode, true);
} }
} }

View File

@ -41,6 +41,8 @@ import {getLViewParent} from '../util/view_traversal_utils';
import {getComponentLViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, isCreationMode, readPatchedLView, resetPreOrderHookFlags, unwrapRNode, viewAttachedToChangeDetector} from '../util/view_utils'; import {getComponentLViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, isCreationMode, readPatchedLView, resetPreOrderHookFlags, unwrapRNode, viewAttachedToChangeDetector} from '../util/view_utils';
import {selectIndexInternal} from './advance'; import {selectIndexInternal} from './advance';
import {ɵɵallocHostVars} from './alloc_host_vars';
import {ɵɵelementHostAttrs} from './element';
import {LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeConstructor, TNodeInitialInputs, TNodeLocalNames, TViewComponents, TViewConstructor, attachLContainerDebug, attachLViewDebug, cloneToLViewFromTViewBlueprint, cloneToTViewData} from './lview_debug'; import {LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeConstructor, TNodeInitialInputs, TNodeLocalNames, TViewComponents, TViewConstructor, attachLContainerDebug, attachLViewDebug, cloneToLViewFromTViewBlueprint, cloneToTViewData} from './lview_debug';
@ -1120,7 +1122,8 @@ export function resolveDirectives(
saveNameToExportMap(tView.data !.length - 1, def, exportsMap); saveNameToExportMap(tView.data !.length - 1, def, exportsMap);
if (def.contentQueries !== null) tNode.flags |= TNodeFlags.hasContentQuery; if (def.contentQueries !== null) tNode.flags |= TNodeFlags.hasContentQuery;
if (def.hostBindings !== null) tNode.flags |= TNodeFlags.hasHostBindings; if (def.hostBindings !== null || def.hostAttrs !== null || def.hostVars !== 0)
tNode.flags |= TNodeFlags.hasHostBindings;
// Only push a node index into the preOrderHooks array if this is the first // Only push a node index into the preOrderHooks array if this is the first
// pre-order hook found on this node. // pre-order hook found on this node.
@ -1194,7 +1197,7 @@ function invokeDirectivesHostBindings(tView: TView, viewData: LView, tNode: TNod
for (let i = start; i < end; i++) { for (let i = start; i < end; i++) {
const def = tView.data[i] as DirectiveDef<any>; const def = tView.data[i] as DirectiveDef<any>;
const directive = viewData[i]; const directive = viewData[i];
if (def.hostBindings) { if (def.hostBindings !== null || def.hostVars !== 0 || def.hostAttrs !== null) {
// It is important that this be called first before the actual instructions // It is important that this be called first before the actual instructions
// are run because this way the first directive ID value is not zero. // are run because this way the first directive ID value is not zero.
incrementActiveDirectiveId(); incrementActiveDirectiveId();
@ -1214,7 +1217,18 @@ export function invokeHostBindingsInCreationMode(
const previousExpandoLength = expando.length; const previousExpandoLength = expando.length;
setCurrentDirectiveDef(def); setCurrentDirectiveDef(def);
const elementIndex = tNode.index - HEADER_OFFSET; const elementIndex = tNode.index - HEADER_OFFSET;
def.hostBindings !(RenderFlags.Create, directive, elementIndex); // TODO(misko-next): This is a temporary work around for the fact that we moved the information
// from instruction to declaration. The workaround is to just call the instruction as if it was
// part of the `hostAttrs`.
if (def.hostVars !== 0) {
ɵɵallocHostVars(def.hostVars);
}
if (def.hostAttrs !== null) {
ɵɵelementHostAttrs(def.hostAttrs);
}
if (def.hostBindings !== null) {
def.hostBindings !(RenderFlags.Create, directive, elementIndex);
}
setCurrentDirectiveDef(null); setCurrentDirectiveDef(null);
// `hostBindings` function may or may not contain `allocHostVars` call // `hostBindings` function may or may not contain `allocHostVars` call
// (e.g. it may not if it only contains host listeners), so we need to check whether // (e.g. it may not if it only contains host listeners), so we need to check whether
@ -1346,7 +1360,8 @@ export function initNodeFlags(tNode: TNode, index: number, numberOfDirectives: n
function baseResolveDirective<T>(tView: TView, viewData: LView, def: DirectiveDef<T>) { function baseResolveDirective<T>(tView: TView, viewData: LView, def: DirectiveDef<T>) {
tView.data.push(def); tView.data.push(def);
const directiveFactory = def.factory || (def.factory = getFactoryDef(def.type, true)); const directiveFactory =
def.factory || ((def as{factory: Function}).factory = getFactoryDef(def.type, true));
const nodeInjectorFactory = new NodeInjectorFactory(directiveFactory, isComponentDef(def), null); const nodeInjectorFactory = new NodeInjectorFactory(directiveFactory, isComponentDef(def), null);
tView.blueprint.push(nodeInjectorFactory); tView.blueprint.push(nodeInjectorFactory);
viewData.push(nodeInjectorFactory); viewData.push(nodeInjectorFactory);

View File

@ -10,7 +10,7 @@ import {SchemaMetadata, ViewEncapsulation} from '../../core';
import {ProcessProvidersFunction} from '../../di/interface/provider'; import {ProcessProvidersFunction} from '../../di/interface/provider';
import {Type} from '../../interface/type'; import {Type} from '../../interface/type';
import {TConstants} from './node'; import {TAttributes, TConstants} from './node';
import {CssSelectorList} from './projection'; import {CssSelectorList} from './projection';
import {TView} from './view'; import {TView} from './view';
@ -146,10 +146,50 @@ export interface DirectiveDef<T> {
/** /**
* Refreshes host bindings on the associated directive. * Refreshes host bindings on the associated directive.
*/ */
hostBindings: HostBindingsFunction<T>|null; readonly hostBindings: HostBindingsFunction<T>|null;
/**
* The number of bindings in this directive `hostBindings` (including pure fn bindings).
*
* Used to calculate the length of the component's LView array, so we
* can pre-fill the array and set the host binding start index.
*/
readonly hostVars: number;
/**
* Assign static attribute values to a host element.
*
* This property will assign static attribute values as well as class and style
* values to a host element. Since attribute values can consist of different types of values, the
* `hostAttrs` array must include the values in the following format:
*
* attrs = [
* // static attributes (like `title`, `name`, `id`...)
* attr1, value1, attr2, value,
*
* // a single namespace value (like `x:id`)
* NAMESPACE_MARKER, namespaceUri1, name1, value1,
*
* // another single namespace value (like `x:name`)
* NAMESPACE_MARKER, namespaceUri2, name2, value2,
*
* // a series of CSS classes that will be applied to the element (no spaces)
* CLASSES_MARKER, class1, class2, class3,
*
* // a series of CSS styles (property + value) that will be applied to the element
* STYLES_MARKER, prop1, value1, prop2, value2
* ]
*
* All non-class and non-style attributes must be defined at the start of the list
* first before all class and style values are set. When there is a change in value
* type (like when classes and styles are introduced) a marker must be used to separate
* the entries. The marker values themselves are set via entries found in the
* [AttributeMarker] enum.
*/
readonly hostAttrs: TAttributes|null;
/** Token representing the directive. Used by DI. */ /** Token representing the directive. Used by DI. */
type: Type<T>; readonly type: Type<T>;
/** Function that resolves providers and publishes them into the DI system. */ /** Function that resolves providers and publishes them into the DI system. */
providersResolver: providersResolver:
@ -168,17 +208,17 @@ export interface DirectiveDef<T> {
* Factory function used to create a new directive instance. Will be null initially. * Factory function used to create a new directive instance. Will be null initially.
* Populated when the factory is first requested by directive instantiation logic. * Populated when the factory is first requested by directive instantiation logic.
*/ */
factory: FactoryFn<T>|null; readonly factory: FactoryFn<T>|null;
/* The following are lifecycle hooks for this component */ /* The following are lifecycle hooks for this component */
onChanges: (() => void)|null; readonly onChanges: (() => void)|null;
onInit: (() => void)|null; readonly onInit: (() => void)|null;
doCheck: (() => void)|null; readonly doCheck: (() => void)|null;
afterContentInit: (() => void)|null; readonly afterContentInit: (() => void)|null;
afterContentChecked: (() => void)|null; readonly afterContentChecked: (() => void)|null;
afterViewInit: (() => void)|null; readonly afterViewInit: (() => void)|null;
afterViewChecked: (() => void)|null; readonly afterViewChecked: (() => void)|null;
onDestroy: (() => void)|null; readonly onDestroy: (() => void)|null;
/** /**
* The features applied to this directive * The features applied to this directive

View File

@ -201,6 +201,15 @@ export const enum TNodeProviderIndexes {
* items are not regular attributes and the processing should be adapted accordingly. * items are not regular attributes and the processing should be adapted accordingly.
*/ */
export const enum AttributeMarker { export const enum AttributeMarker {
/**
* An implicit marker which indicates that the value in the array are of `attributeKey`,
* `attributeValue` format.
*
* NOTE: This is implicit as it is the type when no marker is present in array. We indicate that
* it should not be present at runtime by the negative number.
*/
ImplicitAttributes = -1,
/** /**
* Marker indicates that the following 3 values in the attributes array are: * Marker indicates that the following 3 values in the attributes array are:
* namespaceUri, attributeName, attributeValue * namespaceUri, attributeName, attributeValue

View File

@ -58,7 +58,6 @@ export const angularCoreEnv: {[name: string]: Function} =
'ɵɵnamespaceSVG': r3.ɵɵnamespaceSVG, 'ɵɵnamespaceSVG': r3.ɵɵnamespaceSVG,
'ɵɵenableBindings': r3.ɵɵenableBindings, 'ɵɵenableBindings': r3.ɵɵenableBindings,
'ɵɵdisableBindings': r3.ɵɵdisableBindings, 'ɵɵdisableBindings': r3.ɵɵdisableBindings,
'ɵɵallocHostVars': r3.ɵɵallocHostVars,
'ɵɵelementStart': r3.ɵɵelementStart, 'ɵɵelementStart': r3.ɵɵelementStart,
'ɵɵelementEnd': r3.ɵɵelementEnd, 'ɵɵelementEnd': r3.ɵɵelementEnd,
'ɵɵelement': r3.ɵɵelement, 'ɵɵelement': r3.ɵɵelement,
@ -107,7 +106,6 @@ export const angularCoreEnv: {[name: string]: Function} =
'ɵɵloadQuery': r3.ɵɵloadQuery, 'ɵɵloadQuery': r3.ɵɵloadQuery,
'ɵɵcontentQuery': r3.ɵɵcontentQuery, 'ɵɵcontentQuery': r3.ɵɵcontentQuery,
'ɵɵreference': r3.ɵɵreference, 'ɵɵreference': r3.ɵɵreference,
'ɵɵelementHostAttrs': r3.ɵɵelementHostAttrs,
'ɵɵclassMap': r3.ɵɵclassMap, 'ɵɵclassMap': r3.ɵɵclassMap,
'ɵɵclassMapInterpolate1': r3.ɵɵclassMapInterpolate1, 'ɵɵclassMapInterpolate1': r3.ɵɵclassMapInterpolate1,
'ɵɵclassMapInterpolate2': r3.ɵɵclassMapInterpolate2, 'ɵɵclassMapInterpolate2': r3.ɵɵclassMapInterpolate2,

View File

@ -5,12 +5,14 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import {assertEqual} from '../../util/assert';
import {CharCode} from '../../util/char_code'; import {CharCode} from '../../util/char_code';
import {AttributeMarker, TAttributes} from '../interfaces/node'; import {AttributeMarker, TAttributes} from '../interfaces/node';
import {CssSelector} from '../interfaces/projection'; import {CssSelector} from '../interfaces/projection';
import {ProceduralRenderer3, RElement, Renderer3, isProceduralRenderer} from '../interfaces/renderer'; import {ProceduralRenderer3, RElement, Renderer3, isProceduralRenderer} from '../interfaces/renderer';
/** /**
* Assigns all attribute values to the provided element via the inferred renderer. * Assigns all attribute values to the provided element via the inferred renderer.
* *
@ -106,3 +108,114 @@ export function isAnimationProp(name: string): boolean {
// charCodeAt doesn't allocate memory to return a substring. // charCodeAt doesn't allocate memory to return a substring.
return name.charCodeAt(0) === CharCode.AT_SIGN; return name.charCodeAt(0) === CharCode.AT_SIGN;
} }
/**
* Merges `src` `TAttributes` into `dst` `TAttributes` removing any duplicates in the process.
*
* This merge function keeps the order of attrs same.
*
* @param dst Location of where the merged `TAttributes` should end up.
* @param src `TAttributes` which should be appended to `dst`
*/
export function mergeHostAttrs(dst: TAttributes | null, src: TAttributes | null): TAttributes|null {
if (src === null || src.length === 0) {
// do nothing
} else if (dst === null || dst.length === 0) {
// We have source, but dst is empty, just make a copy.
dst = src.slice();
} else {
let srcMarker: AttributeMarker = AttributeMarker.ImplicitAttributes;
for (let i = 0; i < src.length; i++) {
const item = src[i];
if (typeof item === 'number') {
srcMarker = item;
} else {
if (srcMarker === AttributeMarker.NamespaceURI) {
// Case where we need to consume `key1`, `key2`, `value` items.
} else if (
srcMarker === AttributeMarker.ImplicitAttributes ||
srcMarker === AttributeMarker.Styles) {
// Case where we have to consume `key1` and `value` only.
mergeHostAttribute(dst, srcMarker, item as string, null, src[++i] as string);
} else {
// Case where we have to consume `key1` only.
mergeHostAttribute(dst, srcMarker, item as string, null, null);
}
}
}
}
return dst;
}
/**
* Append `key`/`value` to existing `TAttributes` taking region marker and duplicates into account.
*
* @param dst `TAttributes` to append to.
* @param marker Region where the `key`/`value` should be added.
* @param key1 Key to add to `TAttributes`
* @param key2 Key to add to `TAttributes` (in case of `AttributeMarker.NamespaceURI`)
* @param value Value to add or to overwrite to `TAttributes` Only used if `marker` is not Class.
*/
export function mergeHostAttribute(
dst: TAttributes, marker: AttributeMarker, key1: string, key2: string | null,
value: string | null): void {
let i = 0;
// Assume that new markers will be inserted at the end.
let markerInsertPosition = dst.length;
// scan until correct type.
if (marker === AttributeMarker.ImplicitAttributes) {
markerInsertPosition = -1;
} else {
while (i < dst.length) {
const dstValue = dst[i++];
if (typeof dstValue === 'number') {
if (dstValue === marker) {
markerInsertPosition = -1;
break;
} else if (dstValue > marker) {
// We need to save this as we want the markers to be inserted in specific order.
markerInsertPosition = i - 1;
break;
}
}
}
}
// search until you find place of insertion
while (i < dst.length) {
const item = dst[i];
if (typeof item === 'number') {
// since `i` started as the index after the marker, we did not find it if we are at the next
// marker
break;
} else if (item === key1) {
// We already have same token
if (key2 === null) {
if (value !== null) {
dst[i + 1] = value;
}
return;
} else if (key2 === dst[i + 1]) {
dst[i + 2] = value !;
return;
}
}
// Increment counter.
i++;
if (key2 !== null) i++;
if (value !== null) i++;
}
// insert at location.
if (markerInsertPosition !== -1) {
dst.splice(markerInsertPosition, 0, marker);
i = markerInsertPosition + 1;
}
dst.splice(i++, 0, key1);
if (key2 !== null) {
dst.splice(i++, 0, key2);
}
if (value !== null) {
dst.splice(i++, 0, value);
}
}

View File

@ -7,6 +7,8 @@
*/ */
import {Component, ContentChildren, Directive, EventEmitter, HostBinding, HostListener, Input, OnChanges, Output, QueryList, ViewChildren} from '@angular/core'; import {Component, ContentChildren, Directive, EventEmitter, HostBinding, HostListener, Input, OnChanges, Output, QueryList, ViewChildren} from '@angular/core';
import {ivyEnabled} from '@angular/core/src/ivy_switch';
import {getDirectiveDef} from '@angular/core/src/render3/definition';
import {TestBed} from '@angular/core/testing'; import {TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser'; import {By} from '@angular/platform-browser';
import {onlyInIvy} from '@angular/private/testing'; import {onlyInIvy} from '@angular/private/testing';
@ -42,6 +44,86 @@ describe('inheritance', () => {
}).toThrowError('Directives cannot inherit Components'); }).toThrowError('Directives cannot inherit Components');
}); });
describe('multiple children', () => {
it('should ensure that multiple child classes don\'t cause multiple parent execution', () => {
// Assume this inheritance:
// Base
// |
// Super
// / \
// Sub1 Sub2
//
// In the above case:
// 1. Sub1 as will walk the inheritance Sub1, Super, Base
// 2. Sub2 as will walk the inheritance Sub2, Super, Base
//
// Notice that Super, Base will get walked twice. Because inheritance works by wrapping parent
// hostBindings function in a delegate which calls the hostBindings of the directive as well
// as super, we need to ensure that we don't double wrap the hostBindings function. Doing so
// would result in calling the hostBindings multiple times (unnecessarily). This would be
// especially an issue if we have a lot of sub-classes (as is common in component libraries)
const log: string[] = [];
@Directive({selector: '[superDir]'})
class BaseDirective {
@HostBinding('style.background-color')
get backgroundColor() {
log.push('Base.backgroundColor');
return 'white';
}
}
@Directive({selector: '[superDir]'})
class SuperDirective extends BaseDirective {
@HostBinding('style.color')
get color() {
log.push('Super.color');
return 'blue';
}
}
@Directive({selector: '[subDir1]'})
class Sub1Directive extends SuperDirective {
@HostBinding('style.height')
get height() {
log.push('Sub1.height');
return '200px';
}
}
@Directive({selector: '[subDir2]'})
class Sub2Directive extends SuperDirective {
@HostBinding('style.width')
get width() {
log.push('Sub2.width');
return '100px';
}
}
@Component({template: `<div subDir1 subDir2></div>`})
class App {
}
TestBed.configureTestingModule({
declarations: [App, Sub1Directive, Sub2Directive, SuperDirective],
});
const fixture = TestBed.createComponent(App);
fixture.detectChanges(false); // Don't check for no changes (so that assertion does not need
// to worry about it.)
expect(log).toEqual([
'Base.backgroundColor', 'Super.color', 'Sub1.height', //
'Base.backgroundColor', 'Super.color', 'Sub2.width', //
]);
if (ivyEnabled) {
expect(getDirectiveDef(BaseDirective) !.hostVars).toEqual(1);
expect(getDirectiveDef(SuperDirective) !.hostVars).toEqual(2);
expect(getDirectiveDef(Sub1Directive) !.hostVars).toEqual(3);
expect(getDirectiveDef(Sub2Directive) !.hostVars).toEqual(3);
}
});
});
describe('ngOnChanges', () => { describe('ngOnChanges', () => {
it('should be inherited when super is a directive', () => { it('should be inherited when super is a directive', () => {
const log: string[] = []; const log: string[] = [];

View File

@ -314,6 +314,9 @@
{ {
"name": "getContainerRenderParent" "name": "getContainerRenderParent"
}, },
{
"name": "getCurrentDirectiveDef"
},
{ {
"name": "getDirectiveDef" "name": "getDirectiveDef"
}, },
@ -404,6 +407,9 @@
{ {
"name": "getStylingMapArray" "name": "getStylingMapArray"
}, },
{
"name": "getTNode"
},
{ {
"name": "hasActiveElementFlag" "name": "hasActiveElementFlag"
}, },
@ -545,6 +551,12 @@
{ {
"name": "objectToClassName" "name": "objectToClassName"
}, },
{
"name": "prefillHostVars"
},
{
"name": "queueHostBindingForCheck"
},
{ {
"name": "refreshChildComponents" "name": "refreshChildComponents"
}, },
@ -695,6 +707,9 @@
{ {
"name": "writeStylingValueDirectly" "name": "writeStylingValueDirectly"
}, },
{
"name": "ɵɵallocHostVars"
},
{ {
"name": "ɵɵdefineComponent" "name": "ɵɵdefineComponent"
}, },
@ -710,6 +725,9 @@
{ {
"name": "ɵɵelementEnd" "name": "ɵɵelementEnd"
}, },
{
"name": "ɵɵelementHostAttrs"
},
{ {
"name": "ɵɵelementStart" "name": "ɵɵelementStart"
}, },

View File

@ -104,6 +104,9 @@
{ {
"name": "RENDERER_FACTORY" "name": "RENDERER_FACTORY"
}, },
{
"name": "RendererStyleFlags3"
},
{ {
"name": "SANITIZER" "name": "SANITIZER"
}, },
@ -137,12 +140,18 @@
{ {
"name": "_renderCompCount" "name": "_renderCompCount"
}, },
{
"name": "addItemToStylingMap"
},
{ {
"name": "addToViewTree" "name": "addToViewTree"
}, },
{ {
"name": "allocLFrame" "name": "allocLFrame"
}, },
{
"name": "allocStylingMapArray"
},
{ {
"name": "appendChild" "name": "appendChild"
}, },
@ -161,6 +170,9 @@
{ {
"name": "callHooks" "name": "callHooks"
}, },
{
"name": "concatString"
},
{ {
"name": "createLFrame" "name": "createLFrame"
}, },
@ -227,6 +239,9 @@
{ {
"name": "extractPipeDef" "name": "extractPipeDef"
}, },
{
"name": "forceStylesAsString"
},
{ {
"name": "generateExpandoInstructionBlock" "name": "generateExpandoInstructionBlock"
}, },
@ -248,6 +263,9 @@
{ {
"name": "getContainerRenderParent" "name": "getContainerRenderParent"
}, },
{
"name": "getCurrentDirectiveDef"
},
{ {
"name": "getDirectiveDef" "name": "getDirectiveDef"
}, },
@ -260,6 +278,9 @@
{ {
"name": "getFirstNativeNode" "name": "getFirstNativeNode"
}, },
{
"name": "getInitialStylingValue"
},
{ {
"name": "getInjectorIndex" "name": "getInjectorIndex"
}, },
@ -275,6 +296,12 @@
{ {
"name": "getLViewParent" "name": "getLViewParent"
}, },
{
"name": "getMapProp"
},
{
"name": "getMapValue"
},
{ {
"name": "getNativeAnchorNode" "name": "getNativeAnchorNode"
}, },
@ -317,12 +344,21 @@
{ {
"name": "getSelectedIndex" "name": "getSelectedIndex"
}, },
{
"name": "getStylingMapArray"
},
{
"name": "getTNode"
},
{ {
"name": "hasActiveElementFlag" "name": "hasActiveElementFlag"
}, },
{ {
"name": "hasParentInjector" "name": "hasParentInjector"
}, },
{
"name": "hyphenate"
},
{ {
"name": "includeViewProviders" "name": "includeViewProviders"
}, },
@ -350,6 +386,9 @@
{ {
"name": "invokeHostBindingsInCreationMode" "name": "invokeHostBindingsInCreationMode"
}, },
{
"name": "isAnimationProp"
},
{ {
"name": "isComponentDef" "name": "isComponentDef"
}, },
@ -365,6 +404,12 @@
{ {
"name": "isProceduralRenderer" "name": "isProceduralRenderer"
}, },
{
"name": "isStylingContext"
},
{
"name": "isStylingValueDefined"
},
{ {
"name": "leaveDI" "name": "leaveDI"
}, },
@ -398,6 +443,15 @@
{ {
"name": "noSideEffects" "name": "noSideEffects"
}, },
{
"name": "objectToClassName"
},
{
"name": "prefillHostVars"
},
{
"name": "queueHostBindingForCheck"
},
{ {
"name": "refreshChildComponents" "name": "refreshChildComponents"
}, },
@ -416,6 +470,9 @@
{ {
"name": "refreshView" "name": "refreshView"
}, },
{
"name": "registerInitialStylingOnTNode"
},
{ {
"name": "registerPreOrderHooks" "name": "registerPreOrderHooks"
}, },
@ -428,9 +485,15 @@
{ {
"name": "renderComponent" "name": "renderComponent"
}, },
{
"name": "renderInitialStyling"
},
{ {
"name": "renderStringify" "name": "renderStringify"
}, },
{
"name": "renderStylingMap"
},
{ {
"name": "renderView" "name": "renderView"
}, },
@ -449,6 +512,12 @@
{ {
"name": "setBindingRoot" "name": "setBindingRoot"
}, },
{
"name": "setClass"
},
{
"name": "setClassName"
},
{ {
"name": "setCurrentDirectiveDef" "name": "setCurrentDirectiveDef"
}, },
@ -464,27 +533,54 @@
{ {
"name": "setInjectImplementation" "name": "setInjectImplementation"
}, },
{
"name": "setMapValue"
},
{ {
"name": "setPreviousOrParentTNode" "name": "setPreviousOrParentTNode"
}, },
{ {
"name": "setSelectedIndex" "name": "setSelectedIndex"
}, },
{
"name": "setStyle"
},
{
"name": "setStyleAttr"
},
{
"name": "setUpAttributes"
},
{ {
"name": "stringifyForError" "name": "stringifyForError"
}, },
{
"name": "stylingMapToString"
},
{ {
"name": "syncViewWithBlueprint" "name": "syncViewWithBlueprint"
}, },
{ {
"name": "unwrapRNode" "name": "unwrapRNode"
}, },
{
"name": "updateRawValueOnContext"
},
{ {
"name": "viewAttachedToChangeDetector" "name": "viewAttachedToChangeDetector"
}, },
{
"name": "writeStylingValueDirectly"
},
{
"name": "ɵɵallocHostVars"
},
{ {
"name": "ɵɵdefineComponent" "name": "ɵɵdefineComponent"
}, },
{
"name": "ɵɵelementHostAttrs"
},
{ {
"name": "ɵɵtext" "name": "ɵɵtext"
} }

View File

@ -647,6 +647,9 @@
{ {
"name": "getContextLView" "name": "getContextLView"
}, },
{
"name": "getCurrentDirectiveDef"
},
{ {
"name": "getCurrentStyleSanitizer" "name": "getCurrentStyleSanitizer"
}, },
@ -1079,6 +1082,12 @@
{ {
"name": "patchHostStylingFlag" "name": "patchHostStylingFlag"
}, },
{
"name": "prefillHostVars"
},
{
"name": "queueHostBindingForCheck"
},
{ {
"name": "readPatchedData" "name": "readPatchedData"
}, },
@ -1358,6 +1367,9 @@
{ {
"name": "ɵɵadvance" "name": "ɵɵadvance"
}, },
{
"name": "ɵɵallocHostVars"
},
{ {
"name": "ɵɵclassProp" "name": "ɵɵclassProp"
}, },
@ -1376,6 +1388,9 @@
{ {
"name": "ɵɵelementEnd" "name": "ɵɵelementEnd"
}, },
{
"name": "ɵɵelementHostAttrs"
},
{ {
"name": "ɵɵelementStart" "name": "ɵɵelementStart"
}, },

View File

@ -0,0 +1,119 @@
/**
* @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 {AttributeMarker} from '@angular/core/src/render3';
import {TAttributes} from '@angular/core/src/render3/interfaces/node';
import {mergeHostAttribute, mergeHostAttrs} from '@angular/core/src/render3/util/attrs_utils';
import {describe} from '@angular/core/testing/src/testing_internal';
describe('attr_util', () => {
describe('mergeHostAttribute', () => {
it('should add new attributes', () => {
const attrs: TAttributes = [];
mergeHostAttribute(attrs, -1, 'Key', null, 'value');
expect(attrs).toEqual(['Key', 'value']);
mergeHostAttribute(attrs, -1, 'A', null, 'a');
expect(attrs).toEqual(['Key', 'value', 'A', 'a']);
mergeHostAttribute(attrs, -1, 'X', null, 'x');
expect(attrs).toEqual(['Key', 'value', 'A', 'a', 'X', 'x']);
mergeHostAttribute(attrs, -1, 'Key', null, 'new');
expect(attrs).toEqual(['Key', 'new', 'A', 'a', 'X', 'x']);
});
it('should add new classes', () => {
const attrs: TAttributes = [];
mergeHostAttribute(attrs, AttributeMarker.Classes, 'CLASS', null, null);
expect(attrs).toEqual([AttributeMarker.Classes, 'CLASS']);
mergeHostAttribute(attrs, AttributeMarker.Classes, 'A', null, null);
expect(attrs).toEqual([AttributeMarker.Classes, 'CLASS', 'A']);
mergeHostAttribute(attrs, AttributeMarker.Classes, 'X', null, null);
expect(attrs).toEqual([AttributeMarker.Classes, 'CLASS', 'A', 'X']);
mergeHostAttribute(attrs, AttributeMarker.Classes, 'CLASS', null, null);
expect(attrs).toEqual([AttributeMarker.Classes, 'CLASS', 'A', 'X']);
});
it('should add new styles', () => {
const attrs: TAttributes = [];
mergeHostAttribute(attrs, AttributeMarker.Styles, 'Style', null, 'v1');
expect(attrs).toEqual([AttributeMarker.Styles, 'Style', 'v1']);
mergeHostAttribute(attrs, AttributeMarker.Styles, 'A', null, 'v2');
expect(attrs).toEqual([AttributeMarker.Styles, 'Style', 'v1', 'A', 'v2']);
mergeHostAttribute(attrs, AttributeMarker.Styles, 'X', null, 'v3');
expect(attrs).toEqual([AttributeMarker.Styles, 'Style', 'v1', 'A', 'v2', 'X', 'v3']);
mergeHostAttribute(attrs, AttributeMarker.Styles, 'Style', null, 'new');
expect(attrs).toEqual([AttributeMarker.Styles, 'Style', 'new', 'A', 'v2', 'X', 'v3']);
});
it('should keep different types together', () => {
const attrs: TAttributes = [];
mergeHostAttribute(attrs, -1, 'Key', null, 'value');
expect(attrs).toEqual(['Key', 'value']);
mergeHostAttribute(attrs, AttributeMarker.Classes, 'CLASS', null, null);
expect(attrs).toEqual(['Key', 'value', AttributeMarker.Classes, 'CLASS']);
mergeHostAttribute(attrs, AttributeMarker.Styles, 'Style', null, 'v1');
expect(attrs).toEqual([
'Key', 'value', AttributeMarker.Classes, 'CLASS', AttributeMarker.Styles, 'Style', 'v1'
]);
mergeHostAttribute(attrs, -1, 'Key2', null, 'value2');
expect(attrs).toEqual([
'Key', 'value', 'Key2', 'value2', AttributeMarker.Classes, 'CLASS', AttributeMarker.Styles,
'Style', 'v1'
]);
mergeHostAttribute(attrs, AttributeMarker.Classes, 'CLASS2', null, null);
expect(attrs).toEqual([
'Key', 'value', 'Key2', 'value2', AttributeMarker.Classes, 'CLASS', 'CLASS2',
AttributeMarker.Styles, 'Style', 'v1'
]);
mergeHostAttribute(attrs, AttributeMarker.Styles, 'Style2', null, 'v2');
expect(attrs).toEqual([
'Key', 'value', 'Key2', 'value2', AttributeMarker.Classes, 'CLASS', 'CLASS2',
AttributeMarker.Styles, 'Style', 'v1', 'Style2', 'v2'
]);
mergeHostAttribute(attrs, AttributeMarker.NamespaceURI, 'uri', 'key', 'value');
expect(attrs).toEqual([
'Key', 'value', 'Key2', 'value2', AttributeMarker.NamespaceURI, 'uri', 'key', 'value',
AttributeMarker.Classes, 'CLASS', 'CLASS2', AttributeMarker.Styles, 'Style', 'v1', 'Style2',
'v2'
]);
mergeHostAttribute(attrs, AttributeMarker.NamespaceURI, 'uri', 'key', 'new value');
expect(attrs).toEqual([
'Key', 'value', 'Key2', 'value2', AttributeMarker.NamespaceURI, 'uri', 'key', 'new value',
AttributeMarker.Classes, 'CLASS', 'CLASS2', AttributeMarker.Styles, 'Style', 'v1', 'Style2',
'v2'
]);
});
});
describe('mergeHostAttrs', () => {
it('should ignore nulls/empty', () => {
expect(mergeHostAttrs(null, null)).toEqual(null);
expect(mergeHostAttrs([], null)).toEqual([]);
expect(mergeHostAttrs(null, [])).toEqual(null);
});
it('should copy if dst is null', () => {
expect(mergeHostAttrs(null, ['K', 'v'])).toEqual(['K', 'v']);
expect(mergeHostAttrs(['K', '', 'X', 'x'], ['K', 'v'])).toEqual(['K', 'v', 'X', 'x']);
});
});
});

View File

@ -682,8 +682,6 @@ export interface OutputDecorator {
export declare function ɵɵadvance(delta: number): void; export declare function ɵɵadvance(delta: number): void;
export declare function ɵɵallocHostVars(count: number): void;
export declare function ɵɵattribute(name: string, value: any, sanitizer?: SanitizerFn | null, namespace?: string): typeof ɵɵattribute; export declare function ɵɵattribute(name: string, value: any, sanitizer?: SanitizerFn | null, namespace?: string): typeof ɵɵattribute;
export declare function ɵɵattributeInterpolate1(attrName: string, prefix: string, v0: any, suffix: string, sanitizer?: SanitizerFn, namespace?: string): typeof ɵɵattributeInterpolate1; export declare function ɵɵattributeInterpolate1(attrName: string, prefix: string, v0: any, suffix: string, sanitizer?: SanitizerFn, namespace?: string): typeof ɵɵattributeInterpolate1;
@ -760,6 +758,8 @@ export declare function ɵɵdefineComponent<T>(componentDefinition: {
[P in keyof T]?: string; [P in keyof T]?: string;
}; };
hostBindings?: HostBindingsFunction<T>; hostBindings?: HostBindingsFunction<T>;
hostVars?: number;
hostAttrs?: TAttributes;
contentQueries?: ContentQueriesFunction<T>; contentQueries?: ContentQueriesFunction<T>;
exportAs?: string[]; exportAs?: string[];
template: ComponentTemplate<T>; template: ComponentTemplate<T>;
@ -785,6 +785,8 @@ export declare const ɵɵdefineDirective: <T>(directiveDefinition: {
outputs?: { [P_1 in keyof T]?: string | undefined; } | undefined; outputs?: { [P_1 in keyof T]?: string | undefined; } | undefined;
features?: DirectiveDefFeature[] | undefined; features?: DirectiveDefFeature[] | undefined;
hostBindings?: HostBindingsFunction<T> | undefined; hostBindings?: HostBindingsFunction<T> | undefined;
hostVars?: number | undefined;
hostAttrs?: (string | (string | SelectorFlags)[] | AttributeMarker)[] | undefined;
contentQueries?: ContentQueriesFunction<T> | undefined; contentQueries?: ContentQueriesFunction<T> | undefined;
viewQuery?: ViewQueriesFunction<T> | null | undefined; viewQuery?: ViewQueriesFunction<T> | null | undefined;
exportAs?: string[] | undefined; exportAs?: string[] | undefined;
@ -839,8 +841,6 @@ export declare function ɵɵelementContainerStart(index: number, attrsIndex?: nu
export declare function ɵɵelementEnd(): void; export declare function ɵɵelementEnd(): void;
export declare function ɵɵelementHostAttrs(attrs: TAttributes): void;
export declare function ɵɵelementStart(index: number, name: string, attrsIndex?: number | null, localRefsIndex?: number): void; export declare function ɵɵelementStart(index: number, name: string, attrsIndex?: number | null, localRefsIndex?: number): void;
export declare function ɵɵembeddedViewEnd(): void; export declare function ɵɵembeddedViewEnd(): void;