revert: refactor(ivy): remove styling state storage and introduce direct style writing (#32259)

This reverts commit 15aeab1620.
This commit is contained in:
Matias Niemelä 2019-09-11 15:24:10 -07:00
parent bb9e61202c
commit 53dbff66d7
46 changed files with 1613 additions and 1727 deletions

View File

@ -16,8 +16,8 @@
"uncompressed": { "uncompressed": {
"runtime-es5": 3042, "runtime-es5": 3042,
"runtime-es2015": 3048, "runtime-es2015": 3048,
"main-es5": 492593, "main-es5": 493330,
"main-es2015": 438296, "main-es2015": 435296,
"polyfills-es5": 131024, "polyfills-es5": 131024,
"polyfills-es2015": 52433 "polyfills-es2015": 52433
} }
@ -28,7 +28,7 @@
"uncompressed": { "uncompressed": {
"runtime-es5": 2932, "runtime-es5": 2932,
"runtime-es2015": 2938, "runtime-es2015": 2938,
"main-es5": 552068, "main-es5": 550854,
"main-es2015": 493320, "main-es2015": 493320,
"polyfills-es5": 131024, "polyfills-es5": 131024,
"polyfills-es2015": 52433 "polyfills-es2015": 52433

View File

@ -12,7 +12,7 @@
"master": { "master": {
"uncompressed": { "uncompressed": {
"runtime": 1440, "runtime": 1440,
"main": 13604, "main": 13264,
"polyfills": 45340 "polyfills": 45340
} }
} }

View File

@ -36,7 +36,7 @@ export class TableComponent {
@NgModule({imports: [BrowserModule], bootstrap: [TableComponent], declarations: [TableComponent]}) @NgModule({imports: [BrowserModule], bootstrap: [TableComponent], declarations: [TableComponent]})
export class AppModule { export class AppModule {
constructor(sanitizer: DomSanitizer) { constructor(sanitizer: DomSanitizer) {
trustedEmptyColor = sanitizer.bypassSecurityTrustStyle('white'); trustedEmptyColor = sanitizer.bypassSecurityTrustStyle('');
trustedGreyColor = sanitizer.bypassSecurityTrustStyle('grey'); trustedGreyColor = sanitizer.bypassSecurityTrustStyle('grey');
} }
} }

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ɵRenderFlags, ɵrenderComponent as renderComponent, ɵɵadvance, ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵdefineComponent, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵstyleProp, ɵɵtext, ɵɵtextInterpolate1} from '@angular/core'; import {ɵRenderFlags, ɵrenderComponent as renderComponent, ɵɵadvance, ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵdefineComponent, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵstyleProp, ɵɵstyling, ɵɵstylingApply, ɵɵtext, ɵɵtextInterpolate1} from '@angular/core';
import {bindAction, profile} from '../../util'; import {bindAction, profile} from '../../util';
import {createDom, destroyDom, detectChanges} from '../render3/tree'; import {createDom, destroyDom, detectChanges} from '../render3/tree';
@ -39,6 +39,7 @@ export function TreeTpl(rf: ɵRenderFlags, ctx: TreeNode) {
ɵɵelementStart(0, 'tree'); ɵɵelementStart(0, 'tree');
{ {
ɵɵelementStart(1, 'span'); ɵɵelementStart(1, 'span');
ɵɵstyling();
{ ɵɵtext(2); } { ɵɵtext(2); }
ɵɵelementEnd(); ɵɵelementEnd();
ɵɵcontainer(3); ɵɵcontainer(3);
@ -49,6 +50,7 @@ export function TreeTpl(rf: ɵRenderFlags, ctx: TreeNode) {
if (rf & ɵRenderFlags.Update) { if (rf & ɵRenderFlags.Update) {
ɵɵadvance(1); ɵɵadvance(1);
ɵɵstyleProp('background-color', ctx.depth % 2 ? '' : 'grey'); ɵɵstyleProp('background-color', ctx.depth % 2 ? '' : 'grey');
ɵɵstylingApply();
ɵɵadvance(1); ɵɵadvance(1);
ɵɵtextInterpolate1(' ', ctx.value, ' '); ɵɵtextInterpolate1(' ', ctx.value, ' ');
ɵɵcontainerRefreshStart(3); ɵɵcontainerRefreshStart(3);

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, ɵɵallocHostVars, ɵɵclassMap, ɵɵdefineDirective, ɵɵstyling, ɵɵstylingApply} from '@angular/core';
import {NgClassImpl, NgClassImplProvider} from './ng_class_impl'; import {NgClassImpl, NgClassImplProvider} from './ng_class_impl';
@ -35,9 +35,11 @@ export const ngClassDirectiveDef__POST_R3__ = ɵɵdefineDirective({
hostBindings: function(rf: ɵRenderFlags, ctx: any, elIndex: number) { hostBindings: function(rf: ɵRenderFlags, ctx: any, elIndex: number) {
if (rf & ɵRenderFlags.Create) { if (rf & ɵRenderFlags.Create) {
ɵɵallocHostVars(1); ɵɵallocHostVars(1);
ɵɵstyling();
} }
if (rf & ɵRenderFlags.Update) { if (rf & ɵRenderFlags.Update) {
ɵɵclassMap(ctx.getValue()); ɵɵclassMap(ctx.getValue());
ɵɵstylingApply();
} }
} }
}); });

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, ɵɵdefineDirective, ɵɵstyleMap} from '@angular/core'; import {Directive, DoCheck, Input, ɵRenderFlags, ɵɵdefineDirective, ɵɵstyleMap, ɵɵstyling, ɵɵstylingApply} from '@angular/core';
import {NgStyleImpl, NgStyleImplProvider} from './ng_style_impl'; import {NgStyleImpl, NgStyleImplProvider} from './ng_style_impl';
@ -34,8 +34,12 @@ export const ngStyleDirectiveDef__POST_R3__ = ɵɵdefineDirective({
type: function() {} as any, type: function() {} as any,
selectors: null as any, selectors: null as any,
hostBindings: function(rf: ɵRenderFlags, ctx: any, elIndex: number) { hostBindings: function(rf: ɵRenderFlags, ctx: any, elIndex: number) {
if (rf & ɵRenderFlags.Create) {
ɵɵstyling();
}
if (rf & ɵRenderFlags.Update) { if (rf & ɵRenderFlags.Update) {
ɵɵstyleMap(ctx.getValue()); ɵɵstyleMap(ctx.getValue());
ɵɵstylingApply();
} }
} }
}); });

View File

@ -483,11 +483,14 @@ describe('compiler compliance', () => {
vars: 2, vars: 2,
template: function MyComponent_Template(rf,ctx){ template: function MyComponent_Template(rf,ctx){
if (rf & 1) { if (rf & 1) {
$r3$.ɵɵelement(0, "div"); $r3$.ɵɵelementStart(0, "div");
$r3$.ɵɵstyling();
$r3$.ɵɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵstyleProp("background-color", ctx.color); $r3$.ɵɵstyleProp("background-color", ctx.color);
$r3$.ɵɵclassProp("error", ctx.error); $r3$.ɵɵclassProp("error", ctx.error);
$r3$.ɵɵstylingApply();
} }
}, },
encapsulation: 2 encapsulation: 2

View File

@ -376,11 +376,14 @@ describe('compiler compliance: styling', () => {
const template = ` const template = `
template: function MyComponent_Template(rf, $ctx$) { template: function MyComponent_Template(rf, $ctx$) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵɵelement(0, "div"); $r3$.ɵɵelementStart(0, "div");
$r3$.ɵɵstyling();
$r3$.ɵɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer); $r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵstyleMap($ctx$.myStyleExp); $r3$.ɵɵstyleMap($ctx$.myStyleExp);
$r3$.ɵɵstylingApply();
} }
} }
`; `;
@ -439,10 +442,13 @@ describe('compiler compliance: styling', () => {
vars: 2, vars: 2,
template: function MyComponentWithInterpolation_Template(rf, $ctx$) { template: function MyComponentWithInterpolation_Template(rf, $ctx$) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵɵelement(0, "div"); $r3$.ɵɵelementStart(0, "div");
$r3$.ɵɵstyling();
$r3$.ɵɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵclassMapInterpolate1("foo foo-", $ctx$.fooId, ""); $r3$.ɵɵclassMapInterpolate1("foo foo-", $ctx$.fooId, "");
$r3$.ɵɵstylingApply();
} }
} }
@ -450,10 +456,13 @@ describe('compiler compliance: styling', () => {
vars: 3, vars: 3,
template: function MyComponentWithMuchosInterpolation_Template(rf, $ctx$) { template: function MyComponentWithMuchosInterpolation_Template(rf, $ctx$) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵɵelement(0, "div"); $r3$.ɵɵelementStart(0, "div");
$r3$.ɵɵstyling();
$r3$.ɵɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵclassMapInterpolate2("foo foo-", $ctx$.fooId, "-", $ctx$.fooUsername, ""); $r3$.ɵɵclassMapInterpolate2("foo foo-", $ctx$.fooId, "-", $ctx$.fooUsername, "");
$r3$.ɵɵstylingApply();
} }
} }
@ -461,10 +470,13 @@ describe('compiler compliance: styling', () => {
vars: 1, vars: 1,
template: function MyComponentWithoutInterpolation_Template(rf, $ctx$) { template: function MyComponentWithoutInterpolation_Template(rf, $ctx$) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵɵelement(0, "div"); $r3$.ɵɵelementStart(0, "div");
$r3$.ɵɵstyling();
$r3$.ɵɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵclassMap($ctx$.exp); $r3$.ɵɵclassMap($ctx$.exp);
$r3$.ɵɵstylingApply();
} }
} }
`; `;
@ -510,13 +522,16 @@ describe('compiler compliance: styling', () => {
vars: 4, vars: 4,
template: function MyComponent_Template(rf, $ctx$) { template: function MyComponent_Template(rf, $ctx$) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵɵelement(0, "div", $_c0$); $r3$.ɵɵelementStart(0, "div", $_c0$);
$r3$.ɵɵstyling();
$r3$.ɵɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer); $r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵstyleMap($ctx$.myStyleExp); $r3$.ɵɵstyleMap($ctx$.myStyleExp);
$r3$.ɵɵstyleProp("width", $ctx$.myWidth); $r3$.ɵɵstyleProp("width", $ctx$.myWidth);
$r3$.ɵɵstyleProp("height", $ctx$.myHeight); $r3$.ɵɵstyleProp("height", $ctx$.myHeight);
$r3$.ɵɵstylingApply();
$r3$.ɵɵattribute("style", "border-width: 10px", $r3$.ɵɵsanitizeStyle); $r3$.ɵɵattribute("style", "border-width: 10px", $r3$.ɵɵsanitizeStyle);
} }
}, },
@ -557,11 +572,14 @@ describe('compiler compliance: styling', () => {
vars: 1, vars: 1,
template: function MyComponent_Template(rf, ctx) { template: function MyComponent_Template(rf, ctx) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵɵelement(0, "div"); $r3$.ɵɵelementStart(0, "div");
$r3$.ɵɵstyling();
$r3$.ɵɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer); $r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵstyleProp("background-image", ctx.myImage); $r3$.ɵɵstyleProp("background-image", ctx.myImage);
$r3$.ɵɵstylingApply();
} }
}, },
encapsulation: 2 encapsulation: 2
@ -594,10 +612,13 @@ describe('compiler compliance: styling', () => {
const template = ` const template = `
template: function MyComponent_Template(rf, ctx) { template: function MyComponent_Template(rf, ctx) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵɵelement(0, "div"); $r3$.ɵɵelementStart(0, "div");
$r3$.ɵɵstyling();
$r3$.ɵɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵstyleProp("font-size", 12, "px"); $r3$.ɵɵstyleProp("font-size", 12, "px");
$r3$.ɵɵstylingApply();
} }
} }
`; `;
@ -655,10 +676,13 @@ describe('compiler compliance: styling', () => {
const template = ` const template = `
template: function MyComponent_Template(rf, $ctx$) { template: function MyComponent_Template(rf, $ctx$) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵɵelement(0, "div"); $r3$.ɵɵelementStart(0, "div");
$r3$.ɵɵstyling();
$r3$.ɵɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵclassMap($ctx$.myClassExp); $r3$.ɵɵclassMap($ctx$.myClassExp);
$r3$.ɵɵstylingApply();
} }
} }
`; `;
@ -704,12 +728,15 @@ describe('compiler compliance: styling', () => {
vars: 4, vars: 4,
template: function MyComponent_Template(rf, $ctx$) { template: function MyComponent_Template(rf, $ctx$) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵɵelement(0, "div", $e0_attrs$); $r3$.ɵɵelementStart(0, "div", $e0_attrs$);
$r3$.ɵɵstyling();
$r3$.ɵɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵclassMap($ctx$.myClassExp); $r3$.ɵɵclassMap($ctx$.myClassExp);
$r3$.ɵɵclassProp("apple", $ctx$.yesToApple); $r3$.ɵɵclassProp("apple", $ctx$.yesToApple);
$r3$.ɵɵclassProp("orange", $ctx$.yesToOrange); $r3$.ɵɵclassProp("orange", $ctx$.yesToOrange);
$r3$.ɵɵstylingApply();
$r3$.ɵɵattribute("class", "banana"); $r3$.ɵɵattribute("class", "banana");
} }
}, },
@ -817,12 +844,15 @@ describe('compiler compliance: styling', () => {
const template = ` const template = `
template: function MyComponent_Template(rf, $ctx$) { template: function MyComponent_Template(rf, $ctx$) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵɵelement(0, "div"); $r3$.ɵɵelementStart(0, "div");
$r3$.ɵɵstyling();
$r3$.ɵɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer); $r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵstyleMap($ctx$.myStyleExp); $r3$.ɵɵstyleMap($ctx$.myStyleExp);
$r3$.ɵɵclassMap($ctx$.myClassExp); $r3$.ɵɵclassMap($ctx$.myClassExp);
$r3$.ɵɵstylingApply();
} }
} }
`; `;
@ -857,6 +887,7 @@ describe('compiler compliance: styling', () => {
template: function MyComponent_Template(rf, $ctx$) { template: function MyComponent_Template(rf, $ctx$) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵɵelementStart(0, "div"); $r3$.ɵɵelementStart(0, "div");
$r3$.ɵɵstyling();
$r3$.ɵɵpipe(1, "stylePipe"); $r3$.ɵɵpipe(1, "stylePipe");
$r3$.ɵɵpipe(2, "classPipe"); $r3$.ɵɵpipe(2, "classPipe");
$r3$.ɵɵelementEnd(); $r3$.ɵɵelementEnd();
@ -865,6 +896,7 @@ describe('compiler compliance: styling', () => {
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer); $r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵstyleMap($r3$.ɵɵpipeBind1(1, 2, $ctx$.myStyleExp)); $r3$.ɵɵstyleMap($r3$.ɵɵpipeBind1(1, 2, $ctx$.myStyleExp));
$r3$.ɵɵclassMap($r3$.ɵɵpipeBind1(2, 4, $ctx$.myClassExp)); $r3$.ɵɵclassMap($r3$.ɵɵpipeBind1(2, 4, $ctx$.myClassExp));
$r3$.ɵɵstylingApply();
} }
} }
`; `;
@ -907,6 +939,7 @@ describe('compiler compliance: styling', () => {
template: function MyComponent_Template(rf, $ctx$) { template: function MyComponent_Template(rf, $ctx$) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵɵelementStart(0, "div"); $r3$.ɵɵelementStart(0, "div");
$r3$.ɵɵstyling();
$r3$.ɵɵpipe(1, "pipe"); $r3$.ɵɵpipe(1, "pipe");
$r3$.ɵɵpipe(2, "pipe"); $r3$.ɵɵpipe(2, "pipe");
$r3$.ɵɵpipe(3, "pipe"); $r3$.ɵɵpipe(3, "pipe");
@ -921,6 +954,7 @@ describe('compiler compliance: styling', () => {
$r3$.ɵɵstyleProp("bar", $r3$.ɵɵpipeBind2(2, 9, $ctx$.barExp, 3000)); $r3$.ɵɵstyleProp("bar", $r3$.ɵɵpipeBind2(2, 9, $ctx$.barExp, 3000));
$r3$.ɵɵstyleProp("baz", $r3$.ɵɵpipeBind2(3, 12, $ctx$.bazExp, 4000)); $r3$.ɵɵstyleProp("baz", $r3$.ɵɵpipeBind2(3, 12, $ctx$.bazExp, 4000));
$r3$.ɵɵclassProp("foo", $r3$.ɵɵpipeBind2(4, 15, $ctx$.fooExp, 2000)); $r3$.ɵɵclassProp("foo", $r3$.ɵɵpipeBind2(4, 15, $ctx$.fooExp, 2000));
$r3$.ɵɵstylingApply();
$r3$.ɵɵadvance(5); $r3$.ɵɵadvance(5);
$r3$.ɵɵtextInterpolate1(" ", $ctx$.item, ""); $r3$.ɵɵtextInterpolate1(" ", $ctx$.item, "");
} }
@ -965,12 +999,16 @@ describe('compiler compliance: styling', () => {
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵstyleProp("width", $ctx$.w1); $r3$.ɵɵstyleProp("width", $ctx$.w1);
$r3$.ɵɵstylingApply();
$r3$.ɵɵadvance(1); $r3$.ɵɵadvance(1);
$r3$.ɵɵstyleProp("height", $ctx$.h1); $r3$.ɵɵstyleProp("height", $ctx$.h1);
$r3$.ɵɵstylingApply();
$r3$.ɵɵadvance(1); $r3$.ɵɵadvance(1);
$r3$.ɵɵclassProp("active", $ctx$.a1); $r3$.ɵɵclassProp("active", $ctx$.a1);
$r3$.ɵɵstylingApply();
$r3$.ɵɵadvance(1); $r3$.ɵɵadvance(1);
$r3$.ɵɵclassProp("removed", $ctx$.r1); $r3$.ɵɵclassProp("removed", $ctx$.r1);
$r3$.ɵɵstylingApply();
} }
} }
`; `;
@ -1020,6 +1058,7 @@ describe('compiler compliance: styling', () => {
if (rf & 1) { if (rf & 1) {
$r3$.ɵɵallocHostVars(4); $r3$.ɵɵallocHostVars(4);
$r3$.ɵɵelementHostAttrs($e0_attrs$); $r3$.ɵɵelementHostAttrs($e0_attrs$);
$r3$.ɵɵstyling();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer); $r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
@ -1027,6 +1066,7 @@ describe('compiler compliance: styling', () => {
$r3$.ɵɵclassMap(ctx.myClass); $r3$.ɵɵclassMap(ctx.myClass);
$r3$.ɵɵstyleProp("color", ctx.myColorProp); $r3$.ɵɵstyleProp("color", ctx.myColorProp);
$r3$.ɵɵclassProp("foo", ctx.myFooClass); $r3$.ɵɵclassProp("foo", ctx.myFooClass);
$r3$.ɵɵstylingApply();
} }
}, },
consts: 0, consts: 0,
@ -1078,6 +1118,7 @@ describe('compiler compliance: styling', () => {
hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) { hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵɵallocHostVars(6); $r3$.ɵɵallocHostVars(6);
$r3$.ɵɵstyling();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer); $r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
@ -1087,6 +1128,7 @@ describe('compiler compliance: styling', () => {
$r3$.ɵɵstyleProp("width", ctx.myWidthProp); $r3$.ɵɵstyleProp("width", ctx.myWidthProp);
$r3$.ɵɵclassProp("bar", ctx.myBarClass); $r3$.ɵɵclassProp("bar", ctx.myBarClass);
$r3$.ɵɵclassProp("foo", ctx.myFooClass); $r3$.ɵɵclassProp("foo", ctx.myFooClass);
$r3$.ɵɵstylingApply();
} }
}, },
consts: 0, consts: 0,
@ -1137,7 +1179,9 @@ describe('compiler compliance: styling', () => {
const template = ` const template = `
function MyComponent_Template(rf, ctx) { function MyComponent_Template(rf, ctx) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵɵelement(0, "div"); $r3$.ɵɵelementStart(0, "div");
$r3$.ɵɵstyling();
$r3$.ɵɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer); $r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
@ -1145,6 +1189,7 @@ describe('compiler compliance: styling', () => {
$r3$.ɵɵclassMap(ctx.myClassExp); $r3$.ɵɵclassMap(ctx.myClassExp);
$r3$.ɵɵstyleProp("height", ctx.myHeightExp); $r3$.ɵɵstyleProp("height", ctx.myHeightExp);
$r3$.ɵɵclassProp("bar", ctx.myBarClassExp); $r3$.ɵɵclassProp("bar", ctx.myBarClassExp);
$r3$.ɵɵstylingApply();
} }
}, },
`; `;
@ -1153,6 +1198,7 @@ describe('compiler compliance: styling', () => {
hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) { hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵɵallocHostVars(4); $r3$.ɵɵallocHostVars(4);
$r3$.ɵɵstyling();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer); $r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
@ -1160,6 +1206,7 @@ describe('compiler compliance: styling', () => {
$r3$.ɵɵclassMap(ctx.myClassExp); $r3$.ɵɵclassMap(ctx.myClassExp);
$r3$.ɵɵstyleProp("width", ctx.myWidthExp); $r3$.ɵɵstyleProp("width", ctx.myWidthExp);
$r3$.ɵɵclassProp("foo", ctx.myFooClassExp); $r3$.ɵɵclassProp("foo", ctx.myFooClassExp);
$r3$.ɵɵstylingApply();
} }
}, },
`; `;
@ -1219,29 +1266,35 @@ describe('compiler compliance: styling', () => {
function ClassDirective_HostBindings(rf, ctx, elIndex) { function ClassDirective_HostBindings(rf, ctx, elIndex) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵɵallocHostVars(1); $r3$.ɵɵallocHostVars(1);
$r3$.ɵɵstyling();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵclassMap(ctx.myClassMap); $r3$.ɵɵclassMap(ctx.myClassMap);
$r3$.ɵɵstylingApply();
} }
} }
function WidthDirective_HostBindings(rf, ctx, elIndex) { function WidthDirective_HostBindings(rf, ctx, elIndex) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵɵallocHostVars(2); $r3$.ɵɵallocHostVars(2);
$r3$.ɵɵstyling();
} }
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);
$r3$.ɵɵstylingApply();
} }
} }
function HeightDirective_HostBindings(rf, ctx, elIndex) { function HeightDirective_HostBindings(rf, ctx, elIndex) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵɵallocHostVars(2); $r3$.ɵɵallocHostVars(2);
$r3$.ɵɵstyling();
} }
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);
$r3$.ɵɵstylingApply();
} }
} }
@ -1283,24 +1336,34 @@ describe('compiler compliance: styling', () => {
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵclassMapInterpolateV(["a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h", ctx.eight, "i", ctx.nine, "j"]); $r3$.ɵɵclassMapInterpolateV(["a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h", ctx.eight, "i", ctx.nine, "j"]);
$r3$.ɵɵstylingApply();
$r3$.ɵɵadvance(1); $r3$.ɵɵadvance(1);
$r3$.ɵɵclassMapInterpolate8("a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h", ctx.eight, "i"); $r3$.ɵɵclassMapInterpolate8("a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h", ctx.eight, "i");
$r3$.ɵɵstylingApply();
$r3$.ɵɵadvance(1); $r3$.ɵɵadvance(1);
$r3$.ɵɵclassMapInterpolate7("a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h"); $r3$.ɵɵclassMapInterpolate7("a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h");
$r3$.ɵɵstylingApply();
$r3$.ɵɵadvance(1); $r3$.ɵɵadvance(1);
$r3$.ɵɵclassMapInterpolate6("a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g"); $r3$.ɵɵclassMapInterpolate6("a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g");
$r3$.ɵɵstylingApply();
$r3$.ɵɵadvance(1); $r3$.ɵɵadvance(1);
$r3$.ɵɵclassMapInterpolate5("a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f"); $r3$.ɵɵclassMapInterpolate5("a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f");
$r3$.ɵɵstylingApply();
$r3$.ɵɵadvance(1); $r3$.ɵɵadvance(1);
$r3$.ɵɵclassMapInterpolate4("a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e"); $r3$.ɵɵclassMapInterpolate4("a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e");
$r3$.ɵɵstylingApply();
$r3$.ɵɵadvance(1); $r3$.ɵɵadvance(1);
$r3$.ɵɵclassMapInterpolate3("a", ctx.one, "b", ctx.two, "c", ctx.three, "d"); $r3$.ɵɵclassMapInterpolate3("a", ctx.one, "b", ctx.two, "c", ctx.three, "d");
$r3$.ɵɵstylingApply();
$r3$.ɵɵadvance(1); $r3$.ɵɵadvance(1);
$r3$.ɵɵclassMapInterpolate2("a", ctx.one, "b", ctx.two, "c"); $r3$.ɵɵclassMapInterpolate2("a", ctx.one, "b", ctx.two, "c");
$r3$.ɵɵstylingApply();
$r3$.ɵɵadvance(1); $r3$.ɵɵadvance(1);
$r3$.ɵɵclassMapInterpolate1("a", ctx.one, "b"); $r3$.ɵɵclassMapInterpolate1("a", ctx.one, "b");
$r3$.ɵɵstylingApply();
$r3$.ɵɵadvance(1); $r3$.ɵɵadvance(1);
$r3$.ɵɵclassMap(ctx.one); $r3$.ɵɵclassMap(ctx.one);
$r3$.ɵɵstylingApply();
} }
`; `;
@ -1375,24 +1438,34 @@ describe('compiler compliance: styling', () => {
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵstylePropInterpolateV("color", ["a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h", ctx.eight, "i", ctx.nine, "j"]); $r3$.ɵɵstylePropInterpolateV("color", ["a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h", ctx.eight, "i", ctx.nine, "j"]);
$r3$.ɵɵstylingApply();
$r3$.ɵɵadvance(1); $r3$.ɵɵadvance(1);
$r3$.ɵɵstylePropInterpolate8("color", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h", ctx.eight, "i"); $r3$.ɵɵstylePropInterpolate8("color", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h", ctx.eight, "i");
$r3$.ɵɵstylingApply();
$r3$.ɵɵadvance(1); $r3$.ɵɵadvance(1);
$r3$.ɵɵstylePropInterpolate7("color", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h"); $r3$.ɵɵstylePropInterpolate7("color", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h");
$r3$.ɵɵstylingApply();
$r3$.ɵɵadvance(1); $r3$.ɵɵadvance(1);
$r3$.ɵɵstylePropInterpolate6("color", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g"); $r3$.ɵɵstylePropInterpolate6("color", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g");
$r3$.ɵɵstylingApply();
$r3$.ɵɵadvance(1); $r3$.ɵɵadvance(1);
$r3$.ɵɵstylePropInterpolate5("color", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f"); $r3$.ɵɵstylePropInterpolate5("color", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f");
$r3$.ɵɵstylingApply();
$r3$.ɵɵadvance(1); $r3$.ɵɵadvance(1);
$r3$.ɵɵstylePropInterpolate4("color", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e"); $r3$.ɵɵstylePropInterpolate4("color", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e");
$r3$.ɵɵstylingApply();
$r3$.ɵɵadvance(1); $r3$.ɵɵadvance(1);
$r3$.ɵɵstylePropInterpolate3("color", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d"); $r3$.ɵɵstylePropInterpolate3("color", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d");
$r3$.ɵɵstylingApply();
$r3$.ɵɵadvance(1); $r3$.ɵɵadvance(1);
$r3$.ɵɵstylePropInterpolate2("color", "a", ctx.one, "b", ctx.two, "c"); $r3$.ɵɵstylePropInterpolate2("color", "a", ctx.one, "b", ctx.two, "c");
$r3$.ɵɵstylingApply();
$r3$.ɵɵadvance(1); $r3$.ɵɵadvance(1);
$r3$.ɵɵstylePropInterpolate1("color", "a", ctx.one, "b"); $r3$.ɵɵstylePropInterpolate1("color", "a", ctx.one, "b");
$r3$.ɵɵstylingApply();
$r3$.ɵɵadvance(1); $r3$.ɵɵadvance(1);
$r3$.ɵɵstyleProp("color", ctx.one); $r3$.ɵɵstyleProp("color", ctx.one);
$r3$.ɵɵstylingApply();
} }
`; `;
@ -1423,6 +1496,7 @@ describe('compiler compliance: styling', () => {
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵstylePropInterpolate2("width", "a", ctx.one, "b", ctx.two, "c", "px"); $r3$.ɵɵstylePropInterpolate2("width", "a", ctx.one, "b", ctx.two, "c", "px");
$r3$.ɵɵstylingApply();
} }
`; `;
@ -1453,6 +1527,7 @@ describe('compiler compliance: styling', () => {
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵstylePropInterpolate2("width", "a", ctx.one, "b", ctx.two, "c"); $r3$.ɵɵstylePropInterpolate2("width", "a", ctx.one, "b", ctx.two, "c");
$r3$.ɵɵstylingApply();
} }
`; `;
@ -1508,12 +1583,14 @@ describe('compiler compliance: styling', () => {
if (rf & 1) { if (rf & 1) {
$r3$.ɵɵallocHostVars(4); $r3$.ɵɵallocHostVars(4);
$r3$.ɵɵelementHostAttrs($_c0$); $r3$.ɵɵelementHostAttrs($_c0$);
$r3$.ɵɵstyling();
} }
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);
$r3$.ɵɵstyleMap(ctx.myStyle); $r3$.ɵɵstyleMap(ctx.myStyle);
$r3$.ɵɵclassMap(ctx.myClass); $r3$.ɵɵclassMap(ctx.myClass);
$r3$.ɵɵstylingApply();
} }
} }
`; `;
@ -1550,11 +1627,13 @@ describe('compiler compliance: styling', () => {
hostBindings: function WidthDirective_HostBindings(rf, ctx, elIndex) { hostBindings: function WidthDirective_HostBindings(rf, ctx, elIndex) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵɵallocHostVars(4); $r3$.ɵɵallocHostVars(4);
$r3$.ɵɵstyling();
} }
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);
$r3$.ɵɵclassProp("foo", ctx.myFooClass); $r3$.ɵɵclassProp("foo", ctx.myFooClass);
$r3$.ɵɵstylingApply();
} }
} }
`; `;
@ -1590,6 +1669,7 @@ describe('compiler compliance: styling', () => {
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer); $r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵstyleProp("background-image", ctx.bgExp); $r3$.ɵɵstyleProp("background-image", ctx.bgExp);
$r3$.ɵɵstylingApply();
} }
} }
@ -1625,6 +1705,7 @@ describe('compiler compliance: styling', () => {
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer); $r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵstyleMap(ctx.mapExp); $r3$.ɵɵstyleMap(ctx.mapExp);
$r3$.ɵɵstylingApply();
} }
} }
@ -1661,6 +1742,7 @@ describe('compiler compliance: styling', () => {
if (rf & 2) { if (rf & 2) {
$r3$.ɵɵclassMap(ctx.mapExp); $r3$.ɵɵclassMap(ctx.mapExp);
$r3$.ɵɵclassProp("name", ctx.nameExp); $r3$.ɵɵclassProp("name", ctx.nameExp);
$r3$.ɵɵstylingApply();
} }
} }
@ -1723,6 +1805,7 @@ describe('compiler compliance: styling', () => {
$r3$.ɵɵpureFunction2(6, _c1, ctx._animValue, $r3$.ɵɵpureFunction2(6, _c1, ctx._animValue,
$r3$.ɵɵpureFunction2(3, _c0, ctx._animParam1, ctx._animParam2))); $r3$.ɵɵpureFunction2(3, _c0, ctx._animParam1, ctx._animParam2)));
$r3$.ɵɵclassProp("foo", ctx.foo); $r3$.ɵɵclassProp("foo", ctx.foo);
$r3$.ɵɵstylingApply();
} }
} }
`; `;

View File

@ -1867,11 +1867,13 @@ runInEachFileSystem(os => {
i0.ɵɵlistener("click", function FooCmp_click_HostBindingHandler($event) { return ctx.onClick($event); }); i0.ɵɵlistener("click", function FooCmp_click_HostBindingHandler($event) { return ctx.onClick($event); });
i0.ɵɵlistener("click", function FooCmp_click_HostBindingHandler($event) { return ctx.onBodyClick($event); }, false, i0.ɵɵresolveBody); i0.ɵɵlistener("click", function FooCmp_click_HostBindingHandler($event) { return ctx.onBodyClick($event); }, false, i0.ɵɵresolveBody);
i0.ɵɵlistener("change", function FooCmp_change_HostBindingHandler($event) { return ctx.onChange(ctx.arg1, ctx.arg2, ctx.arg3); }); i0.ɵɵlistener("change", function FooCmp_change_HostBindingHandler($event) { return ctx.onChange(ctx.arg1, ctx.arg2, ctx.arg3); });
i0.ɵɵstyling();
} }
if (rf & 2) { if (rf & 2) {
i0.ɵɵhostProperty("prop", ctx.bar); i0.ɵɵhostProperty("prop", ctx.bar);
i0.ɵɵattribute("hello", ctx.foo); i0.ɵɵattribute("hello", ctx.foo);
i0.ɵɵclassProp("someclass", ctx.someClass); i0.ɵɵclassProp("someclass", ctx.someClass);
i0.ɵɵstylingApply();
} }
} }
`; `;

View File

@ -69,6 +69,8 @@ export class Identifiers {
static elementContainer: o.ExternalReference = {name: 'ɵɵelementContainer', moduleName: CORE}; static elementContainer: o.ExternalReference = {name: 'ɵɵelementContainer', moduleName: CORE};
static styling: o.ExternalReference = {name: 'ɵɵstyling', moduleName: CORE};
static styleMap: o.ExternalReference = {name: 'ɵɵstyleMap', moduleName: CORE}; static styleMap: o.ExternalReference = {name: 'ɵɵstyleMap', moduleName: CORE};
static classMap: o.ExternalReference = {name: 'ɵɵclassMap', moduleName: CORE}; static classMap: o.ExternalReference = {name: 'ɵɵclassMap', moduleName: CORE};
@ -113,6 +115,8 @@ export class Identifiers {
static stylePropInterpolateV: static stylePropInterpolateV:
o.ExternalReference = {name: 'ɵɵstylePropInterpolateV', moduleName: CORE}; o.ExternalReference = {name: 'ɵɵstylePropInterpolateV', moduleName: CORE};
static stylingApply: o.ExternalReference = {name: 'ɵɵstylingApply', moduleName: CORE};
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 elementHostAttrs: o.ExternalReference = {name: 'ɵɵelementHostAttrs', moduleName: CORE};

View File

@ -713,6 +713,16 @@ function createHostBindingsFunction(
} }
if (styleBuilder.hasBindings) { if (styleBuilder.hasBindings) {
// singular style/class bindings (things like `[style.prop]` and `[class.name]`)
// MUST be registered on a given element within the component/directive
// templateFn/hostBindingsFn functions. The instruction below will figure out
// what all the bindings are and then generate the statements required to register
// those bindings to the element via `styling`.
const stylingInstruction = styleBuilder.buildStylingInstruction(null, constantPool);
if (stylingInstruction) {
createStatements.push(createStylingStmt(stylingInstruction, bindingContext, bindingFn));
}
// 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
// the update block of a component/directive templateFn/hostBindingsFn so that the bindings // the update block of a component/directive templateFn/hostBindingsFn so that the bindings
// are evaluated and updated for the element. // are evaluated and updated for the element.

View File

@ -7,7 +7,7 @@
*/ */
import {ConstantPool} from '../../constant_pool'; import {ConstantPool} from '../../constant_pool';
import {AttributeMarker} from '../../core'; import {AttributeMarker} from '../../core';
import {AST, ASTWithSource, BindingPipe, BindingType, Interpolation} from '../../expression_parser/ast'; import {AST, BindingType, Interpolation} from '../../expression_parser/ast';
import * as o from '../../output/output_ast'; import * as o from '../../output/output_ast';
import {ParseSourceSpan} from '../../parse_util'; import {ParseSourceSpan} from '../../parse_util';
import {isEmptyExpression} from '../../template_parser/template_parser'; import {isEmptyExpression} from '../../template_parser/template_parser';
@ -68,6 +68,7 @@ interface BoundStylingEntry {
* classMap(...) * classMap(...)
* styleProp(...) * styleProp(...)
* classProp(...) * classProp(...)
* stylingApply(...)
* } * }
* *
* The creation/update methods within the builder class produce these instructions. * The creation/update methods within the builder class produce these instructions.
@ -80,7 +81,6 @@ export class StylingBuilder {
* (i.e. `[style]`, `[class]`, `[style.prop]` or `[class.name]`) * (i.e. `[style]`, `[class]`, `[style.prop]` or `[class.name]`)
*/ */
public hasBindings = false; public hasBindings = false;
public hasBindingsWithPipes = false;
/** the input for [class] (if it exists) */ /** the input for [class] (if it exists) */
private _classMapInput: BoundStylingEntry|null = null; private _classMapInput: BoundStylingEntry|null = null;
@ -187,7 +187,6 @@ export class StylingBuilder {
} }
this._lastStylingInput = entry; this._lastStylingInput = entry;
this._firstStylingInput = this._firstStylingInput || entry; this._firstStylingInput = this._firstStylingInput || entry;
this._checkForPipes(value);
this.hasBindings = true; this.hasBindings = true;
return entry; return entry;
} }
@ -208,17 +207,10 @@ export class StylingBuilder {
} }
this._lastStylingInput = entry; this._lastStylingInput = entry;
this._firstStylingInput = this._firstStylingInput || entry; this._firstStylingInput = this._firstStylingInput || entry;
this._checkForPipes(value);
this.hasBindings = true; this.hasBindings = true;
return entry; return entry;
} }
private _checkForPipes(value: AST) {
if ((value instanceof ASTWithSource) && (value.ast instanceof BindingPipe)) {
this.hasBindingsWithPipes = true;
}
}
/** /**
* Registers the element's static style string value to the builder. * Registers the element's static style string value to the builder.
* *
@ -292,6 +284,25 @@ export class StylingBuilder {
return null; return null;
} }
/**
* Builds an instruction with all the expressions and parameters for `styling`.
*
* The instruction generation code below is used for producing the AOT statement code which is
* responsible for registering style/class bindings to an element.
*/
buildStylingInstruction(sourceSpan: ParseSourceSpan|null, constantPool: ConstantPool):
StylingInstruction|null {
if (this.hasBindings) {
return {
sourceSpan,
allocateBindingSlots: 0,
reference: R3.styling,
params: () => [],
};
}
return null;
}
/** /**
* Builds an instruction with all the expressions and parameters for `classMap`. * Builds an instruction with all the expressions and parameters for `classMap`.
* *
@ -411,6 +422,15 @@ export class StylingBuilder {
return []; return [];
} }
private _buildApplyFn(): StylingInstruction {
return {
sourceSpan: this._lastStylingInput ? this._lastStylingInput.sourceSpan : null,
reference: R3.stylingApply,
allocateBindingSlots: 0,
params: () => { return []; }
};
}
private _buildSanitizerFn(): StylingInstruction { private _buildSanitizerFn(): StylingInstruction {
return { return {
sourceSpan: this._firstStylingInput ? this._firstStylingInput.sourceSpan : null, sourceSpan: this._firstStylingInput ? this._firstStylingInput.sourceSpan : null,
@ -440,6 +460,7 @@ export class StylingBuilder {
} }
instructions.push(...this._buildStyleInputs(valueConverter)); instructions.push(...this._buildStyleInputs(valueConverter));
instructions.push(...this._buildClassInputs(valueConverter)); instructions.push(...this._buildClassInputs(valueConverter));
instructions.push(this._buildApplyFn());
} }
return instructions; return instructions;
} }

View File

@ -623,10 +623,11 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
const hasChildren = (!isI18nRootElement && this.i18n) ? !hasTextChildrenOnly(element.children) : const hasChildren = (!isI18nRootElement && this.i18n) ? !hasTextChildrenOnly(element.children) :
element.children.length > 0; element.children.length > 0;
const createSelfClosingInstruction = !stylingBuilder.hasBindingsWithPipes && const createSelfClosingInstruction = !stylingBuilder.hasBindings &&
element.outputs.length === 0 && i18nAttrs.length === 0 && !hasChildren; element.outputs.length === 0 && i18nAttrs.length === 0 && !hasChildren;
const createSelfClosingI18nInstruction =
!createSelfClosingInstruction && hasTextChildrenOnly(element.children); const createSelfClosingI18nInstruction = !createSelfClosingInstruction &&
!stylingBuilder.hasBindings && hasTextChildrenOnly(element.children);
if (createSelfClosingInstruction) { if (createSelfClosingInstruction) {
this.creationInstruction( this.creationInstruction(
@ -680,6 +681,16 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
} }
} }
// The style bindings code is placed into two distinct blocks within the template function AOT
// code: creation and update. The creation code contains the `styling` instructions
// which will apply the collected binding values to the element. `styling` is
// designed to run inside of `elementStart` and `elementEnd`. The update instructions
// (things like `styleProp`, `classProp`, etc..) are applied later on in this
// file
this.processStylingInstruction(
elementIndex,
stylingBuilder.buildStylingInstruction(element.sourceSpan, this.constantPool), true);
// Generate Listeners (outputs) // Generate Listeners (outputs)
element.outputs.forEach((outputAst: t.BoundEvent) => { element.outputs.forEach((outputAst: t.BoundEvent) => {
this.creationInstruction( this.creationInstruction(

View File

@ -120,6 +120,7 @@ export {
ɵɵelementContainerStart, ɵɵelementContainerStart,
ɵɵelementContainerEnd, ɵɵelementContainerEnd,
ɵɵelementContainer, ɵɵelementContainer,
ɵɵstyling,
ɵɵstyleMap, ɵɵstyleMap,
ɵɵstyleSanitizer, ɵɵstyleSanitizer,
ɵɵclassMap, ɵɵclassMap,
@ -142,6 +143,7 @@ export {
ɵɵstylePropInterpolate7, ɵɵstylePropInterpolate7,
ɵɵstylePropInterpolate8, ɵɵstylePropInterpolate8,
ɵɵstylePropInterpolateV, ɵɵstylePropInterpolateV,
ɵɵstylingApply,
ɵɵclassProp, ɵɵclassProp,
ɵɵelementHostAttrs, ɵɵelementHostAttrs,

View File

@ -12,9 +12,10 @@ import {CONTAINER_HEADER_OFFSET, LContainer, NATIVE} from '../render3/interfaces
import {TElementNode, TNode, TNodeFlags, TNodeType} from '../render3/interfaces/node'; import {TElementNode, TNode, TNodeFlags, TNodeType} from '../render3/interfaces/node';
import {isComponentHost, isLContainer} from '../render3/interfaces/type_checks'; import {isComponentHost, isLContainer} from '../render3/interfaces/type_checks';
import {LView, PARENT, TData, TVIEW, T_HOST} from '../render3/interfaces/view'; import {LView, PARENT, TData, TVIEW, T_HOST} from '../render3/interfaces/view';
import {StylingMapArray, TStylingContext} from '../render3/styling_next/interfaces'; import {TStylingContext} from '../render3/styling_next/interfaces';
import {stylingMapToStringMap} from '../render3/styling_next/map_based_bindings';
import {NodeStylingDebug} from '../render3/styling_next/styling_debug'; import {NodeStylingDebug} from '../render3/styling_next/styling_debug';
import {isStylingContext, stylingMapToStringMap} from '../render3/styling_next/util'; import {isStylingContext} from '../render3/styling_next/util';
import {getComponent, getContext, getInjectionTokens, getInjector, getListeners, getLocalRefs, isBrowserEvents, loadLContext} from '../render3/util/discovery_utils'; import {getComponent, getContext, getInjectionTokens, getInjector, getListeners, getLocalRefs, isBrowserEvents, loadLContext} from '../render3/util/discovery_utils';
import {INTERPOLATION_DELIMITER, renderStringify} from '../render3/util/misc_utils'; import {INTERPOLATION_DELIMITER, renderStringify} from '../render3/util/misc_utils';
import {findComponentView} from '../render3/util/view_traversal_utils'; import {findComponentView} from '../render3/util/view_traversal_utils';
@ -430,11 +431,11 @@ function _getStylingDebugInfo(element: any, isClassBased: boolean) {
if (isClassBased) { if (isClassBased) {
return isStylingContext(tNode.classes) ? return isStylingContext(tNode.classes) ?
new NodeStylingDebug(tNode.classes as TStylingContext, lView, true).values : new NodeStylingDebug(tNode.classes as TStylingContext, lView, true).values :
stylingMapToStringMap(tNode.classes as StylingMapArray | null); stylingMapToStringMap(tNode.classes);
} else { } else {
return isStylingContext(tNode.styles) ? return isStylingContext(tNode.styles) ?
new NodeStylingDebug(tNode.styles as TStylingContext, lView, false).values : new NodeStylingDebug(tNode.styles as TStylingContext, lView, false).values :
stylingMapToStringMap(tNode.styles as StylingMapArray | null); stylingMapToStringMap(tNode.styles);
} }
} }

View File

@ -23,7 +23,7 @@ import {TElementNode, TNode, TNodeType} from './interfaces/node';
import {PlayerHandler} from './interfaces/player'; import {PlayerHandler} from './interfaces/player';
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer'; import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
import {CONTEXT, HEADER_OFFSET, LView, LViewFlags, RootContext, RootContextFlags, TVIEW} from './interfaces/view'; import {CONTEXT, HEADER_OFFSET, LView, LViewFlags, RootContext, RootContextFlags, TVIEW} from './interfaces/view';
import {getPreviousOrParentTNode, incrementActiveDirectiveId, resetComponentState, selectView, setActiveHostElement} from './state'; import {getPreviousOrParentTNode, resetComponentState, selectView, setActiveHostElement} from './state';
import {publishDefaultGlobalUtils} from './util/global_utils'; import {publishDefaultGlobalUtils} from './util/global_utils';
import {defaultScheduler, stringifyForError} from './util/misc_utils'; import {defaultScheduler, stringifyForError} from './util/misc_utils';
import {getRootContext} from './util/view_traversal_utils'; import {getRootContext} from './util/view_traversal_utils';
@ -217,7 +217,6 @@ export function createRootComponent<T>(
if (tView.firstTemplatePass && componentDef.hostBindings) { if (tView.firstTemplatePass && componentDef.hostBindings) {
const elementIndex = rootTNode.index - HEADER_OFFSET; const elementIndex = rootTNode.index - HEADER_OFFSET;
setActiveHostElement(elementIndex); setActiveHostElement(elementIndex);
incrementActiveDirectiveId();
const expando = tView.expandoInstructions !; const expando = tView.expandoInstructions !;
invokeHostBindingsInCreationMode( invokeHostBindingsInCreationMode(

View File

@ -11,6 +11,7 @@ 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 {isComponentDef} from '../interfaces/type_checks'; import {isComponentDef} from '../interfaces/type_checks';
import {adjustActiveDirectiveSuperClassDepthPosition} from '../state';
import {ɵɵNgOnChangesFeature} from './ng_onchanges_feature'; import {ɵɵNgOnChangesFeature} from './ng_onchanges_feature';
@ -176,8 +177,24 @@ function inheritHostBindings(
// to ensure we don't inherit it twice. // to ensure we don't inherit it twice.
if (superHostBindings !== prevHostBindings) { if (superHostBindings !== prevHostBindings) {
if (prevHostBindings) { if (prevHostBindings) {
// because inheritance is unknown during compile time, the runtime code
// needs to be informed of the super-class depth so that instruction code
// can distinguish one host bindings function from another. The reason why
// relying on the directive uniqueId exclusively is not enough is because the
// uniqueId value and the directive instance stay the same between hostBindings
// calls throughout the directive inheritance chain. This means that without
// a super-class depth value, there is no way to know whether a parent or
// sub-class host bindings function is currently being executed.
definition.hostBindings = (rf: RenderFlags, ctx: any, elementIndex: number) => { definition.hostBindings = (rf: RenderFlags, ctx: any, elementIndex: number) => {
// The reason why we increment first and then decrement is so that parent
// hostBindings calls have a higher id value compared to sub-class hostBindings
// calls (this way the leaf directive is always at a super-class depth of 0).
adjustActiveDirectiveSuperClassDepthPosition(1);
try {
superHostBindings(rf, ctx, elementIndex); superHostBindings(rf, ctx, elementIndex);
} finally {
adjustActiveDirectiveSuperClassDepthPosition(-1);
}
prevHostBindings(rf, ctx, elementIndex); prevHostBindings(rf, ctx, elementIndex);
}; };
} else { } else {

View File

@ -114,6 +114,8 @@ export {
ɵɵstylePropInterpolateV, ɵɵstylePropInterpolateV,
ɵɵstyleSanitizer, ɵɵstyleSanitizer,
ɵɵstyling,
ɵɵstylingApply,
ɵɵtemplate, ɵɵtemplate,
ɵɵtext, ɵɵtext,

View File

@ -8,10 +8,7 @@
import {assertDataInRange, assertGreaterThan} from '../../util/assert'; import {assertDataInRange, assertGreaterThan} from '../../util/assert';
import {executeCheckHooks, executeInitAndCheckHooks} from '../hooks'; import {executeCheckHooks, executeInitAndCheckHooks} from '../hooks';
import {FLAGS, HEADER_OFFSET, InitPhaseState, LView, LViewFlags, TVIEW} from '../interfaces/view'; import {FLAGS, HEADER_OFFSET, InitPhaseState, LView, LViewFlags, TVIEW} from '../interfaces/view';
import {ActiveElementFlags, executeElementExitFn, getCheckNoChangesMode, getLView, getSelectedIndex, hasActiveElementFlag, setSelectedIndex} from '../state'; import {getCheckNoChangesMode, getLView, getSelectedIndex, setSelectedIndex} from '../state';
import {resetStylingState} from '../styling_next/state';
/** /**
* Advances to an element for later binding instructions. * Advances to an element for later binding instructions.
@ -54,10 +51,6 @@ export function selectIndexInternal(lView: LView, index: number, checkNoChangesM
ngDevMode && assertGreaterThan(index, -1, 'Invalid index'); ngDevMode && assertGreaterThan(index, -1, 'Invalid index');
ngDevMode && assertDataInRange(lView, index + HEADER_OFFSET); ngDevMode && assertDataInRange(lView, index + HEADER_OFFSET);
if (hasActiveElementFlag(ActiveElementFlags.RunExitFn)) {
executeElementExitFn();
}
// Flush the initial hooks for elements in the view that have been added up to this point. // Flush the initial hooks for elements in the view that have been added up to this point.
// PERF WARNING: do NOT extract this to a separate function without running benchmarks // PERF WARNING: do NOT extract this to a separate function without running benchmarks
if (!checkNoChangesMode) { if (!checkNoChangesMode) {
@ -76,10 +69,6 @@ export function selectIndexInternal(lView: LView, index: number, checkNoChangesM
} }
} }
if (hasActiveElementFlag(ActiveElementFlags.ResetStylesOnExit)) {
resetStylingState();
}
// We must set the selected index *after* running the hooks, because hooks may have side-effects // We must set the selected index *after* running the hooks, because hooks may have side-effects
// that cause other template functions to run, thus updating the selected index, which is global // that cause other template functions to run, thus updating the selected index, which is global
// state. If we run `setSelectedIndex` *before* we run the hooks, in some cases the selected index // state. If we run `setSelectedIndex` *before* we run the hooks, in some cases the selected index

View File

@ -138,11 +138,11 @@ export function ɵɵelementEnd(): void {
} }
} }
if (hasClassInput(tNode)) { if (hasClassInput(tNode) && tNode.classes) {
setDirectiveStylingInput(tNode.classes, lView, tNode.inputs !['class']); setDirectiveStylingInput(tNode.classes, lView, tNode.inputs !['class']);
} }
if (hasStyleInput(tNode)) { if (hasStyleInput(tNode) && tNode.styles) {
setDirectiveStylingInput(tNode.styles, lView, tNode.inputs !['style']); setDirectiveStylingInput(tNode.styles, lView, tNode.inputs !['style']);
} }
} }
@ -236,12 +236,11 @@ export function ɵɵelementHostAttrs(attrs: TAttributes) {
} }
function setDirectiveStylingInput( function setDirectiveStylingInput(
context: TStylingContext | StylingMapArray | null, lView: LView, context: TStylingContext | StylingMapArray, lView: LView, stylingInputs: (string | number)[]) {
stylingInputs: (string | number)[]) {
// older versions of Angular treat the input as `null` in the // older versions of Angular treat the input as `null` in the
// event that the value does not exist at all. For this reason // event that the value does not exist at all. For this reason
// we can't have a styling value be an empty string. // we can't have a styling value be an empty string.
const value = (context && getInitialStylingValue(context)) || null; const value = getInitialStylingValue(context) || null;
// Ivy does an extra `[class]` write with a falsy value since the value // Ivy does an extra `[class]` write with a falsy value since the value
// is applied during creation mode. This is a deviation from VE and should // is applied during creation mode. This is a deviation from VE and should

View File

@ -29,9 +29,8 @@ import {isComponentDef, isComponentHost, isContentQueryHost, isLContainer, isRoo
import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TData, TVIEW, TView, T_HOST} from '../interfaces/view'; import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TData, TVIEW, TView, T_HOST} from '../interfaces/view';
import {assertNodeOfPossibleTypes} from '../node_assert'; import {assertNodeOfPossibleTypes} from '../node_assert';
import {isNodeMatchingSelectorList} from '../node_selector_matcher'; import {isNodeMatchingSelectorList} from '../node_selector_matcher';
import {ActiveElementFlags, executeElementExitFn, getBindingsEnabled, getCheckNoChangesMode, getIsParent, getLView, getPreviousOrParentTNode, getSelectedIndex, hasActiveElementFlag, incrementActiveDirectiveId, namespaceHTMLInternal, selectView, setActiveHostElement, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setPreviousOrParentTNode, setSelectedIndex} from '../state'; import {getBindingsEnabled, getCheckNoChangesMode, getIsParent, getLView, getPreviousOrParentTNode, getSelectedIndex, incrementActiveDirectiveId, namespaceHTMLInternal, selectView, setActiveHostElement, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setPreviousOrParentTNode, setSelectedIndex} from '../state';
import {renderStylingMap} from '../styling_next/bindings'; import {renderStylingMap} from '../styling_next/bindings';
import {resetStylingState} from '../styling_next/state';
import {NO_CHANGE} from '../tokens'; import {NO_CHANGE} from '../tokens';
import {ANIMATION_PROP_PREFIX, isAnimationProp} from '../util/attrs_utils'; import {ANIMATION_PROP_PREFIX, isAnimationProp} from '../util/attrs_utils';
import {INTERPOLATION_DELIMITER, renderStringify, stringifyForError} from '../util/misc_utils'; import {INTERPOLATION_DELIMITER, renderStringify, stringifyForError} from '../util/misc_utils';
@ -87,18 +86,16 @@ export function setHostBindings(tView: TView, viewData: LView): void {
} else { } else {
// If it's not a number, it's a host binding function that needs to be executed. // If it's not a number, it's a host binding function that needs to be executed.
if (instruction !== null) { if (instruction !== null) {
viewData[BINDING_INDEX] = bindingRootIndex;
const hostCtx = unwrapRNode(viewData[currentDirectiveIndex]);
instruction(RenderFlags.Update, hostCtx, currentElementIndex);
// Each directive gets a uniqueId value that is the same for both // Each directive gets a uniqueId value that is the same for both
// create and update calls when the hostBindings function is called. The // create and update calls when the hostBindings function is called. The
// directive uniqueId is not set anywhere--it is just incremented between // directive uniqueId is not set anywhere--it is just incremented between
// each hostBindings call and is useful for helping instruction code // each hostBindings call and is useful for helping instruction code
// uniquely determine which directive is currently active when executed. // uniquely determine which directive is currently active when executed.
// 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.
incrementActiveDirectiveId(); incrementActiveDirectiveId();
viewData[BINDING_INDEX] = bindingRootIndex;
const hostCtx = unwrapRNode(viewData[currentDirectiveIndex]);
instruction(RenderFlags.Update, hostCtx, currentElementIndex);
} }
currentDirectiveIndex++; currentDirectiveIndex++;
} }
@ -506,12 +503,6 @@ function executeTemplate<T>(
} }
templateFn(rf, context); templateFn(rf, context);
} finally { } finally {
if (hasActiveElementFlag(ActiveElementFlags.RunExitFn)) {
executeElementExitFn();
}
if (hasActiveElementFlag(ActiveElementFlags.ResetStylesOnExit)) {
resetStylingState();
}
setSelectedIndex(prevSelectedIndex); setSelectedIndex(prevSelectedIndex);
} }
} }
@ -1095,10 +1086,14 @@ function invokeDirectivesHostBindings(tView: TView, viewData: LView, tNode: TNod
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) {
// 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.
incrementActiveDirectiveId();
invokeHostBindingsInCreationMode(def, expando, directive, tNode, firstTemplatePass); invokeHostBindingsInCreationMode(def, expando, directive, tNode, firstTemplatePass);
// Each directive gets a uniqueId value that is the same for both
// create and update calls when the hostBindings function is called. The
// directive uniqueId is not set anywhere--it is just incremented between
// each hostBindings call and is useful for helping instruction code
// uniquely determine which directive is currently active when executed.
incrementActiveDirectiveId();
} else if (firstTemplatePass) { } else if (firstTemplatePass) {
expando.push(null); expando.push(null);
} }

View File

@ -116,6 +116,7 @@ export const angularCoreEnv: {[name: string]: Function} =
'ɵɵclassMapInterpolate7': r3.ɵɵclassMapInterpolate7, 'ɵɵclassMapInterpolate7': r3.ɵɵclassMapInterpolate7,
'ɵɵclassMapInterpolate8': r3.ɵɵclassMapInterpolate8, 'ɵɵclassMapInterpolate8': r3.ɵɵclassMapInterpolate8,
'ɵɵclassMapInterpolateV': r3.ɵɵclassMapInterpolateV, 'ɵɵclassMapInterpolateV': r3.ɵɵclassMapInterpolateV,
'ɵɵstyling': r3.ɵɵstyling,
'ɵɵstyleMap': r3.ɵɵstyleMap, 'ɵɵstyleMap': r3.ɵɵstyleMap,
'ɵɵstyleProp': r3.ɵɵstyleProp, 'ɵɵstyleProp': r3.ɵɵstyleProp,
'ɵɵstylePropInterpolate1': r3.ɵɵstylePropInterpolate1, 'ɵɵstylePropInterpolate1': r3.ɵɵstylePropInterpolate1,
@ -128,6 +129,7 @@ export const angularCoreEnv: {[name: string]: Function} =
'ɵɵstylePropInterpolate8': r3.ɵɵstylePropInterpolate8, 'ɵɵstylePropInterpolate8': r3.ɵɵstylePropInterpolate8,
'ɵɵstylePropInterpolateV': r3.ɵɵstylePropInterpolateV, 'ɵɵstylePropInterpolateV': r3.ɵɵstylePropInterpolateV,
'ɵɵstyleSanitizer': r3.ɵɵstyleSanitizer, 'ɵɵstyleSanitizer': r3.ɵɵstyleSanitizer,
'ɵɵstylingApply': r3.ɵɵstylingApply,
'ɵɵclassProp': r3.ɵɵclassProp, 'ɵɵclassProp': r3.ɵɵclassProp,
'ɵɵselect': r3.ɵɵselect, 'ɵɵselect': r3.ɵɵselect,
'ɵɵadvance': r3.ɵɵadvance, 'ɵɵadvance': r3.ɵɵadvance,

View File

@ -13,7 +13,7 @@ import {assertLViewOrUndefined} from './assert';
import {ComponentDef, DirectiveDef} from './interfaces/definition'; import {ComponentDef, DirectiveDef} from './interfaces/definition';
import {TElementNode, TNode, TViewNode} from './interfaces/node'; import {TElementNode, TNode, TViewNode} from './interfaces/node';
import {CONTEXT, DECLARATION_VIEW, LView, OpaqueViewState, TVIEW} from './interfaces/view'; import {CONTEXT, DECLARATION_VIEW, LView, OpaqueViewState, TVIEW} from './interfaces/view';
import {resetStylingState} from './styling_next/state'; import {resetAllStylingState, resetStylingState} from './styling_next/state';
/** /**
@ -129,38 +129,31 @@ export function getLView(): LView {
* The reason why this value is `1` instead of `0` is because the `0` * The reason why this value is `1` instead of `0` is because the `0`
* value is reserved for the template. * value is reserved for the template.
*/ */
let activeDirectiveId = 0; const MIN_DIRECTIVE_ID = 1;
let activeDirectiveId = MIN_DIRECTIVE_ID;
/** /**
* Flags used for an active element during change detection. * Position depth (with respect from leaf to root) in a directive sub-class inheritance chain.
*/
let activeDirectiveSuperClassDepthPosition = 0;
/**
* Total count of how many directives are a part of an inheritance chain.
* *
* These flags are used within other instructions to inform cleanup or * When directives are sub-classed (extended) from one to another, Angular
* exit operations to run when an element is being processed. * needs to keep track of exactly how many were encountered so it can accurately
* generate the next directive id (once the next directive id is visited).
* Normally the next directive id just a single incremented value from the
* previous one, however, if the previous directive is a part of an inheritance
* chain (a series of sub-classed directives) then the incremented value must
* also take into account the total amount of sub-classed values.
* *
* Note that these flags are reset each time an element changes (whether it * Note that this value resets back to zero once the next directive is
* happens when `advance()` is run or when change detection exits out of a template * visited (when `incrementActiveDirectiveId` or `setActiveHostElement`
* function or when all host bindings are processed for an element). * is called).
*/ */
export const enum ActiveElementFlags { let activeDirectiveSuperClassHeight = 0;
Initial = 0b00,
RunExitFn = 0b01,
ResetStylesOnExit = 0b10,
Size = 2,
}
/**
* Determines whether or not a flag is currently set for the active element.
*/
export function hasActiveElementFlag(flag: ActiveElementFlags) {
return (_selectedIndex & flag) === flag;
}
/**
* Sets a flag is for the active element.
*/
export function setActiveElementFlag(flag: ActiveElementFlags) {
_selectedIndex |= flag;
}
/** /**
* Sets the active directive host element and resets the directive id value * Sets the active directive host element and resets the directive id value
@ -170,44 +163,14 @@ export function setActiveElementFlag(flag: ActiveElementFlags) {
* the directive/component instance lives * the directive/component instance lives
*/ */
export function setActiveHostElement(elementIndex: number | null = null) { export function setActiveHostElement(elementIndex: number | null = null) {
if (getSelectedIndex() !== elementIndex) { if (_selectedIndex !== elementIndex) {
if (hasActiveElementFlag(ActiveElementFlags.RunExitFn)) {
executeElementExitFn();
}
if (hasActiveElementFlag(ActiveElementFlags.ResetStylesOnExit)) {
resetStylingState();
}
setSelectedIndex(elementIndex === null ? -1 : elementIndex); setSelectedIndex(elementIndex === null ? -1 : elementIndex);
activeDirectiveId = 0; activeDirectiveId = elementIndex === null ? 0 : MIN_DIRECTIVE_ID;
activeDirectiveSuperClassDepthPosition = 0;
activeDirectiveSuperClassHeight = 0;
} }
} }
let _elementExitFn: Function|null = null;
export function executeElementExitFn() {
_elementExitFn !();
// TODO (matsko|misko): remove this unassignment once the state management of
// global variables are better managed.
_selectedIndex &= ~ActiveElementFlags.RunExitFn;
}
/**
* Queues a function to be run once the element is "exited" in CD.
*
* Change detection will focus on an element either when the `advance()`
* instruction is called or when the template or host bindings instruction
* code is invoked. The element is then "exited" when the next element is
* selected or when change detection for the template or host bindings is
* complete. When this occurs (the element change operation) then an exit
* function will be invoked if it has been set. This function can be used
* to assign that exit function.
*
* @param fn
*/
export function setElementExitFn(fn: Function): void {
setActiveElementFlag(ActiveElementFlags.RunExitFn);
_elementExitFn = fn;
}
/** /**
* Returns the current id value of the current directive. * Returns the current id value of the current directive.
* *
@ -248,12 +211,71 @@ export function getActiveDirectiveId() {
* different set of directives). * different set of directives).
*/ */
export function incrementActiveDirectiveId() { export function incrementActiveDirectiveId() {
// Each directive gets a uniqueId value that is the same for both activeDirectiveId += 1 + activeDirectiveSuperClassHeight;
// create and update calls when the hostBindings function is called. The
// directive uniqueId is not set anywhere--it is just incremented between // because we are dealing with a new directive this
// each hostBindings call and is useful for helping instruction code // means we have exited out of the inheritance chain
// uniquely determine which directive is currently active when executed. activeDirectiveSuperClassDepthPosition = 0;
activeDirectiveId += 1; activeDirectiveSuperClassHeight = 0;
}
/**
* Set the current super class (reverse inheritance) position depth for a directive.
*
* For example we have two directives: Child and Other (but Child is a sub-class of Parent)
* <div child-dir other-dir></div>
*
* // increment
* parentInstance->hostBindings() (depth = 1)
* // decrement
* childInstance->hostBindings() (depth = 0)
* otherInstance->hostBindings() (depth = 0 b/c it's a different directive)
*
* Note that this is only active when `hostBinding` functions are being processed.
*/
export function adjustActiveDirectiveSuperClassDepthPosition(delta: number) {
activeDirectiveSuperClassDepthPosition += delta;
// we keep track of the height value so that when the next directive is visited
// then Angular knows to generate a new directive id value which has taken into
// account how many sub-class directives were a part of the previous directive.
activeDirectiveSuperClassHeight =
Math.max(activeDirectiveSuperClassHeight, activeDirectiveSuperClassDepthPosition);
}
/**
* Returns he current depth of the super/sub class inheritance chain.
*
* This will return how many inherited directive/component classes
* exist in the current chain.
*
* ```typescript
* @Directive({ selector: '[super-dir]' })
* class SuperDir {}
*
* @Directive({ selector: '[sub-dir]' })
* class SubDir extends SuperDir {}
*
* // if `<div sub-dir>` is used then the super class height is `1`
* // if `<div super-dir>` is used then the super class height is `0`
* ```
*/
export function getActiveDirectiveSuperClassHeight() {
return activeDirectiveSuperClassHeight;
}
/**
* Returns the current super class (reverse inheritance) depth for a directive.
*
* This is designed to help instruction code distinguish different hostBindings
* calls from each other when a directive has extended from another directive.
* Normally using the directive id value is enough, but with the case
* of parent/sub-class directive inheritance more information is required.
*
* Note that this is only active when `hostBinding` functions are being processed.
*/
export function getActiveDirectiveSuperClassDepth() {
return activeDirectiveSuperClassDepthPosition;
} }
/** /**
@ -425,10 +447,10 @@ export function resetComponentState() {
elementDepthCount = 0; elementDepthCount = 0;
bindingsEnabled = true; bindingsEnabled = true;
setCurrentStyleSanitizer(null); setCurrentStyleSanitizer(null);
resetAllStylingState();
} }
/* tslint:disable */ let _selectedIndex = -1;
let _selectedIndex = -1 << ActiveElementFlags.Size;
/** /**
* Gets the most recent index passed to {@link select} * Gets the most recent index passed to {@link select}
@ -437,7 +459,7 @@ let _selectedIndex = -1 << ActiveElementFlags.Size;
* current `LView` to act on. * current `LView` to act on.
*/ */
export function getSelectedIndex() { export function getSelectedIndex() {
return _selectedIndex >> ActiveElementFlags.Size; return _selectedIndex;
} }
/** /**
@ -445,12 +467,13 @@ export function getSelectedIndex() {
* *
* Used with {@link property} instruction (and more in the future) to identify the index in the * Used with {@link property} instruction (and more in the future) to identify the index in the
* current `LView` to act on. * current `LView` to act on.
*
* (Note that if an "exit function" was set earlier (via `setElementExitFn()`) then that will be
* run if and when the provided `index` value is different from the current selected index value.)
*/ */
export function setSelectedIndex(index: number) { export function setSelectedIndex(index: number) {
_selectedIndex = index << ActiveElementFlags.Size; _selectedIndex = index;
// we have now jumped to another element
// therefore the state is stale
resetStylingState();
} }

View File

@ -5,14 +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 {SafeValue, unwrapSafeValue} from '../../sanitization/bypass'; import {SafeValue} from '../../sanitization/bypass';
import {StyleSanitizeFn, StyleSanitizeMode} from '../../sanitization/style_sanitizer'; import {StyleSanitizeFn, StyleSanitizeMode} from '../../sanitization/style_sanitizer';
import {ProceduralRenderer3, RElement, Renderer3, RendererStyleFlags3, isProceduralRenderer} from '../interfaces/renderer'; import {ProceduralRenderer3, RElement, Renderer3, RendererStyleFlags3, isProceduralRenderer} from '../interfaces/renderer';
import {NO_CHANGE} from '../tokens'; import {NO_CHANGE} from '../tokens';
import {ApplyStylingFn, LStylingData, StylingMapArray, StylingMapArrayIndex, StylingMapsSyncMode, SyncStylingMapsFn, TStylingConfig, TStylingContext, TStylingContextIndex, TStylingContextPropConfigFlags} from './interfaces'; import {ApplyStylingFn, LStylingData, StylingMapArray, StylingMapArrayIndex, StylingMapsSyncMode, SyncStylingMapsFn, TStylingContext, TStylingContextIndex, TStylingContextPropConfigFlags} from './interfaces';
import {getStylingState, resetStylingState} from './state'; import {BIT_MASK_START_VALUE, deleteStylingStateFromStorage, getStylingState, resetStylingState, storeStylingState} from './state';
import {DEFAULT_BINDING_INDEX, DEFAULT_BINDING_VALUE, DEFAULT_GUARD_MASK_VALUE, MAP_BASED_ENTRY_PROP_NAME, getBindingValue, getConfig, getDefaultValue, getGuardMask, getMapProp, getMapValue, getProp, getPropValuesStartPosition, getStylingMapArray, getTotalSources, getValue, getValuesCount, hasConfig, hasValueChanged, isContextLocked, isHostStylingActive, isSanitizationRequired, isStylingValueDefined, lockContext, patchConfig, setDefaultValue, setGuardMask, setMapAsDirty, setValue} from './util'; import {allowStylingFlush, getBindingValue, getGuardMask, getMapProp, getMapValue, getProp, getPropValuesStartPosition, getStylingMapArray, getValuesCount, hasValueChanged, isContextLocked, isSanitizationRequired, isStylingValueDefined, lockContext, setGuardMask, stateIsPersisted} from './util';
@ -35,6 +35,13 @@ import {DEFAULT_BINDING_INDEX, DEFAULT_BINDING_VALUE, DEFAULT_GUARD_MASK_VALUE,
* -------- * --------
*/ */
// The first bit value reflects a map-based binding value's bit.
// The reason why it's always activated for every entry in the map
// is so that if any map-binding values update then all other prop
// based bindings will pass the guard check automatically without
// any extra code or flags.
export const DEFAULT_GUARD_MASK_VALUE = 0b1;
/** /**
* The guard/update mask bit index location for map-based bindings. * The guard/update mask bit index location for map-based bindings.
* *
@ -42,6 +49,26 @@ import {DEFAULT_BINDING_INDEX, DEFAULT_BINDING_VALUE, DEFAULT_GUARD_MASK_VALUE,
*/ */
const STYLING_INDEX_FOR_MAP_BINDING = 0; const STYLING_INDEX_FOR_MAP_BINDING = 0;
/**
* Default fallback value for a styling binding.
*
* A value of `null` is used here which signals to the styling algorithm that
* the styling value is not present. This way if there are no other values
* detected then it will be removed once the style/class property is dirty and
* diffed within the styling algorithm present in `flushStyling`.
*/
const DEFAULT_BINDING_VALUE = null;
/**
* Default size count value for a new entry in a context.
*
* A value of `1` is used here because each entry in the context has a default
* property.
*/
const DEFAULT_SIZE_VALUE = 1;
let deferredBindingQueue: (TStylingContext | number | string | null | boolean)[] = [];
/** /**
* Visits a class-based binding and updates the new value (if changed). * Visits a class-based binding and updates the new value (if changed).
* *
@ -52,25 +79,23 @@ const STYLING_INDEX_FOR_MAP_BINDING = 0;
* state each time it's called (which then allows the `TStylingContext` * state each time it's called (which then allows the `TStylingContext`
* and the bit mask values to be in sync). * and the bit mask values to be in sync).
*/ */
export function updateClassViaContext( export function updateClassBinding(
context: TStylingContext, data: LStylingData, element: RElement, directiveIndex: number, context: TStylingContext, data: LStylingData, element: RElement, prop: string | null,
prop: string | null, bindingIndex: number, bindingIndex: number, value: boolean | string | null | undefined | StylingMapArray | NO_CHANGE,
value: boolean | string | null | undefined | StylingMapArray | NO_CHANGE, deferRegistration: boolean, forceUpdate: boolean): boolean {
forceUpdate?: boolean): boolean {
const isMapBased = !prop; const isMapBased = !prop;
const state = getStylingState(element, directiveIndex); const state = getStylingState(element, stateIsPersisted(context));
const countIndex = isMapBased ? STYLING_INDEX_FOR_MAP_BINDING : state.classesIndex++; const index = isMapBased ? STYLING_INDEX_FOR_MAP_BINDING : state.classesIndex++;
if (value !== NO_CHANGE) { if (value !== NO_CHANGE) {
const updated = updateBindingData( const updated = updateBindingData(
context, data, countIndex, state.sourceIndex, prop, bindingIndex, value, forceUpdate, context, data, index, prop, bindingIndex, value, deferRegistration, forceUpdate, false);
false);
if (updated || forceUpdate) { if (updated || forceUpdate) {
// We flip the bit in the bitMask to reflect that the binding // We flip the bit in the bitMask to reflect that the binding
// at the `index` slot has changed. This identifies to the flushing // at the `index` slot has changed. This identifies to the flushing
// phase that the bindings for this particular CSS class need to be // phase that the bindings for this particular CSS class need to be
// applied again because on or more of the bindings for the CSS // applied again because on or more of the bindings for the CSS
// class have changed. // class have changed.
state.classesBitMask |= 1 << countIndex; state.classesBitMask |= 1 << index;
return true; return true;
} }
} }
@ -87,20 +112,19 @@ export function updateClassViaContext(
* state each time it's called (which then allows the `TStylingContext` * state each time it's called (which then allows the `TStylingContext`
* and the bit mask values to be in sync). * and the bit mask values to be in sync).
*/ */
export function updateStyleViaContext( export function updateStyleBinding(
context: TStylingContext, data: LStylingData, element: RElement, directiveIndex: number, context: TStylingContext, data: LStylingData, element: RElement, prop: string | null,
prop: string | null, bindingIndex: number, bindingIndex: number,
value: string | number | SafeValue | null | undefined | StylingMapArray | NO_CHANGE, value: string | number | SafeValue | null | undefined | StylingMapArray | NO_CHANGE,
sanitizer: StyleSanitizeFn | null, forceUpdate?: boolean): boolean { sanitizer: StyleSanitizeFn | null, deferRegistration: boolean, forceUpdate: boolean): boolean {
const isMapBased = !prop; const isMapBased = !prop;
const state = getStylingState(element, directiveIndex); const state = getStylingState(element, stateIsPersisted(context));
const countIndex = isMapBased ? STYLING_INDEX_FOR_MAP_BINDING : state.stylesIndex++; const index = isMapBased ? STYLING_INDEX_FOR_MAP_BINDING : state.stylesIndex++;
if (value !== NO_CHANGE) { if (value !== NO_CHANGE) {
const sanitizationRequired = isMapBased ? const sanitizationRequired = isMapBased ||
true :
(sanitizer ? sanitizer(prop !, null, StyleSanitizeMode.ValidateProperty) : false); (sanitizer ? sanitizer(prop !, null, StyleSanitizeMode.ValidateProperty) : false);
const updated = updateBindingData( const updated = updateBindingData(
context, data, countIndex, state.sourceIndex, prop, bindingIndex, value, forceUpdate, context, data, index, prop, bindingIndex, value, deferRegistration, forceUpdate,
sanitizationRequired); sanitizationRequired);
if (updated || forceUpdate) { if (updated || forceUpdate) {
// We flip the bit in the bitMask to reflect that the binding // We flip the bit in the bitMask to reflect that the binding
@ -108,7 +132,7 @@ export function updateStyleViaContext(
// phase that the bindings for this particular property need to be // phase that the bindings for this particular property need to be
// applied again because on or more of the bindings for the CSS // applied again because on or more of the bindings for the CSS
// property have changed. // property have changed.
state.stylesBitMask |= 1 << countIndex; state.stylesBitMask |= 1 << index;
return true; return true;
} }
} }
@ -120,6 +144,8 @@ export function updateStyleViaContext(
* *
* This function is designed to be called from `updateStyleBinding` and `updateClassBinding`. * This function is designed to be called from `updateStyleBinding` and `updateClassBinding`.
* If called during the first update pass, the binding will be registered in the context. * If called during the first update pass, the binding will be registered in the context.
* If the binding does get registered and the `deferRegistration` flag is true then the
* binding data will be queued up until the context is later flushed in `applyStyling`.
* *
* This function will also update binding slot in the provided `LStylingData` with the * This function will also update binding slot in the provided `LStylingData` with the
* new binding entry (if it has changed). * new binding entry (if it has changed).
@ -127,92 +153,74 @@ export function updateStyleViaContext(
* @returns whether or not the binding value was updated in the `LStylingData`. * @returns whether or not the binding value was updated in the `LStylingData`.
*/ */
function updateBindingData( function updateBindingData(
context: TStylingContext, data: LStylingData, counterIndex: number, sourceIndex: number, context: TStylingContext, data: LStylingData, counterIndex: number, prop: string | null,
prop: string | null, bindingIndex: number, bindingIndex: number,
value: string | SafeValue | number | boolean | null | undefined | StylingMapArray, value: string | SafeValue | number | boolean | null | undefined | StylingMapArray | NO_CHANGE,
forceUpdate?: boolean, sanitizationRequired?: boolean): boolean { deferRegistration: boolean, forceUpdate: boolean, sanitizationRequired: boolean): boolean {
const hostBindingsMode = isHostStylingActive(sourceIndex); if (!isContextLocked(context)) {
if (!isContextLocked(context, hostBindingsMode)) { if (deferRegistration) {
deferBindingRegistration(context, counterIndex, prop, bindingIndex, sanitizationRequired);
} else {
deferredBindingQueue.length && flushDeferredBindings();
// this will only happen during the first update pass of the // this will only happen during the first update pass of the
// context. The reason why we can't use `tNode.firstTemplatePass` // context. The reason why we can't use `tNode.firstTemplatePass`
// here is because its not guaranteed to be true when the first // here is because its not guaranteed to be true when the first
// update pass is executed (remember that all styling instructions // update pass is executed (remember that all styling instructions
// are run in the update phase, and, as a result, are no more // are run in the update phase, and, as a result, are no more
// styling instructions that are run in the creation phase). // styling instructions that are run in the creation phase).
registerBinding(context, counterIndex, sourceIndex, prop, bindingIndex, sanitizationRequired); registerBinding(context, counterIndex, prop, bindingIndex, sanitizationRequired);
patchConfig( }
context,
hostBindingsMode ? TStylingConfig.HasHostBindings : TStylingConfig.HasTemplateBindings);
patchConfig(context, prop ? TStylingConfig.HasPropBindings : TStylingConfig.HasMapBindings);
} }
const changed = forceUpdate || hasValueChanged(data[bindingIndex], value); const changed = forceUpdate || hasValueChanged(data[bindingIndex], value);
if (changed) { if (changed) {
setValue(data, bindingIndex, value); data[bindingIndex] = value;
const doSetValuesAsStale = (getConfig(context) & TStylingConfig.HasHostBindings) &&
!hostBindingsMode && (prop ? !value : true);
if (doSetValuesAsStale) {
renderHostBindingsAsStale(context, data, prop, !prop);
}
} }
return changed; return changed;
} }
/** /**
* Iterates over all host-binding values for the given `prop` value in the context and sets their * Schedules a binding registration to be run at a later point.
* corresponding binding values to `null`.
* *
* Whenever a template binding changes its value to `null`, all host-binding values should be * The reasoning for this feature is to ensure that styling
* re-applied * bindings are registered in the correct order for when
* to the element when the host bindings are evaluated. This may not always happen in the event * directives/components have a super/sub class inheritance
* that none of the bindings changed within the host bindings code. For this reason this function * chains. Each directive's styling bindings must be
* is expected to be called each time a template binding becomes falsy or when a map-based template * registered into the context in reverse order. Therefore all
* binding changes. * bindings will be buffered in reverse order and then applied
* after the inheritance chain exits.
*/ */
function renderHostBindingsAsStale( function deferBindingRegistration(
context: TStylingContext, data: LStylingData, prop: string | null, isMapBased: boolean): void { context: TStylingContext, counterIndex: number, prop: string | null, bindingIndex: number,
const valuesCount = getValuesCount(context); sanitizationRequired: boolean) {
deferredBindingQueue.unshift(context, counterIndex, prop, bindingIndex, sanitizationRequired);
if (hasConfig(context, TStylingConfig.HasPropBindings)) {
const itemsPerRow = TStylingContextIndex.BindingsStartOffset + valuesCount;
let i = TStylingContextIndex.ValuesStartPosition;
while (i < context.length) {
if (getProp(context, i) === prop) {
break;
}
i += itemsPerRow;
} }
const bindingsStart = i + TStylingContextIndex.BindingsStartOffset; /**
const valuesStart = bindingsStart + 1; // the first column is template bindings * Flushes the collection of deferred bindings and causes each entry
const valuesEnd = bindingsStart + valuesCount - 1; * to be registered into the context.
*/
for (let i = valuesStart; i < valuesEnd; i++) { function flushDeferredBindings() {
const bindingIndex = context[i] as number; let i = 0;
if (bindingIndex !== 0) { while (i < deferredBindingQueue.length) {
setValue(data, bindingIndex, null); const context = deferredBindingQueue[i++] as TStylingContext;
} const count = deferredBindingQueue[i++] as number;
} const prop = deferredBindingQueue[i++] as string;
} const bindingIndex = deferredBindingQueue[i++] as number | null;
const sanitizationRequired = deferredBindingQueue[i++] as boolean;
if (hasConfig(context, TStylingConfig.HasMapBindings)) { registerBinding(context, count, prop, bindingIndex, sanitizationRequired);
const bindingsStart =
TStylingContextIndex.ValuesStartPosition + TStylingContextIndex.BindingsStartOffset;
const valuesStart = bindingsStart + 1; // the first column is template bindings
const valuesEnd = bindingsStart + valuesCount - 1;
for (let i = valuesStart; i < valuesEnd; i++) {
const stylingMap = getValue<StylingMapArray>(data, context[i] as number);
if (stylingMap) {
setMapAsDirty(stylingMap);
}
}
} }
deferredBindingQueue.length = 0;
} }
/** /**
* Registers the provided binding (prop + bindingIndex) into the context. * Registers the provided binding (prop + bindingIndex) into the context.
* *
* This function is shared between bindings that are assigned immediately
* (via `updateBindingData`) and at a deferred stage. When called, it will
* figure out exactly where to place the binding data in the context.
*
* It is needed because it will either update or insert a styling property * It is needed because it will either update or insert a styling property
* into the context at the correct spot. * into the context at the correct spot.
* *
@ -238,79 +246,61 @@ function renderHostBindingsAsStale(
* value. * value.
* *
* Note that this function is also used for map-based styling bindings. They are treated * Note that this function is also used for map-based styling bindings. They are treated
* much the same as prop-based bindings, but, their property name value is set as `[MAP]`. * much the same as prop-based bindings, but, because they do not have a property value
* (since it's a map), all map-based entries are stored in an already populated area of
* the context at the top (which is reserved for map-based entries).
*/ */
export function registerBinding( export function registerBinding(
context: TStylingContext, countId: number, sourceIndex: number, prop: string | null, context: TStylingContext, countId: number, prop: string | null,
bindingValue: number | null | string | boolean, sanitizationRequired?: boolean): void { bindingValue: number | null | string | boolean, sanitizationRequired?: boolean): boolean {
let registered = false;
if (prop) {
// prop-based bindings (e.g `<div [style.width]="w" [class.foo]="f">`)
let found = false; let found = false;
prop = prop || MAP_BASED_ENTRY_PROP_NAME; let i = getPropValuesStartPosition(context);
const total = getTotalSources(context);
// if a new source is detected then a new column needs to be allocated into
// the styling context. The column is basically a new allocation of binding
// sources that will be available to each property.
if (sourceIndex >= total) {
addNewSourceColumn(context);
}
const isBindingIndexValue = typeof bindingValue === 'number';
const entriesPerRow = TStylingContextIndex.BindingsStartOffset + getValuesCount(context);
let i = TStylingContextIndex.ValuesStartPosition;
// all style/class bindings are sorted by property name
while (i < context.length) { while (i < context.length) {
const valuesCount = getValuesCount(context, i);
const p = getProp(context, i); const p = getProp(context, i);
if (prop <= p) { found = prop <= p;
if (found) {
// all style/class bindings are sorted by property name
if (prop < p) { if (prop < p) {
allocateNewContextEntry(context, i, prop, sanitizationRequired); allocateNewContextEntry(context, i, prop, sanitizationRequired);
} else if (isBindingIndexValue) {
patchConfig(context, TStylingConfig.HasCollisions);
} }
addBindingIntoContext(context, i, bindingValue, countId, sourceIndex); addBindingIntoContext(context, false, i, bindingValue, countId);
found = true;
break; break;
} }
i += entriesPerRow; i += TStylingContextIndex.BindingsStartOffset + valuesCount;
} }
if (!found) { if (!found) {
allocateNewContextEntry(context, context.length, prop, sanitizationRequired); allocateNewContextEntry(context, context.length, prop, sanitizationRequired);
addBindingIntoContext(context, i, bindingValue, countId, sourceIndex); addBindingIntoContext(context, false, i, bindingValue, countId);
registered = true;
} }
} else {
// map-based bindings (e.g `<div [style]="s" [class]="{className:true}">`)
// there is no need to allocate the map-based binding region into the context
// since it is already there when the context is first created.
addBindingIntoContext(
context, true, TStylingContextIndex.MapBindingsPosition, bindingValue, countId);
registered = true;
}
return registered;
} }
/**
* Inserts a new row into the provided `TStylingContext` and assigns the provided `prop` value as
* the property entry.
*/
function allocateNewContextEntry( function allocateNewContextEntry(
context: TStylingContext, index: number, prop: string, sanitizationRequired?: boolean): void { context: TStylingContext, index: number, prop: string, sanitizationRequired?: boolean) {
// 1,2: splice index locations
// 3: each entry gets a config value (guard mask + flags)
// 4. each entry gets a size value (which is always one because there is always a default binding
// value)
// 5. the property that is getting allocated into the context
// 6. the default binding value (usually `null`)
const config = sanitizationRequired ? TStylingContextPropConfigFlags.SanitizationRequired : const config = sanitizationRequired ? TStylingContextPropConfigFlags.SanitizationRequired :
TStylingContextPropConfigFlags.Default; TStylingContextPropConfigFlags.Default;
context.splice( context.splice(index, 0, config, DEFAULT_SIZE_VALUE, prop, DEFAULT_BINDING_VALUE);
index, 0, setGuardMask(context, index, DEFAULT_GUARD_MASK_VALUE);
config, // 1) config value
DEFAULT_GUARD_MASK_VALUE, // 2) template bit mask
DEFAULT_GUARD_MASK_VALUE, // 3) host bindings bit mask
prop, // 4) prop value (e.g. `width`, `myClass`, etc...)
);
index += 4; // the 4 values above
// 5...) default binding index for the template value
// depending on how many sources already exist in the context,
// multiple default index entries may need to be inserted for
// the new value in the context.
const totalBindingsPerEntry = getTotalSources(context);
for (let i = 0; i < totalBindingsPerEntry; i++) {
context.splice(index, 0, DEFAULT_BINDING_INDEX);
index++;
}
// 6) default binding value for the new entry
context.splice(index, 0, DEFAULT_BINDING_VALUE);
} }
/** /**
@ -326,47 +316,50 @@ function allocateNewContextEntry(
* *
* - Otherwise the binding value will update the default value for the property * - Otherwise the binding value will update the default value for the property
* and this will only happen if the default value is `null`. * and this will only happen if the default value is `null`.
*
* Note that this function also handles map-based bindings and will insert them
* at the top of the context.
*/ */
function addBindingIntoContext( function addBindingIntoContext(
context: TStylingContext, index: number, bindingValue: number | string | boolean | null, context: TStylingContext, isMapBased: boolean, index: number,
bitIndex: number, sourceIndex: number) { bindingValue: number | string | boolean | null, countId: number) {
const valuesCount = getValuesCount(context, index);
const firstValueIndex = index + TStylingContextIndex.BindingsStartOffset;
let lastValueIndex = firstValueIndex + valuesCount;
if (!isMapBased) {
// prop-based values all have default values, but map-based entries do not.
// we want to access the index for the default value in this case and not just
// the bindings...
lastValueIndex--;
}
if (typeof bindingValue === 'number') { if (typeof bindingValue === 'number') {
const hostBindingsMode = isHostStylingActive(sourceIndex); // the loop here will check to see if the binding already exists
const cellIndex = index + TStylingContextIndex.BindingsStartOffset + sourceIndex; // for the property in the context. Why? The reason for this is
context[cellIndex] = bindingValue; // because the styling context is not "locked" until the first
const updatedBitMask = getGuardMask(context, index, hostBindingsMode) | (1 << bitIndex); // flush has occurred. This means that if a repeated element
setGuardMask(context, index, updatedBitMask, hostBindingsMode); // registers its styling bindings then it will register each
} else if (bindingValue !== null && getDefaultValue(context, index) === null) { // binding more than once (since its duplicated). This check
setDefaultValue(context, index, bindingValue); // will prevent that from happening. Note that this only happens
} // when a binding is first encountered and not each time it is
// updated.
for (let i = firstValueIndex; i <= lastValueIndex; i++) {
const indexAtPosition = context[i];
if (indexAtPosition === bindingValue) return;
} }
/** context.splice(lastValueIndex, 0, bindingValue);
* Registers a new column into the provided `TStylingContext`. (context[index + TStylingContextIndex.ValuesCountOffset] as number)++;
*
* If and when a new source is detected then a new column needs to
* be allocated into the styling context. The column is basically
* a new allocation of binding sources that will be available to each
* property.
*
* Each column that exists in the styling context resembles a styling
* source. A styling source an either be the template or one or more
* components or directives all containing styling host bindings.
*/
function addNewSourceColumn(context: TStylingContext): void {
// we use -1 here because we want to insert right before the last value (the default value)
const insertOffset = TStylingContextIndex.BindingsStartOffset + getValuesCount(context) - 1;
let index = TStylingContextIndex.ValuesStartPosition; // now that a new binding index has been added to the property
while (index < context.length) { // the guard mask bit value (at the `countId` position) needs
index += insertOffset; // to be included into the existing mask value.
context.splice(index++, 0, DEFAULT_BINDING_INDEX); const guardMask = getGuardMask(context, index) | (1 << countId);
setGuardMask(context, index, guardMask);
// the value was inserted just before the default value, but the } else if (bindingValue !== null && context[lastValueIndex] == null) {
// next entry in the context starts just after it. Therefore++. context[lastValueIndex] = bindingValue;
index++;
} }
context[TStylingContextIndex.TotalSourcesPosition]++;
} }
/** /**
@ -374,22 +367,24 @@ function addNewSourceColumn(context: TStylingContext): void {
* *
* This function will attempt to flush styling via the provided `classesContext` * This function will attempt to flush styling via the provided `classesContext`
* and `stylesContext` context values. This function is designed to be run from * and `stylesContext` context values. This function is designed to be run from
* the internal `stylingApply` function (which is scheduled to run at the very * the `stylingApply()` instruction (which is run at the very end of styling
* end of change detection for an element if one or more style/class bindings * change detection) and will rely on any state values that are set from when
* were processed) and will rely on any state values that are set from when * any styling bindings update.
* any of the styling bindings executed.
* *
* This function is designed to be called twice: one when change detection has * This function may be called multiple times on the same element because it can
* processed an element within the template bindings (i.e. just as `advance()` * be called from the template code as well as from host bindings. In order for
* is called) and when host bindings have been processed. In both cases the * styling to be successfully flushed to the element (which will only happen once
* styles and classes in both contexts will be applied to the element, but the * despite this being called multiple times), the following criteria must be met:
* algorithm will selectively decide which bindings to run depending on the
* columns in the context. The provided `directiveIndex` value will help the
* algorithm determine which bindings to apply: either the template bindings or
* the host bindings (see `applyStylingToElement` for more information).
* *
* Note that once this function is called all temporary styling state data * - `flushStyling` is called from the very last directive that has styling for
* (i.e. the `bitMask` and `counter` values for styles and classes will be cleared). * the element (see `allowStylingFlush()`).
* - one or more bindings for classes or styles has updated (this is checked by
* examining the classes or styles bit mask).
*
* If the style and class values are successfully applied to the element then
* the temporary state values for the element will be cleared. Otherwise, if
* this did not occur then the styling state is persisted (see `state.ts` for
* more information on how this works).
*/ */
export function flushStyling( export function flushStyling(
renderer: Renderer3 | ProceduralRenderer3 | null, data: LStylingData, renderer: Renderer3 | ProceduralRenderer3 | null, data: LStylingData,
@ -397,28 +392,53 @@ export function flushStyling(
element: RElement, directiveIndex: number, styleSanitizer: StyleSanitizeFn | null): void { element: RElement, directiveIndex: number, styleSanitizer: StyleSanitizeFn | null): void {
ngDevMode && ngDevMode.flushStyling++; ngDevMode && ngDevMode.flushStyling++;
const state = getStylingState(element, directiveIndex); const persistState = classesContext ? stateIsPersisted(classesContext) :
const hostBindingsMode = isHostStylingActive(state.sourceIndex); (stylesContext ? stateIsPersisted(stylesContext) : false);
const allowFlushClasses = allowStylingFlush(classesContext, directiveIndex);
const allowFlushStyles = allowStylingFlush(stylesContext, directiveIndex);
if (stylesContext) { // deferred bindings are bindings which are scheduled to register with
if (!isContextLocked(stylesContext, hostBindingsMode)) { // the context at a later point. These bindings can only registered when
lockAndFinalizeContext(stylesContext, hostBindingsMode); // the context will be 100% flushed to the element.
} if (deferredBindingQueue.length && (allowFlushClasses || allowFlushStyles)) {
applyStylingViaContext( flushDeferredBindings();
stylesContext, renderer, element, data, state.stylesBitMask, setStyle, styleSanitizer,
hostBindingsMode);
} }
if (classesContext) { const state = getStylingState(element, persistState);
if (!isContextLocked(classesContext, hostBindingsMode)) { const classesFlushed = maybeApplyStyling(
lockAndFinalizeContext(classesContext, hostBindingsMode); renderer, element, data, classesContext, allowFlushClasses, state.classesBitMask, setClass,
} null);
applyStylingViaContext( const stylesFlushed = maybeApplyStyling(
classesContext, renderer, element, data, state.classesBitMask, setClass, null, renderer, element, data, stylesContext, allowFlushStyles, state.stylesBitMask, setStyle,
hostBindingsMode); styleSanitizer);
}
if (classesFlushed && stylesFlushed) {
resetStylingState(); resetStylingState();
if (persistState) {
deleteStylingStateFromStorage(element);
}
} else if (persistState) {
storeStylingState(element, state);
}
}
function maybeApplyStyling(
renderer: Renderer3 | ProceduralRenderer3 | null, element: RElement, data: LStylingData,
context: TStylingContext | null, allowFlush: boolean, bitMask: number,
styleSetter: ApplyStylingFn, styleSanitizer: any | null): boolean {
if (allowFlush && context) {
lockAndFinalizeContext(context);
if (contextHasUpdates(context, bitMask)) {
ngDevMode && (styleSanitizer ? ngDevMode.stylesApplied++ : ngDevMode.classesApplied++);
applyStyling(context !, renderer, element, data, bitMask, styleSetter, styleSanitizer);
return true;
}
}
return allowFlush;
}
function contextHasUpdates(context: TStylingContext | null, bitMask: number) {
return context && bitMask > BIT_MASK_START_VALUE;
} }
/** /**
@ -442,50 +462,14 @@ export function flushStyling(
* be updated each time a host binding applies its static styling values (via `elementHostAttrs`) * be updated each time a host binding applies its static styling values (via `elementHostAttrs`)
* so these values are only read at this point because this is the very last point before the * so these values are only read at this point because this is the very last point before the
* first style/class values are flushed to the element. * first style/class values are flushed to the element.
*
* Note that the `TStylingContext` styling context contains two locks: one for template bindings
* and another for host bindings. Either one of these locks will be set when styling is applied
* during the template binding flush and/or during the host bindings flush.
*/ */
function lockAndFinalizeContext(context: TStylingContext, hostBindingsMode: boolean): void { function lockAndFinalizeContext(context: TStylingContext): void {
const initialValues = getStylingMapArray(context) !; if (!isContextLocked(context)) {
const initialValues = getStylingMapArray(context);
if (initialValues) {
updateInitialStylingOnContext(context, initialValues); updateInitialStylingOnContext(context, initialValues);
lockContext(context, hostBindingsMode);
} }
lockContext(context);
/**
* Registers all initial styling entries into the provided context.
*
* This function will iterate over all entries in the provided `initialStyling` ar}ray and register
* them as default (initial) values in the provided context. Initial styling values in a context are
* the default values that are to be applied unless overwritten by a binding.
*
* The reason why this function exists and isn't a part of the context construction is because
* host binding is evaluated at a later stage after the element is created. This means that
* if a directive or component contains any initial styling code (i.e. `<div class="foo">`)
* then that initial styling data can only be applied once the styling for that element
* is first applied (at the end of the update phase). Once that happens then the context will
* update itself with the complete initial styling for the element.
*/
function updateInitialStylingOnContext(
context: TStylingContext, initialStyling: StylingMapArray): void {
// `-1` is used here because all initial styling data is not a apart
// of a binding (since it's static)
const COUNT_ID_FOR_STYLING = -1;
let hasInitialStyling = false;
for (let i = StylingMapArrayIndex.ValuesStartPosition; i < initialStyling.length;
i += StylingMapArrayIndex.TupleSize) {
const value = getMapValue(initialStyling, i);
if (value) {
const prop = getMapProp(initialStyling, i);
registerBinding(context, COUNT_ID_FOR_STYLING, 0, prop, value, false);
hasInitialStyling = true;
}
}
if (hasInitialStyling) {
patchConfig(context, TStylingConfig.HasInitialStyling);
} }
} }
@ -513,74 +497,60 @@ function updateInitialStylingOnContext(
* algorithm works for map-based styling bindings. * algorithm works for map-based styling bindings.
* *
* Note that this function is not designed to be called in isolation (use * Note that this function is not designed to be called in isolation (use
* the `flushStyling` function so that it can call this function for both * `applyClasses` and `applyStyles` to actually apply styling values).
* the styles and classes contexts).
*/ */
export function applyStylingViaContext( export function applyStyling(
context: TStylingContext, renderer: Renderer3 | ProceduralRenderer3 | null, element: RElement, context: TStylingContext, renderer: Renderer3 | ProceduralRenderer3 | null, element: RElement,
bindingData: LStylingData, bitMaskValue: number | boolean, applyStylingFn: ApplyStylingFn, bindingData: LStylingData, bitMaskValue: number | boolean, applyStylingFn: ApplyStylingFn,
sanitizer: StyleSanitizeFn | null, hostBindingsMode: boolean): void { sanitizer: StyleSanitizeFn | null) {
const bitMask = normalizeBitMaskValue(bitMaskValue); const bitMask = normalizeBitMaskValue(bitMaskValue);
const stylingMapsSyncFn = getStylingMapsSyncFn();
let stylingMapsSyncFn: SyncStylingMapsFn|null = null; const mapsGuardMask = getGuardMask(context, TStylingContextIndex.MapBindingsPosition);
let applyAllValues = false; const applyAllValues = (bitMask & mapsGuardMask) > 0;
if (hasConfig(context, TStylingConfig.HasMapBindings)) { const mapsMode =
stylingMapsSyncFn = getStylingMapsSyncFn();
const mapsGuardMask =
getGuardMask(context, TStylingContextIndex.ValuesStartPosition, hostBindingsMode);
applyAllValues = (bitMask & mapsGuardMask) !== 0;
}
const valuesCount = getValuesCount(context);
let totalBindingsToVisit = 1;
let mapsMode =
applyAllValues ? StylingMapsSyncMode.ApplyAllValues : StylingMapsSyncMode.TraverseValues; applyAllValues ? StylingMapsSyncMode.ApplyAllValues : StylingMapsSyncMode.TraverseValues;
if (hostBindingsMode) {
mapsMode |= StylingMapsSyncMode.RecurseInnerMaps;
totalBindingsToVisit = valuesCount - 1;
}
let i = getPropValuesStartPosition(context); let i = getPropValuesStartPosition(context);
while (i < context.length) { while (i < context.length) {
const guardMask = getGuardMask(context, i, hostBindingsMode); const valuesCount = getValuesCount(context, i);
const guardMask = getGuardMask(context, i);
if (bitMask & guardMask) { if (bitMask & guardMask) {
let valueApplied = false; let valueApplied = false;
const prop = getProp(context, i); const prop = getProp(context, i);
const defaultValue = getDefaultValue(context, i); const valuesCountUpToDefault = valuesCount - 1;
const defaultValue = getBindingValue(context, i, valuesCountUpToDefault) as string | null;
// Part 1: Visit the `[styling.prop]` value // case 1: apply prop-based values
for (let j = 0; j < totalBindingsToVisit; j++) { // try to apply the binding values and see if a non-null
// value gets set for the styling binding
for (let j = 0; j < valuesCountUpToDefault; j++) {
const bindingIndex = getBindingValue(context, i, j) as number; const bindingIndex = getBindingValue(context, i, j) as number;
if (!valueApplied && bindingIndex !== 0) { const value = bindingData[bindingIndex];
const value = getValue(bindingData, bindingIndex);
if (isStylingValueDefined(value)) { if (isStylingValueDefined(value)) {
const checkValueOnly = hostBindingsMode && j === 0;
if (!checkValueOnly) {
const finalValue = sanitizer && isSanitizationRequired(context, i) ? const finalValue = sanitizer && isSanitizationRequired(context, i) ?
sanitizer(prop, value, StyleSanitizeMode.SanitizeOnly) : sanitizer(prop, value, StyleSanitizeMode.SanitizeOnly) :
unwrapSafeValue(value); value;
applyStylingFn(renderer, element, prop, finalValue, bindingIndex); applyStylingFn(renderer, element, prop, finalValue, bindingIndex);
}
valueApplied = true; valueApplied = true;
break;
} }
} }
// Part 2: Visit the `[style]` or `[class]` map-based value // case 2: apply map-based values
// traverse through each map-based styling binding and update all values up to
// the provided `prop` value. If the property was not applied in the loop above
// then it will be attempted to be applied in the maps sync code below.
if (stylingMapsSyncFn) { if (stylingMapsSyncFn) {
// determine whether or not to apply the target property or to skip it // determine whether or not to apply the target property or to skip it
let mode = mapsMode | (valueApplied ? StylingMapsSyncMode.SkipTargetProp : const mode = mapsMode | (valueApplied ? StylingMapsSyncMode.SkipTargetProp :
StylingMapsSyncMode.ApplyTargetProp); StylingMapsSyncMode.ApplyTargetProp);
if (hostBindingsMode && j === 0) {
mode |= StylingMapsSyncMode.CheckValuesOnly;
}
const valueAppliedWithinMap = stylingMapsSyncFn( const valueAppliedWithinMap = stylingMapsSyncFn(
context, renderer, element, bindingData, j, applyStylingFn, sanitizer, mode, prop, context, renderer, element, bindingData, applyStylingFn, sanitizer, mode, prop,
defaultValue); defaultValue);
valueApplied = valueApplied || valueAppliedWithinMap; valueApplied = valueApplied || valueAppliedWithinMap;
} }
}
// Part 3: apply the default value (e.g. `<div style="width:200">` => `200px` gets applied) // case 3: apply the default value
// if the value has not yet been applied then a truthy value does not exist in the // if the value has not yet been applied then a truthy value does not exist in the
// prop-based or map-based bindings code. If and when this happens, just apply the // prop-based or map-based bindings code. If and when this happens, just apply the
// default value (even if the default value is `null`). // default value (even if the default value is `null`).
@ -596,90 +566,8 @@ export function applyStylingViaContext(
// values. For this reason, one more call to the sync function // values. For this reason, one more call to the sync function
// needs to be issued at the end. // needs to be issued at the end.
if (stylingMapsSyncFn) { if (stylingMapsSyncFn) {
if (hostBindingsMode) { stylingMapsSyncFn(context, renderer, element, bindingData, applyStylingFn, sanitizer, mapsMode);
mapsMode |= StylingMapsSyncMode.CheckValuesOnly;
} }
stylingMapsSyncFn(
context, renderer, element, bindingData, 0, applyStylingFn, sanitizer, mapsMode);
}
}
/**
* Applies the provided styling map to the element directly (without context resolution).
*
* This function is designed to be run from the styling instructions and will be called
* automatically. This function is intended to be used for performance reasons in the
* event that there is no need to apply styling via context resolution.
*
* See `allowDirectStylingApply`.
*
* @returns whether or not the styling map was applied to the element.
*/
export function applyStylingMapDirectly(
renderer: any, context: TStylingContext, element: RElement, data: LStylingData,
bindingIndex: number, map: StylingMapArray, applyFn: ApplyStylingFn,
sanitizer?: StyleSanitizeFn | null, forceUpdate?: boolean): boolean {
if (forceUpdate || hasValueChanged(data[bindingIndex], map)) {
setValue(data, bindingIndex, map);
for (let i = StylingMapArrayIndex.ValuesStartPosition; i < map.length;
i += StylingMapArrayIndex.TupleSize) {
const prop = getMapProp(map, i);
const value = getMapValue(map, i);
applyStylingValue(renderer, context, element, prop, value, applyFn, bindingIndex, sanitizer);
}
return true;
}
return false;
}
/**
* Applies the provided styling prop/value to the element directly (without context resolution).
*
* This function is designed to be run from the styling instructions and will be called
* automatically. This function is intended to be used for performance reasons in the
* event that there is no need to apply styling via context resolution.
*
* See `allowDirectStylingApply`.
*
* @returns whether or not the prop/value styling was applied to the element.
*/
export function applyStylingValueDirectly(
renderer: any, context: TStylingContext, element: RElement, data: LStylingData,
bindingIndex: number, prop: string, value: any, applyFn: ApplyStylingFn,
sanitizer?: StyleSanitizeFn | null): boolean {
if (hasValueChanged(data[bindingIndex], value)) {
setValue(data, bindingIndex, value);
applyStylingValue(renderer, context, element, prop, value, applyFn, bindingIndex, sanitizer);
return true;
}
return false;
}
function applyStylingValue(
renderer: any, context: TStylingContext, element: RElement, prop: string, value: any,
applyFn: ApplyStylingFn, bindingIndex: number, sanitizer?: StyleSanitizeFn | null) {
let valueToApply: string|null = unwrapSafeValue(value);
if (isStylingValueDefined(valueToApply)) {
valueToApply =
sanitizer ? sanitizer(prop, value, StyleSanitizeMode.SanitizeOnly) : valueToApply;
} else if (hasConfig(context, TStylingConfig.HasInitialStyling)) {
const initialStyles = getStylingMapArray(context);
if (initialStyles) {
valueToApply = findInitialStylingValue(initialStyles, prop);
}
}
applyFn(renderer, element, prop, valueToApply, bindingIndex);
}
function findInitialStylingValue(map: StylingMapArray, prop: string): string|null {
for (let i = StylingMapArrayIndex.ValuesStartPosition; i < map.length;
i += StylingMapArrayIndex.TupleSize) {
const p = getMapProp(map, i);
if (p >= prop) {
return p === prop ? getMapValue(map, i) : null;
}
}
return null;
} }
function normalizeBitMaskValue(value: number | boolean): number { function normalizeBitMaskValue(value: number | boolean): number {
@ -705,7 +593,7 @@ export function setStylingMapsSyncFn(fn: SyncStylingMapsFn) {
/** /**
* Assigns a style value to a style property for the given element. * Assigns a style value to a style property for the given element.
*/ */
export const setStyle: ApplyStylingFn = const setStyle: ApplyStylingFn =
(renderer: Renderer3 | null, native: RElement, prop: string, value: string | null) => { (renderer: Renderer3 | null, native: RElement, prop: string, value: string | null) => {
// the reason why this may be `null` is either because // the reason why this may be `null` is either because
// it's a container element or it's a part of a test // it's a container element or it's a part of a test
@ -732,7 +620,7 @@ export const setStyle: ApplyStylingFn =
/** /**
* Adds/removes the provided className value to the provided element. * Adds/removes the provided className value to the provided element.
*/ */
export const setClass: ApplyStylingFn = const setClass: ApplyStylingFn =
(renderer: Renderer3 | null, native: RElement, className: string, value: any) => { (renderer: Renderer3 | null, native: RElement, className: string, value: any) => {
if (className !== '') { if (className !== '') {
// the reason why this may be `null` is either because // the reason why this may be `null` is either because
@ -778,3 +666,33 @@ export function renderStylingMap(
} }
} }
} }
/**
* Registers all initial styling entries into the provided context.
*
* This function will iterate over all entries in the provided `initialStyling` ar}ray and register
* them as default (initial) values in the provided context. Initial styling values in a context are
* the default values that are to be applied unless overwritten by a binding.
*
* The reason why this function exists and isn't a part of the context construction is because
* host binding is evaluated at a later stage after the element is created. This means that
* if a directive or component contains any initial styling code (i.e. `<div class="foo">`)
* then that initial styling data can only be applied once the styling for that element
* is first applied (at the end of the update phase). Once that happens then the context will
* update itself with the complete initial styling for the element.
*/
function updateInitialStylingOnContext(
context: TStylingContext, initialStyling: StylingMapArray): void {
// `-1` is used here because all initial styling data is not a spart
// of a binding (since it's static)
const INITIAL_STYLING_COUNT_ID = -1;
for (let i = StylingMapArrayIndex.ValuesStartPosition; i < initialStyling.length;
i += StylingMapArrayIndex.TupleSize) {
const value = getMapValue(initialStyling, i);
if (value) {
const prop = getMapProp(initialStyling, i);
registerBinding(context, INITIAL_STYLING_COUNT_ID, prop, value, false);
}
}
}

View File

@ -10,17 +10,17 @@ import {StyleSanitizeFn} from '../../sanitization/style_sanitizer';
import {setInputsForProperty} from '../instructions/shared'; import {setInputsForProperty} from '../instructions/shared';
import {AttributeMarker, TAttributes, TNode, TNodeType} from '../interfaces/node'; import {AttributeMarker, TAttributes, TNode, TNodeType} from '../interfaces/node';
import {RElement} from '../interfaces/renderer'; import {RElement} from '../interfaces/renderer';
import {BINDING_INDEX, LView, RENDERER} from '../interfaces/view'; import {BINDING_INDEX, LView, RENDERER, TVIEW} from '../interfaces/view';
import {ActiveElementFlags, getActiveDirectiveId, getCurrentStyleSanitizer, getLView, getSelectedIndex, setActiveElementFlag, setCurrentStyleSanitizer, setElementExitFn} from '../state'; import {getActiveDirectiveId, getActiveDirectiveSuperClassDepth, getActiveDirectiveSuperClassHeight, getCurrentStyleSanitizer, getLView, getPreviousOrParentTNode, getSelectedIndex, setCurrentStyleSanitizer} from '../state';
import {NO_CHANGE} from '../tokens'; import {NO_CHANGE} from '../tokens';
import {renderStringify} from '../util/misc_utils'; import {renderStringify} from '../util/misc_utils';
import {getNativeByTNode, getTNode} from '../util/view_utils'; import {getNativeByTNode, getTNode} from '../util/view_utils';
import {applyStylingMapDirectly, applyStylingValueDirectly, flushStyling, setClass, setStyle, updateClassViaContext, updateStyleViaContext} from './bindings'; import {flushStyling, updateClassBinding, updateStyleBinding} from './bindings';
import {StylingMapArray, StylingMapArrayIndex, TStylingContext} from './interfaces'; import {StylingMapArray, StylingMapArrayIndex, TStylingContext} from './interfaces';
import {activateStylingMapFeature} from './map_based_bindings'; import {activateStylingMapFeature, addItemToStylingMap, normalizeIntoStylingMap, stylingMapToString} from './map_based_bindings';
import {attachStylingDebugObject} from './styling_debug'; import {attachStylingDebugObject} from './styling_debug';
import {addItemToStylingMap, allocStylingMapArray, allocTStylingContext, allowDirectStyling, concatString, forceClassesAsString, forceStylesAsString, getInitialStylingValue, getStylingMapArray, hasClassInput, hasStyleInput, hasValueChanged, isContextLocked, isHostStylingActive, isStylingContext, normalizeIntoStylingMap, setValue, stylingMapToString} from './util'; import {allocTStylingContext, concatString, forceClassesAsString, forceStylesAsString, getInitialStylingValue, getStylingMapArray, hasClassInput, hasStyleInput, hasValueChanged, isContextLocked, isStylingContext, updateLastDirectiveIndex as _updateLastDirectiveIndex} from './util';
@ -34,13 +34,34 @@ import {addItemToStylingMap, allocStylingMapArray, allocTStylingContext, allowDi
* -------- * --------
*/ */
/**
* Temporary function to bridge styling functionality between this new
* refactor (which is here inside of `styling_next/`) and the old
* implementation (which lives inside of `styling/`).
*
* This function is executed during the creation block of an element.
* Because the existing styling implementation issues a call to the
* `styling()` instruction, this instruction will also get run. The
* central idea here is that the directive index values are bound
* into the context. The directive index is temporary and is only
* required until the `select(n)` instruction is fully functional.
*
* @codeGenApi
*/
export function ɵɵstyling() {
const tView = getLView()[TVIEW];
if (tView.firstTemplatePass) {
updateLastDirectiveIndex(getPreviousOrParentTNode(), getActiveDirectiveStylingIndex());
}
}
/** /**
* Sets the current style sanitizer function which will then be used * Sets the current style sanitizer function which will then be used
* within all follow-up prop and map-based style binding instructions * within all follow-up prop and map-based style binding instructions
* for the given element. * for the given element.
* *
* Note that once styling has been applied to the element (i.e. once * Note that once styling has been applied to the element (i.e. once
* `advance(n)` is executed or the hostBindings/template function exits) * `select(n)` is executed or the hostBindings/template function exits)
* then the active `sanitizerFn` will be set to `null`. This means that * then the active `sanitizerFn` will be set to `null`. This means that
* once styling is applied to another element then a another call to * once styling is applied to another element then a another call to
* `styleSanitizer` will need to be made. * `styleSanitizer` will need to be made.
@ -71,7 +92,7 @@ export function ɵɵstyleSanitizer(sanitizer: StyleSanitizeFn | null): void {
* be ignored. * be ignored.
* *
* Note that this will apply the provided style value to the host element if this function is called * Note that this will apply the provided style value to the host element if this function is called
* within a host binding function. * within a host binding.
* *
* @codeGenApi * @codeGenApi
*/ */
@ -80,15 +101,9 @@ export function ɵɵstyleProp(
stylePropInternal(getSelectedIndex(), prop, value, suffix); stylePropInternal(getSelectedIndex(), prop, value, suffix);
} }
/**
* Internal function for applying a single style to an element.
*
* The reason why this function has been separated from `ɵɵstyleProp` is because
* it is also called from `ɵɵstylePropInterpolate`.
*/
export function stylePropInternal( export function stylePropInternal(
elementIndex: number, prop: string, value: string | number | SafeValue | null, elementIndex: number, prop: string, value: string | number | SafeValue | null,
suffix?: string | null | undefined): void { suffix?: string | null | undefined) {
const lView = getLView(); const lView = getLView();
// if a value is interpolated then it may render a `NO_CHANGE` value. // if a value is interpolated then it may render a `NO_CHANGE` value.
@ -97,8 +112,9 @@ export function stylePropInternal(
// are stored inside of the lView. // are stored inside of the lView.
const bindingIndex = lView[BINDING_INDEX]++; const bindingIndex = lView[BINDING_INDEX]++;
const updated = const updated = _stylingProp(
stylingProp(elementIndex, bindingIndex, prop, resolveStylePropValue(value, suffix), false); elementIndex, bindingIndex, prop, resolveStylePropValue(value, suffix), false,
deferStylingUpdate());
if (ngDevMode) { if (ngDevMode) {
ngDevMode.styleProp++; ngDevMode.styleProp++;
if (updated) { if (updated) {
@ -118,7 +134,7 @@ export function stylePropInternal(
* @param value A true/false value which will turn the class on or off. * @param value A true/false value which will turn the class on or off.
* *
* Note that this will apply the provided class value to the host element if this function * Note that this will apply the provided class value to the host element if this function
* is called within a host binding function. * is called within a host binding.
* *
* @codeGenApi * @codeGenApi
*/ */
@ -131,7 +147,8 @@ export function ɵɵclassProp(className: string, value: boolean | null): void {
// are stored inside of the lView. // are stored inside of the lView.
const bindingIndex = lView[BINDING_INDEX]++; const bindingIndex = lView[BINDING_INDEX]++;
const updated = stylingProp(getSelectedIndex(), bindingIndex, className, value, true); const updated =
_stylingProp(getSelectedIndex(), bindingIndex, className, value, true, deferStylingUpdate());
if (ngDevMode) { if (ngDevMode) {
ngDevMode.classProp++; ngDevMode.classProp++;
if (updated) { if (updated) {
@ -142,57 +159,28 @@ export function ɵɵclassProp(className: string, value: boolean | null): void {
/** /**
* Shared function used to update a prop-based styling binding for an element. * Shared function used to update a prop-based styling binding for an element.
*
* Depending on the state of the `tNode.styles` styles context, the style/prop
* value may be applied directly to the element instead of being processed
* through the context. The reason why this occurs is for performance and fully
* depends on the state of the context (i.e. whether or not there are duplicate
* bindings or whether or not there are map-based bindings and property bindings
* present together).
*/ */
function stylingProp( function _stylingProp(
elementIndex: number, bindingIndex: number, prop: string, elementIndex: number, bindingIndex: number, prop: string,
value: boolean | number | SafeValue | string | null | undefined | NO_CHANGE, value: boolean | number | SafeValue | string | null | undefined | NO_CHANGE,
isClassBased: boolean): boolean { isClassBased: boolean, defer: boolean): boolean {
let updated = false;
const lView = getLView(); const lView = getLView();
const tNode = getTNode(elementIndex, lView); const tNode = getTNode(elementIndex, lView);
const native = getNativeByTNode(tNode, lView) as RElement; const native = getNativeByTNode(tNode, lView) as RElement;
const hostBindingsMode = isHostStyling(); let valueHasChanged = false;
const context = isClassBased ? getClassesContext(tNode) : getStylesContext(tNode);
const sanitizer = isClassBased ? null : getCurrentStyleSanitizer();
// Direct Apply Case: bypass context resolution and apply the
// style/class value directly to the element
if (allowDirectStyling(context, hostBindingsMode)) {
const renderer = getRenderer(tNode, lView);
updated = applyStylingValueDirectly(
renderer, context, native, lView, bindingIndex, prop, value,
isClassBased ? setClass : setStyle, sanitizer);
} else {
// Context Resolution (or first update) Case: save the value
// and defer to the context to flush and apply the style/class binding
// value to the element.
const directiveIndex = getActiveDirectiveId();
if (isClassBased) { if (isClassBased) {
updated = updateClassViaContext( valueHasChanged = updateClassBinding(
context, lView, native, directiveIndex, prop, bindingIndex, getClassesContext(tNode), lView, native, prop, bindingIndex,
value as string | boolean | null); value as string | boolean | null, defer, false);
} else { } else {
updated = updateStyleViaContext( const sanitizer = getCurrentStyleSanitizer();
context, lView, native, directiveIndex, prop, bindingIndex, valueHasChanged = updateStyleBinding(
value as string | SafeValue | null, sanitizer); getStylesContext(tNode), lView, native, prop, bindingIndex,
value as string | SafeValue | null, sanitizer, defer, false);
} }
if (updated) { return valueHasChanged;
setElementExitFn(applyStyling);
}
markStylingStateAsDirty();
}
return updated;
} }
/** /**
@ -219,6 +207,7 @@ export function ɵɵstyleMap(styles: {[styleName: string]: any} | NO_CHANGE | nu
const lView = getLView(); const lView = getLView();
const tNode = getTNode(index, lView); const tNode = getTNode(index, lView);
const context = getStylesContext(tNode); const context = getStylesContext(tNode);
const directiveIndex = getActiveDirectiveStylingIndex();
// if a value is interpolated then it may render a `NO_CHANGE` value. // if a value is interpolated then it may render a `NO_CHANGE` value.
// in this case we do not need to do anything, but the binding index // in this case we do not need to do anything, but the binding index
@ -229,12 +218,12 @@ export function ɵɵstyleMap(styles: {[styleName: string]: any} | NO_CHANGE | nu
// inputs are only evaluated from a template binding into a directive, therefore, // inputs are only evaluated from a template binding into a directive, therefore,
// there should not be a situation where a directive host bindings function // there should not be a situation where a directive host bindings function
// evaluates the inputs (this should only happen in the template function) // evaluates the inputs (this should only happen in the template function)
if (!isHostStyling() && hasStyleInput(tNode) && styles !== NO_CHANGE) { if (!directiveIndex && hasStyleInput(tNode) && styles !== NO_CHANGE) {
updateDirectiveInputValue(context, lView, tNode, bindingIndex, styles, false); updateDirectiveInputValue(context, lView, tNode, bindingIndex, styles, false);
styles = NO_CHANGE; styles = NO_CHANGE;
} }
const updated = _stylingMap(index, context, bindingIndex, styles, false); const updated = _stylingMap(index, context, bindingIndex, styles, false, deferStylingUpdate());
if (ngDevMode) { if (ngDevMode) {
ngDevMode.styleMap++; ngDevMode.styleMap++;
if (updated) { if (updated) {
@ -265,17 +254,12 @@ export function ɵɵclassMap(classes: {[className: string]: any} | NO_CHANGE | s
classMapInternal(getSelectedIndex(), classes); classMapInternal(getSelectedIndex(), classes);
} }
/**
* Internal function for applying a class string or key/value map of classes to an element.
*
* The reason why this function has been separated from `ɵɵclassMap` is because
* it is also called from `ɵɵclassMapInterpolate`.
*/
export function classMapInternal( export function classMapInternal(
elementIndex: number, classes: {[className: string]: any} | NO_CHANGE | string | null): void { elementIndex: number, classes: {[className: string]: any} | NO_CHANGE | string | null) {
const lView = getLView(); const lView = getLView();
const tNode = getTNode(elementIndex, lView); const tNode = getTNode(elementIndex, lView);
const context = getClassesContext(tNode); const context = getClassesContext(tNode);
const directiveIndex = getActiveDirectiveStylingIndex();
// if a value is interpolated then it may render a `NO_CHANGE` value. // if a value is interpolated then it may render a `NO_CHANGE` value.
// in this case we do not need to do anything, but the binding index // in this case we do not need to do anything, but the binding index
@ -286,12 +270,13 @@ export function classMapInternal(
// inputs are only evaluated from a template binding into a directive, therefore, // inputs are only evaluated from a template binding into a directive, therefore,
// there should not be a situation where a directive host bindings function // there should not be a situation where a directive host bindings function
// evaluates the inputs (this should only happen in the template function) // evaluates the inputs (this should only happen in the template function)
if (!isHostStyling() && hasClassInput(tNode) && classes !== NO_CHANGE) { if (!directiveIndex && hasClassInput(tNode) && classes !== NO_CHANGE) {
updateDirectiveInputValue(context, lView, tNode, bindingIndex, classes, true); updateDirectiveInputValue(context, lView, tNode, bindingIndex, classes, true);
classes = NO_CHANGE; classes = NO_CHANGE;
} }
const updated = _stylingMap(elementIndex, context, bindingIndex, classes, true); const updated =
_stylingMap(elementIndex, context, bindingIndex, classes, true, deferStylingUpdate());
if (ngDevMode) { if (ngDevMode) {
ngDevMode.classMap++; ngDevMode.classMap++;
if (updated) { if (updated) {
@ -308,52 +293,27 @@ export function classMapInternal(
*/ */
function _stylingMap( function _stylingMap(
elementIndex: number, context: TStylingContext, bindingIndex: number, elementIndex: number, context: TStylingContext, bindingIndex: number,
value: {[key: string]: any} | string | null, isClassBased: boolean): boolean { value: {[key: string]: any} | string | null, isClassBased: boolean, defer: boolean) {
let updated = false; activateStylingMapFeature();
const lView = getLView(); const lView = getLView();
const directiveIndex = getActiveDirectiveId();
const tNode = getTNode(elementIndex, lView); const tNode = getTNode(elementIndex, lView);
const native = getNativeByTNode(tNode, lView) as RElement; const native = getNativeByTNode(tNode, lView) as RElement;
const oldValue = lView[bindingIndex] as StylingMapArray | null; const oldValue = lView[bindingIndex];
const hostBindingsMode = isHostStyling();
const sanitizer = getCurrentStyleSanitizer();
const valueHasChanged = hasValueChanged(oldValue, value); const valueHasChanged = hasValueChanged(oldValue, value);
const stylingMapArr = const stylingMapArr =
value === NO_CHANGE ? NO_CHANGE : normalizeIntoStylingMap(oldValue, value, !isClassBased); value === NO_CHANGE ? NO_CHANGE : normalizeIntoStylingMap(oldValue, value, !isClassBased);
// Direct Apply Case: bypass context resolution and apply the
// style/class map values directly to the element
if (allowDirectStyling(context, hostBindingsMode)) {
const renderer = getRenderer(tNode, lView);
updated = applyStylingMapDirectly(
renderer, context, native, lView, bindingIndex, stylingMapArr as StylingMapArray,
isClassBased ? setClass : setStyle, sanitizer, valueHasChanged);
} else {
// Context Resolution (or first update) Case: save the map value
// and defer to the context to flush and apply the style/class binding
// value to the element.
if (isClassBased) { if (isClassBased) {
updateClassViaContext( updateClassBinding(
context, lView, native, directiveIndex, null, bindingIndex, stylingMapArr, context, lView, native, null, bindingIndex, stylingMapArr, defer, valueHasChanged);
valueHasChanged);
} else { } else {
updateStyleViaContext( const sanitizer = getCurrentStyleSanitizer();
context, lView, native, directiveIndex, null, bindingIndex, stylingMapArr, sanitizer, updateStyleBinding(
context, lView, native, null, bindingIndex, stylingMapArr, sanitizer, defer,
valueHasChanged); valueHasChanged);
} }
if (valueHasChanged) { return valueHasChanged;
updated = true;
setElementExitFn(applyStyling);
}
activateStylingMapFeature();
}
markStylingStateAsDirty();
return updated;
} }
/** /**
@ -378,15 +338,13 @@ function updateDirectiveInputValue(
// even if the value has changed we may not want to emit it to the // even if the value has changed we may not want to emit it to the
// directive input(s) in the event that it is falsy during the // directive input(s) in the event that it is falsy during the
// first update pass. // first update pass.
if (newValue || isContextLocked(context, false)) { if (newValue || isContextLocked(context)) {
const inputName = isClassBased ? 'class' : 'style'; const inputs = tNode.inputs ![isClassBased ? 'class' : 'style'] !;
const inputs = tNode.inputs ![inputName] !;
const initialValue = getInitialStylingValue(context); const initialValue = getInitialStylingValue(context);
const value = normalizeStylingDirectiveInputValue(initialValue, newValue, isClassBased); const value = normalizeStylingDirectiveInputValue(initialValue, newValue, isClassBased);
setInputsForProperty(lView, inputs, value); setInputsForProperty(lView, inputs, value);
} }
setValue(lView, bindingIndex, newValue); lView[bindingIndex] = newValue;
setElementExitFn(applyStyling);
} }
} }
@ -404,7 +362,7 @@ function normalizeStylingDirectiveInputValue(
// we only concat values if there is an initial value, otherwise we return the value as is. // we only concat values if there is an initial value, otherwise we return the value as is.
// Note that this is to satisfy backwards-compatibility in Angular. // Note that this is to satisfy backwards-compatibility in Angular.
if (initialValue.length) { if (initialValue.length > 0) {
if (isClassBased) { if (isClassBased) {
value = concatString(initialValue, forceClassesAsString(bindingValue)); value = concatString(initialValue, forceClassesAsString(bindingValue));
} else { } else {
@ -419,21 +377,24 @@ function normalizeStylingDirectiveInputValue(
/** /**
* Flushes all styling code to the element. * Flushes all styling code to the element.
* *
* This function is designed to be scheduled from any of the four styling instructions * This function is designed to be called from the template and hostBindings
* in this file. When called it will flush all style and class bindings to the element * functions and may be called multiple times depending whether multiple
* via the context resolution algorithm. * sources of styling exist. If called multiple times, only the last call
* to `stlyingApply()` will render styling to the element.
*
* @codeGenApi
*/ */
function applyStyling(): void { export function ɵɵstylingApply() {
const elementIndex = getSelectedIndex(); const elementIndex = getSelectedIndex();
const lView = getLView(); const lView = getLView();
const tNode = getTNode(elementIndex, lView); const tNode = getTNode(elementIndex, lView);
const renderer = getRenderer(tNode, lView); const renderer = getRenderer(tNode, lView);
const native = getNativeByTNode(tNode, lView) as RElement; const native = getNativeByTNode(tNode, lView) as RElement;
const directiveIndex = getActiveDirectiveStylingIndex();
const sanitizer = getCurrentStyleSanitizer(); const sanitizer = getCurrentStyleSanitizer();
const classesContext = isStylingContext(tNode.classes) ? tNode.classes as TStylingContext : null;
const stylesContext = isStylingContext(tNode.styles) ? tNode.styles as TStylingContext : null;
flushStyling( flushStyling(
renderer, lView, classesContext, stylesContext, native, getActiveDirectiveId(), sanitizer); renderer, lView, getClassesContext(tNode), getStylesContext(tNode), native, directiveIndex,
sanitizer);
setCurrentStyleSanitizer(null); setCurrentStyleSanitizer(null);
} }
@ -456,12 +417,12 @@ export function registerInitialStylingOnTNode(
if (typeof attr == 'number') { if (typeof attr == 'number') {
mode = attr; mode = attr;
} else if (mode == AttributeMarker.Classes) { } else if (mode == AttributeMarker.Classes) {
classes = classes || allocStylingMapArray(); classes = classes || [''];
addItemToStylingMap(classes, attr, true); addItemToStylingMap(classes, attr, true);
hasAdditionalInitialStyling = true; hasAdditionalInitialStyling = true;
} else if (mode == AttributeMarker.Styles) { } else if (mode == AttributeMarker.Styles) {
const value = attrs[++i] as string | null; const value = attrs[++i] as string | null;
styles = styles || allocStylingMapArray(); styles = styles || [''];
addItemToStylingMap(styles, attr, value); addItemToStylingMap(styles, attr, value);
hasAdditionalInitialStyling = true; hasAdditionalInitialStyling = true;
} }
@ -489,6 +450,33 @@ function updateRawValueOnContext(context: TStylingContext | StylingMapArray, val
stylingMapArr[StylingMapArrayIndex.RawValuePosition] = value; stylingMapArr[StylingMapArrayIndex.RawValuePosition] = value;
} }
export function getActiveDirectiveStylingIndex(): number {
// whenever a directive's hostBindings function is called a uniqueId value
// is assigned. Normally this is enough to help distinguish one directive
// from another for the styling context, but there are situations where a
// sub-class directive could inherit and assign styling in concert with a
// parent directive. To help the styling code distinguish between a parent
// sub-classed directive the inheritance depth is taken into account as well.
return getActiveDirectiveId() + getActiveDirectiveSuperClassDepth();
}
/**
* Temporary function that will update the max directive index value in
* both the classes and styles contexts present on the provided `tNode`.
*
* This code is only used because the `select(n)` code functionality is not
* yet 100% functional. The `select(n)` instruction cannot yet evaluate host
* bindings function code in sync with the associated template function code.
* For this reason the styling algorithm needs to track the last directive index
* value so that it knows exactly when to render styling to the element since
* `stylingApply()` is called multiple times per CD (`stylingApply` will be
* removed once `select(n)` is fixed).
*/
function updateLastDirectiveIndex(tNode: TNode, directiveIndex: number) {
_updateLastDirectiveIndex(getClassesContext(tNode), directiveIndex);
_updateLastDirectiveIndex(getStylesContext(tNode), directiveIndex);
}
function getStylesContext(tNode: TNode): TStylingContext { function getStylesContext(tNode: TNode): TStylingContext {
return getContext(tNode, false); return getContext(tNode, false);
} }
@ -500,10 +488,10 @@ function getClassesContext(tNode: TNode): TStylingContext {
/** /**
* Returns/instantiates a styling context from/to a `tNode` instance. * Returns/instantiates a styling context from/to a `tNode` instance.
*/ */
function getContext(tNode: TNode, isClassBased: boolean): TStylingContext { function getContext(tNode: TNode, isClassBased: boolean) {
let context = isClassBased ? tNode.classes : tNode.styles; let context = isClassBased ? tNode.classes : tNode.styles;
if (!isStylingContext(context)) { if (!isStylingContext(context)) {
context = allocTStylingContext(context as StylingMapArray | null); context = allocTStylingContext(context);
if (ngDevMode) { if (ngDevMode) {
attachStylingDebugObject(context as TStylingContext); attachStylingDebugObject(context as TStylingContext);
} }
@ -538,14 +526,18 @@ function resolveStylePropValue(
return resolvedValue; return resolvedValue;
} }
function markStylingStateAsDirty(): void {
setActiveElementFlag(ActiveElementFlags.ResetStylesOnExit);
}
/** /**
* Whether or not the style/class binding being applied was executed within a host bindings * Whether or not a style/class binding update should be applied later.
* function. *
* This function will decide whether a binding should be applied immediately
* or later (just before the styles/classes are flushed to the element). The
* reason why this feature exists is because of super/sub directive inheritance.
* Angular will evaluate host bindings on the super directive first and the sub
* directive, but the styling bindings on the sub directive are of higher priority
* than the super directive. For this reason all styling bindings that take place
* in this circumstance will need to be deferred until later so that they can be
* applied together and in a different order (the algorithm handles that part).
*/ */
function isHostStyling(): boolean { function deferStylingUpdate(): boolean {
return isHostStylingActive(getActiveDirectiveId()); return getActiveDirectiveSuperClassHeight() > 0;
} }

View File

@ -26,7 +26,8 @@ import {LView} from '../interfaces/view';
* The `TStylingContext` unites all template styling bindings (i.e. * The `TStylingContext` unites all template styling bindings (i.e.
* `[class]` and `[style]` bindings) as well as all host-level * `[class]` and `[style]` bindings) as well as all host-level
* styling bindings (for components and directives) together into * styling bindings (for components and directives) together into
* a single manifest * a single manifest. It is used each time there are one or more
* styling bindings present for an element.
* *
* The styling context is stored on a `TNode` on and there are * The styling context is stored on a `TNode` on and there are
* two instances of it: one for classes and another for styles. * two instances of it: one for classes and another for styles.
@ -36,10 +37,6 @@ import {LView} from '../interfaces/view';
* tNode.classes = [ ... a context only for classes ... ]; * tNode.classes = [ ... a context only for classes ... ];
* ``` * ```
* *
* The styling context is created each time there are one or more
* styling bindings (style or class bindings) present for an element,
* but is only created once per `TNode`.
*
* `tNode.styles` and `tNode.classes` can be an instance of the following: * `tNode.styles` and `tNode.classes` can be an instance of the following:
* *
* ```typescript * ```typescript
@ -65,48 +62,36 @@ import {LView} from '../interfaces/view';
* storing actual styling binding values, the lView binding index values * storing actual styling binding values, the lView binding index values
* are stored within the context. (static nature means it is more compact.) * are stored within the context. (static nature means it is more compact.)
* *
* The code below shows a breakdown of two instances of `TStylingContext`
* (one for `tNode.styles` and another for `tNode.classes`):
*
* ```typescript * ```typescript
* // <div [class.active]="c" // lView binding index = 20 * // <div [class.active]="c" // lView binding index = 20
* // [style.width]="x" // lView binding index = 21 * // [style.width]="x" // lView binding index = 21
* // [style.height]="y"> // lView binding index = 22 * // [style.height]="y"> // lView binding index = 22
* // ... * tNode.stylesContext = [
* // </div> * [], // initial values array
* tNode.styles = [ * 0, // the context config value
* 0, // the context config value (see `TStylingContextConfig`)
* 1, // the total amount of sources present (only `1` b/c there are only template
* bindings)
* [null], // initial values array (an instance of `StylingMapArray`)
* *
* 0, // config entry for the property (see `TStylingContextPropConfigFlags`) * 0b001, // guard mask for width
* 0b010, // template guard mask for height * 2, // total entries for width
* 0, // host bindings guard mask for height
* 'height', // the property name
* 22, // the binding location for the "y" binding in the lView
* null, // the default value for height
*
* 0, // config entry for the property (see `TStylingContextPropConfigFlags`)
* 0b001, // template guard mask for width
* 0, // host bindings guard mask for width
* 'width', // the property name * 'width', // the property name
* 21, // the binding location for the "x" binding in the lView * 21, // the binding location for the "x" binding in the lView
* null, // the default value for width * null,
*
* 0b010, // guard mask for height
* 2, // total entries for height
* 'height', // the property name
* 22, // the binding location for the "y" binding in the lView
* null,
* ]; * ];
* *
* tNode.classes = [ * tNode.classesContext = [
* 0, // the context config value (see `TStylingContextConfig`) * [], // initial values array
* 1, // the total amount of sources present (only `1` b/c there are only template * 0, // the context config value
* bindings)
* [null], // initial values array (an instance of `StylingMapArray`)
* *
* 0, // config entry for the property (see `TStylingContextPropConfigFlags`) * 0b001, // guard mask for active
* 0b001, // template guard mask for width * 2, // total entries for active
* 0, // host bindings guard mask for width
* 'active', // the property name * 'active', // the property name
* 20, // the binding location for the "c" binding in the lView * 20, // the binding location for the "c" binding in the lView
* null, // the default value for the `active` class * null,
* ]; * ];
* ``` * ```
* *
@ -115,25 +100,19 @@ import {LView} from '../interfaces/view';
* *
* ```typescript * ```typescript
* context = [ * context = [
* CONFIG, // the styling context config value
* //... * //...
* configValue, * guardMask,
* templateGuardMask, * totalEntries,
* hostBindingsGuardMask,
* propName, * propName,
* ...bindingIndices..., * bindingIndices...,
* defaultValue * defaultValue
* //...
* ]; * ];
* ``` * ```
* *
* Below is a breakdown of each value: * Below is a breakdown of each value:
* *
* - **configValue**: * - **guardMask**:
* Property-specific configuration values. The only config setting
* that is implemented right now is whether or not to sanitize the
* value.
*
* - **templateGuardMask**:
* A numeric value where each bit represents a binding index * A numeric value where each bit represents a binding index
* location. Each binding index location is assigned based on * location. Each binding index location is assigned based on
* a local counter value that increments each time an instruction * a local counter value that increments each time an instruction
@ -157,23 +136,18 @@ import {LView} from '../interfaces/view';
* efficient way to flip all bits on the mask when a special kind * efficient way to flip all bits on the mask when a special kind
* of caching scenario occurs or when there are more than 32 bindings). * of caching scenario occurs or when there are more than 32 bindings).
* *
* - **hostBindingsGuardMask**: * - **totalEntries**:
* Another instance of a guard mask that is specific to host bindings. * Each property present in the contains various binding sources of
* This behaves exactly the same way as does the `templateGuardMask`, * where the styling data could come from. This includes template
* but will not contain any binding information processed in the template. * level bindings, directive/component host bindings as well as the
* The reason why there are two instances of guard masks (one for the * default value (or static value) all writing to the same property.
* template and another for host bindings) is because the template bindings * This value depicts how many binding source entries exist for the
* are processed before host bindings and the state information is not * property.
* carried over into the host bindings code. As soon as host bindings are
* processed for an element the counter and state-based bit mask values are
* set to `0`.
* *
* ``` * The reason why the totalEntries value is needed is because the
* <div [style.width]="x" // binding index = 21 (counter index = 0) * styling context is dynamic in size and it's not possible
* [style.height]="y" // binding index = 22 (counter index = 1) * for the flushing or update algorithms to know when and where
* dir-that-sets-width // binding index = 30 (counter index = 0) * a property starts and ends without it.
* dir-that-sets-width> // binding index = 31 (counter index = 1)
* ```
* *
* - **propName**: * - **propName**:
* The CSS property name or class name (e.g `width` or `active`). * The CSS property name or class name (e.g `width` or `active`).
@ -191,20 +165,15 @@ import {LView} from '../interfaces/view';
* - **defaultValue**: * - **defaultValue**:
* This is the default that will always be applied to the element if * This is the default that will always be applied to the element if
* and when all other binding sources return a result that is null. * and when all other binding sources return a result that is null.
* Usually this value is `null` but it can also be a static value that * Usually this value is null but it can also be a static value that
* is intercepted when the tNode is first constructured (e.g. * is intercepted when the tNode is first constructured (e.g.
* `<div style="width:200px">` has a default value of `200px` for * `<div style="width:200px">` has a default value of `200px` for
* the `width` property). * the `width` property).
* *
* Each time a new binding is encountered it is registered into the * Each time a new binding is encountered it is registered into the
* context. The context then is continually updated until the first * context. The context then is continually updated until the first
* styling apply call has been called (which is automatically scheduled * styling apply call has been called (this is triggered by the
* to be called once an element exits during change detection). Note that * `stylingApply()` instruction for the active element).
* each entry in the context is stored in alphabetical order.
*
* Once styling has been flushed for the first time for an element the
* context will set as locked (this prevents bindings from being added
* to the context later on).
* *
* # How Styles/Classes are Rendered * # How Styles/Classes are Rendered
* Each time a styling instruction (e.g. `[class.name]`, `[style.prop]`, * Each time a styling instruction (e.g. `[class.name]`, `[style.prop]`,
@ -221,24 +190,15 @@ import {LView} from '../interfaces/view';
* function updateStyleProp(prop: string, value: string) { * function updateStyleProp(prop: string, value: string) {
* const lView = getLView(); * const lView = getLView();
* const bindingIndex = BINDING_INDEX++; * const bindingIndex = BINDING_INDEX++;
* * const indexForStyle = localStylesCounter++;
* // update the local counter value
* const indexForStyle = stylingState.stylesCount++;
* if (lView[bindingIndex] !== value) { * if (lView[bindingIndex] !== value) {
* lView[bindingIndex] = value; * lView[bindingIndex] = value;
* * localBitMaskForStyles |= 1 << indexForStyle;
* // tell the local state that we have updated a style value
* // by updating the bit mask
* stylingState.bitMaskForStyles |= 1 << indexForStyle;
* } * }
* } * }
* ``` * ```
* *
* Once all the bindings have updated a `bitMask` value will be populated. * ## The Apply Algorithm
* This `bitMask` value is used in the apply algorithm (which is called
* context resolution).
*
* ## The Apply Algorithm (Context Resolution)
* As explained above, each time a binding updates its value, the resulting * As explained above, each time a binding updates its value, the resulting
* value is stored in the `lView` array. These styling values have yet to * value is stored in the `lView` array. These styling values have yet to
* be flushed to the element. * be flushed to the element.
@ -323,147 +283,94 @@ import {LView} from '../interfaces/view';
*/ */
export interface TStylingContext extends export interface TStylingContext extends
Array<number|string|number|boolean|null|StylingMapArray|{}> { Array<number|string|number|boolean|null|StylingMapArray|{}> {
/** Configuration data for the context */
[TStylingContextIndex.ConfigPosition]: TStylingConfig;
/** The total amount of sources present in the context */
[TStylingContextIndex.TotalSourcesPosition]: number;
/** Initial value position for static styles */ /** Initial value position for static styles */
[TStylingContextIndex.InitialStylingValuePosition]: StylingMapArray; [TStylingContextIndex.InitialStylingValuePosition]: StylingMapArray;
/** Configuration data for the context */
[TStylingContextIndex.ConfigPosition]: TStylingConfigFlags;
/** Temporary value used to track directive index entries until
the old styling code is fully removed. The reason why this
is required is to figure out which directive is last and,
when encountered, trigger a styling flush to happen */
[TStylingContextIndex.LastDirectiveIndexPosition]: number;
/** The bit guard value for all map-based bindings on an element */
[TStylingContextIndex.MapBindingsBitGuardPosition]: number;
/** The total amount of map-based bindings present on an element */
[TStylingContextIndex.MapBindingsValuesCountPosition]: number;
/** The prop value for map-based bindings (there actually isn't a
* value at all, but this is just used in the context to avoid
* having any special code to update the binding information for
* map-based entries). */
[TStylingContextIndex.MapBindingsPropPosition]: string;
} }
/** /**
* A series of flags used to configure the config value present within an instance of * A series of flags used to configure the config value present within a
* `TStylingContext`. * `TStylingContext` value.
*/ */
export const enum TStylingConfig { export const enum TStylingConfigFlags {
/** /**
* The initial state of the styling context config. * The initial state of the styling context config
*/ */
Initial = 0b0000000, Initial = 0b0,
/** /**
* Whether or not there are prop-based bindings present. * A flag which marks the context as being locked.
* *
* Examples include: * The styling context is constructed across an element template
* 1. `<div [style.prop]="x">` * function as well as any associated hostBindings functions. When
* 2. `<div [class.prop]="x">` * this occurs, the context itself is open to mutation and only once
* 3. `@HostBinding('style.prop') x` * it has been flushed once then it will be locked for good (no extra
* 4. `@HostBinding('class.prop') x` * bindings can be added to it).
*/ */
HasPropBindings = 0b0000001, Locked = 0b1,
/** /**
* Whether or not there are map-based bindings present. * Whether or not to store the state between updates in a global storage map.
* *
* Examples include: * This flag helps the algorithm avoid storing all state values temporarily in
* 1. `<div [style]="x">` * a storage map (that lives in `state.ts`). The flag is only flipped to true if
* 2. `<div [class]="x">` * and when an element contains style/class bindings that exist both on the
* 3. `@HostBinding('style') x` * template-level as well as within host bindings on the same element. This is a
* 4. `@HostBinding('class') x` * rare case, and a storage map is required so that the state values can be restored
* between the template code running and the host binding code executing.
*/ */
HasMapBindings = 0b0000010, PersistStateValues = 0b10,
/**
* Whether or not there are map-based and prop-based bindings present.
*
* Examples include:
* 1. `<div [style]="x" [style.prop]="y">`
* 2. `<div [class]="x" [style.prop]="y">`
* 3. `<div [style]="x" dir-that-sets-some-prop>`
* 4. `<div [class]="x" dir-that-sets-some-class>`
*/
HasPropAndMapBindings = 0b0000011,
/**
* Whether or not there are two or more sources for a single property in the context.
*
* Examples include:
* 1. prop + prop: `<div [style.width]="x" dir-that-sets-width>`
* 2. map + prop: `<div [style]="x" [style.prop]>`
* 3. map + map: `<div [style]="x" dir-that-sets-style>`
*/
HasCollisions = 0b0000100,
/**
* Whether or not the context contains initial styling values.
*
* Examples include:
* 1. `<div style="width:200px">`
* 2. `<div class="one two three">`
* 3. `@Directive({ host: { 'style': 'width:200px' } })`
* 4. `@Directive({ host: { 'class': 'one two three' } })`
*/
HasInitialStyling = 0b00001000,
/**
* Whether or not the context contains one or more template bindings.
*
* Examples include:
* 1. `<div [style]="x">`
* 2. `<div [style.width]="x">`
* 3. `<div [class]="x">`
* 4. `<div [class.name]="x">`
*/
HasTemplateBindings = 0b00010000,
/**
* Whether or not the context contains one or more host bindings.
*
* Examples include:
* 1. `@HostBinding('style') x`
* 2. `@HostBinding('style.width') x`
* 3. `@HostBinding('class') x`
* 4. `@HostBinding('class.name') x`
*/
HasHostBindings = 0b00100000,
/**
* Whether or not the template bindings are allowed to be registered in the context.
*
* This flag is after one or more template-based style/class bindings were
* set and processed for an element. Once the bindings are processed then a call
* to stylingApply is issued and the lock will be put into place.
*
* Note that this is only set once.
*/
TemplateBindingsLocked = 0b01000000,
/**
* Whether or not the host bindings are allowed to be registered in the context.
*
* This flag is after one or more host-based style/class bindings were
* set and processed for an element. Once the bindings are processed then a call
* to stylingApply is issued and the lock will be put into place.
*
* Note that this is only set once.
*/
HostBindingsLocked = 0b10000000,
/** A Mask of all the configurations */ /** A Mask of all the configurations */
Mask = 0b11111111, Mask = 0b11,
/** Total amount of configuration bits used */ /** Total amount of configuration bits used */
TotalBits = 8, TotalBits = 2,
} }
/** /**
* An index of position and offset values used to navigate the `TStylingContext`. * An index of position and offset values used to natigate the `TStylingContext`.
*/ */
export const enum TStylingContextIndex { export const enum TStylingContextIndex {
ConfigPosition = 0, InitialStylingValuePosition = 0,
TotalSourcesPosition = 1, ConfigPosition = 1,
InitialStylingValuePosition = 2, LastDirectiveIndexPosition = 2,
ValuesStartPosition = 3,
// index/offset values for map-based entries (i.e. `[style]`
// and `[class]` bindings).
MapBindingsPosition = 3,
MapBindingsBitGuardPosition = 3,
MapBindingsValuesCountPosition = 4,
MapBindingsPropPosition = 5,
MapBindingsBindingsStartPosition = 6,
// each tuple entry in the context // each tuple entry in the context
// (config, templateBitGuard, hostBindingBitGuard, prop, ...bindings||default-value) // (mask, count, prop, ...bindings||default-value)
ConfigOffset = 0, ConfigAndGuardOffset = 0,
TemplateBitGuardOffset = 1, ValuesCountOffset = 1,
HostBindingsBitGuardOffset = 2, PropOffset = 2,
PropOffset = 3, BindingsStartOffset = 3,
BindingsStartOffset = 4 MinTupleLength = 4,
} }
/** /**
@ -480,8 +387,8 @@ export const enum TStylingContextPropConfigFlags {
* A function used to apply or remove styling from an element for a given property. * A function used to apply or remove styling from an element for a given property.
*/ */
export interface ApplyStylingFn { export interface ApplyStylingFn {
(renderer: Renderer3|ProceduralRenderer3|null, element: RElement, prop: string, value: any, (renderer: Renderer3|ProceduralRenderer3|null, element: RElement, prop: string,
bindingIndex?: number|null): void; value: string|null, bindingIndex?: number|null): void;
} }
/** /**
@ -512,12 +419,12 @@ export interface StylingMapArray extends Array<{}|string|number|null> {
* An index of position and offset points for any data stored within a `StylingMapArray` instance. * An index of position and offset points for any data stored within a `StylingMapArray` instance.
*/ */
export const enum StylingMapArrayIndex { export const enum StylingMapArrayIndex {
/** Where the values start in the array */
ValuesStartPosition = 1,
/** The location of the raw key/value map instance used last to populate the array entries */ /** The location of the raw key/value map instance used last to populate the array entries */
RawValuePosition = 0, RawValuePosition = 0,
/** Where the values start in the array */
ValuesStartPosition = 1,
/** The size of each property/value entry */ /** The size of each property/value entry */
TupleSize = 2, TupleSize = 2,
@ -551,9 +458,8 @@ export const enum StylingMapArrayIndex {
*/ */
export interface SyncStylingMapsFn { export interface SyncStylingMapsFn {
(context: TStylingContext, renderer: Renderer3|ProceduralRenderer3|null, element: RElement, (context: TStylingContext, renderer: Renderer3|ProceduralRenderer3|null, element: RElement,
data: LStylingData, sourceIndex: number, applyStylingFn: ApplyStylingFn, data: LStylingData, applyStylingFn: ApplyStylingFn, sanitizer: StyleSanitizeFn|null,
sanitizer: StyleSanitizeFn|null, mode: StylingMapsSyncMode, targetProp?: string|null, mode: StylingMapsSyncMode, targetProp?: string|null, defaultValue?: string|null): boolean;
defaultValue?: boolean|string|null): boolean;
} }
/** /**
@ -571,10 +477,4 @@ export const enum StylingMapsSyncMode {
/** Skip applying the target prop/value entry */ /** Skip applying the target prop/value entry */
SkipTargetProp = 0b100, SkipTargetProp = 0b100,
/** Iterate over inner maps map values in the context */
RecurseInnerMaps = 0b1000,
/** Only check to see if a value was set somewhere in each map */
CheckValuesOnly = 0b10000,
} }

View File

@ -5,13 +5,12 @@
* 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 {unwrapSafeValue} from '../../sanitization/bypass';
import {StyleSanitizeFn, StyleSanitizeMode} from '../../sanitization/style_sanitizer'; import {StyleSanitizeFn, StyleSanitizeMode} from '../../sanitization/style_sanitizer';
import {ProceduralRenderer3, RElement, Renderer3} from '../interfaces/renderer'; import {ProceduralRenderer3, RElement, Renderer3} from '../interfaces/renderer';
import {setStylingMapsSyncFn} from './bindings'; import {setStylingMapsSyncFn} from './bindings';
import {ApplyStylingFn, LStylingData, StylingMapArray, StylingMapArrayIndex, StylingMapsSyncMode, SyncStylingMapsFn, TStylingContext, TStylingContextIndex} from './interfaces'; import {ApplyStylingFn, LStylingData, StylingMapArray, StylingMapArrayIndex, StylingMapsSyncMode, SyncStylingMapsFn, TStylingContext, TStylingContextIndex} from './interfaces';
import {getBindingValue, getMapProp, getMapValue, getValue, getValuesCount, isStylingValueDefined} from './util'; import {concatString, getBindingValue, getMapProp, getMapValue, getValuesCount, hyphenate, isStylingValueDefined, setMapValue} from './util';
@ -24,13 +23,6 @@ import {getBindingValue, getMapProp, getMapValue, getValue, getValuesCount, isSt
* -------- * --------
*/ */
/**
* Enables support for map-based styling bindings (e.g. `[style]` and `[class]` bindings).
*/
export function activateStylingMapFeature() {
setStylingMapsSyncFn(syncStylingMap);
}
/** /**
* Used to apply styling values presently within any map-based bindings on an element. * Used to apply styling values presently within any map-based bindings on an element.
* *
@ -61,7 +53,7 @@ export function activateStylingMapFeature() {
* value is marked as dirty. * value is marked as dirty.
* *
* Styling values are applied once CD exits the element (which happens when * Styling values are applied once CD exits the element (which happens when
* the `advance(n)` instruction is called or the template function exits). When * the `select(n)` instruction is called or the template function exits). When
* this occurs, all prop-based bindings are applied. If a map-based binding is * this occurs, all prop-based bindings are applied. If a map-based binding is
* present then a special flushing function (called a sync function) is made * present then a special flushing function (called a sync function) is made
* available and it will be called each time a styling property is flushed. * available and it will be called each time a styling property is flushed.
@ -113,14 +105,14 @@ export function activateStylingMapFeature() {
*/ */
export const syncStylingMap: SyncStylingMapsFn = export const syncStylingMap: SyncStylingMapsFn =
(context: TStylingContext, renderer: Renderer3 | ProceduralRenderer3 | null, element: RElement, (context: TStylingContext, renderer: Renderer3 | ProceduralRenderer3 | null, element: RElement,
data: LStylingData, sourceIndex: number, applyStylingFn: ApplyStylingFn, data: LStylingData, applyStylingFn: ApplyStylingFn, sanitizer: StyleSanitizeFn | null,
sanitizer: StyleSanitizeFn | null, mode: StylingMapsSyncMode, targetProp?: string | null, mode: StylingMapsSyncMode, targetProp?: string | null,
defaultValue?: string | boolean | null): boolean => { defaultValue?: string | null): boolean => {
let targetPropValueWasApplied = false; let targetPropValueWasApplied = false;
// once the map-based styling code is activate it is never deactivated. For this reason a // once the map-based styling code is activate it is never deactivated. For this reason a
// check to see if the current styling context has any map based bindings is required. // check to see if the current styling context has any map based bindings is required.
const totalMaps = getValuesCount(context); const totalMaps = getValuesCount(context, TStylingContextIndex.MapBindingsPosition);
if (totalMaps) { if (totalMaps) {
let runTheSyncAlgorithm = true; let runTheSyncAlgorithm = true;
const loopUntilEnd = !targetProp; const loopUntilEnd = !targetProp;
@ -129,7 +121,7 @@ export const syncStylingMap: SyncStylingMapsFn =
// hasn't been flagged to apply values (it only traverses values) then // hasn't been flagged to apply values (it only traverses values) then
// there is no point in iterating over the array because nothing will // there is no point in iterating over the array because nothing will
// be applied to the element. // be applied to the element.
if (loopUntilEnd && (mode & StylingMapsSyncMode.ApplyAllValues) === 0) { if (loopUntilEnd && (mode & ~StylingMapsSyncMode.ApplyAllValues)) {
runTheSyncAlgorithm = false; runTheSyncAlgorithm = false;
targetPropValueWasApplied = true; targetPropValueWasApplied = true;
} }
@ -137,7 +129,7 @@ export const syncStylingMap: SyncStylingMapsFn =
if (runTheSyncAlgorithm) { if (runTheSyncAlgorithm) {
targetPropValueWasApplied = innerSyncStylingMap( targetPropValueWasApplied = innerSyncStylingMap(
context, renderer, element, data, applyStylingFn, sanitizer, mode, targetProp || null, context, renderer, element, data, applyStylingFn, sanitizer, mode, targetProp || null,
sourceIndex, defaultValue || null); 0, defaultValue || null);
} }
if (loopUntilEnd) { if (loopUntilEnd) {
@ -161,27 +153,15 @@ function innerSyncStylingMap(
context: TStylingContext, renderer: Renderer3 | ProceduralRenderer3 | null, element: RElement, context: TStylingContext, renderer: Renderer3 | ProceduralRenderer3 | null, element: RElement,
data: LStylingData, applyStylingFn: ApplyStylingFn, sanitizer: StyleSanitizeFn | null, data: LStylingData, applyStylingFn: ApplyStylingFn, sanitizer: StyleSanitizeFn | null,
mode: StylingMapsSyncMode, targetProp: string | null, currentMapIndex: number, mode: StylingMapsSyncMode, targetProp: string | null, currentMapIndex: number,
defaultValue: string | boolean | null): boolean { defaultValue: string | null): boolean {
const totalMaps = getValuesCount(context) - 1; // maps have no default value
const mapsLimit = totalMaps - 1;
const recurseInnerMaps =
currentMapIndex < mapsLimit && (mode & StylingMapsSyncMode.RecurseInnerMaps) !== 0;
const checkValuesOnly = (mode & StylingMapsSyncMode.CheckValuesOnly) !== 0;
if (checkValuesOnly) {
// inner modes do not check values ever (that can only happen
// when sourceIndex === 0)
mode &= ~StylingMapsSyncMode.CheckValuesOnly;
}
let targetPropValueWasApplied = false; let targetPropValueWasApplied = false;
if (currentMapIndex <= mapsLimit) { const totalMaps = getValuesCount(context, TStylingContextIndex.MapBindingsPosition);
let cursor = getCurrentSyncCursor(currentMapIndex); if (currentMapIndex < totalMaps) {
const bindingIndex = getBindingValue( const bindingIndex = getBindingValue(
context, TStylingContextIndex.ValuesStartPosition, currentMapIndex) as number; context, TStylingContextIndex.MapBindingsPosition, currentMapIndex) as number;
const stylingMapArr = getValue<StylingMapArray>(data, bindingIndex); const stylingMapArr = data[bindingIndex] as StylingMapArray;
if (stylingMapArr) { let cursor = getCurrentSyncCursor(currentMapIndex);
while (cursor < stylingMapArr.length) { while (cursor < stylingMapArr.length) {
const prop = getMapProp(stylingMapArr, cursor); const prop = getMapProp(stylingMapArr, cursor);
const iteratedTooFar = targetProp && prop > targetProp; const iteratedTooFar = targetProp && prop > targetProp;
@ -197,13 +177,10 @@ function innerSyncStylingMap(
// even if the code has iterated too far. // even if the code has iterated too far.
const innerMode = const innerMode =
iteratedTooFar ? mode : resolveInnerMapMode(mode, valueIsDefined, isTargetPropMatched); iteratedTooFar ? mode : resolveInnerMapMode(mode, valueIsDefined, isTargetPropMatched);
const innerProp = iteratedTooFar ? targetProp : prop; const innerProp = iteratedTooFar ? targetProp : prop;
let valueApplied = recurseInnerMaps ? let valueApplied = innerSyncStylingMap(
innerSyncStylingMap(
context, renderer, element, data, applyStylingFn, sanitizer, innerMode, innerProp, context, renderer, element, data, applyStylingFn, sanitizer, innerMode, innerProp,
currentMapIndex + 1, defaultValue) : currentMapIndex + 1, defaultValue);
false;
if (iteratedTooFar) { if (iteratedTooFar) {
if (!targetPropValueWasApplied) { if (!targetPropValueWasApplied) {
@ -213,23 +190,14 @@ function innerSyncStylingMap(
} }
if (!valueApplied && isValueAllowedToBeApplied(mode, isTargetPropMatched)) { if (!valueApplied && isValueAllowedToBeApplied(mode, isTargetPropMatched)) {
valueApplied = true;
if (!checkValuesOnly) {
const useDefault = isTargetPropMatched && !valueIsDefined; const useDefault = isTargetPropMatched && !valueIsDefined;
const bindingIndexToApply = isTargetPropMatched ? bindingIndex : null; const valueToApply = useDefault ? defaultValue : value;
const bindingIndexToApply = useDefault ? bindingIndex : null;
let finalValue: any; const finalValue = sanitizer ?
if (useDefault) { sanitizer(prop, valueToApply, StyleSanitizeMode.ValidateAndSanitize) :
finalValue = defaultValue; valueToApply;
} else {
finalValue = sanitizer ?
sanitizer(prop, value, StyleSanitizeMode.ValidateAndSanitize) :
(value ? unwrapSafeValue(value) : null);
}
applyStylingFn(renderer, element, prop, finalValue, bindingIndexToApply); applyStylingFn(renderer, element, prop, finalValue, bindingIndexToApply);
} valueApplied = true;
} }
targetPropValueWasApplied = valueApplied && isTargetPropMatched; targetPropValueWasApplied = valueApplied && isTargetPropMatched;
@ -243,22 +211,25 @@ function innerSyncStylingMap(
// through all of the properties in the remaining maps as well. If the current // through all of the properties in the remaining maps as well. If the current
// styling map is too short then there are no values to iterate over. In either // styling map is too short then there are no values to iterate over. In either
// case the follow-up maps need to be iterated over. // case the follow-up maps need to be iterated over.
if (recurseInnerMaps && if (stylingMapArr.length === StylingMapArrayIndex.ValuesStartPosition || !targetProp) {
(stylingMapArr.length === StylingMapArrayIndex.ValuesStartPosition || !targetProp)) { return innerSyncStylingMap(
targetPropValueWasApplied = innerSyncStylingMap(
context, renderer, element, data, applyStylingFn, sanitizer, mode, targetProp,
currentMapIndex + 1, defaultValue);
}
} else if (recurseInnerMaps) {
targetPropValueWasApplied = innerSyncStylingMap(
context, renderer, element, data, applyStylingFn, sanitizer, mode, targetProp, context, renderer, element, data, applyStylingFn, sanitizer, mode, targetProp,
currentMapIndex + 1, defaultValue); currentMapIndex + 1, defaultValue);
} }
} }
return targetPropValueWasApplied; return targetPropValueWasApplied;
} }
/**
* Enables support for map-based styling bindings (e.g. `[style]` and `[class]` bindings).
*/
export function activateStylingMapFeature() {
setStylingMapsSyncFn(syncStylingMap);
}
/** /**
* Used to determine the mode for the inner recursive call. * Used to determine the mode for the inner recursive call.
* *
@ -305,8 +276,8 @@ function resolveInnerMapMode(
* - But do not allow if the current prop is set to be skipped. * - But do not allow if the current prop is set to be skipped.
* 2. Otherwise if the current prop is permitted then allow. * 2. Otherwise if the current prop is permitted then allow.
*/ */
function isValueAllowedToBeApplied(mode: StylingMapsSyncMode, isTargetPropMatched: boolean) { function isValueAllowedToBeApplied(mode: number, isTargetPropMatched: boolean) {
let doApplyValue = (mode & StylingMapsSyncMode.ApplyAllValues) !== 0; let doApplyValue = (mode & StylingMapsSyncMode.ApplyAllValues) > 0;
if (!doApplyValue) { if (!doApplyValue) {
if (mode & StylingMapsSyncMode.ApplyTargetProp) { if (mode & StylingMapsSyncMode.ApplyTargetProp) {
doApplyValue = isTargetPropMatched; doApplyValue = isTargetPropMatched;
@ -349,3 +320,126 @@ function getCurrentSyncCursor(mapIndex: number) {
function setCurrentSyncCursor(mapIndex: number, indexValue: number) { function setCurrentSyncCursor(mapIndex: number, indexValue: number) {
MAP_CURSORS[mapIndex] = indexValue; MAP_CURSORS[mapIndex] = indexValue;
} }
/**
* Used to convert a {key:value} map into a `StylingMapArray` array.
*
* This function will either generate a new `StylingMapArray` instance
* or it will patch the provided `newValues` map value into an
* existing `StylingMapArray` value (this only happens if `bindingValue`
* is an instance of `StylingMapArray`).
*
* If a new key/value map is provided with an old `StylingMapArray`
* value then all properties will be overwritten with their new
* values or with `null`. This means that the array will never
* shrink in size (but it will also not be created and thrown
* away whenever the {key:value} map entries change).
*/
export function normalizeIntoStylingMap(
bindingValue: null | StylingMapArray,
newValues: {[key: string]: any} | string | null | undefined,
normalizeProps?: boolean): StylingMapArray {
const stylingMapArr: StylingMapArray = Array.isArray(bindingValue) ? bindingValue : [null];
stylingMapArr[StylingMapArrayIndex.RawValuePosition] = newValues || null;
// because the new values may not include all the properties
// that the old ones had, all values are set to `null` before
// the new values are applied. This way, when flushed, the
// styling algorithm knows exactly what style/class values
// to remove from the element (since they are `null`).
for (let j = StylingMapArrayIndex.ValuesStartPosition; j < stylingMapArr.length;
j += StylingMapArrayIndex.TupleSize) {
setMapValue(stylingMapArr, j, null);
}
let props: string[]|null = null;
let map: {[key: string]: any}|undefined|null;
let allValuesTrue = false;
if (typeof newValues === 'string') { // [class] bindings allow string values
if (newValues.length) {
props = newValues.split(/\s+/);
allValuesTrue = true;
}
} else {
props = newValues ? Object.keys(newValues) : null;
map = newValues;
}
if (props) {
for (let i = 0; i < props.length; i++) {
const prop = props[i] as string;
const newProp = normalizeProps ? hyphenate(prop) : prop;
const value = allValuesTrue ? true : map ![prop];
addItemToStylingMap(stylingMapArr, newProp, value, true);
}
}
return stylingMapArr;
}
/**
* Inserts the provided item into the provided styling array at the right spot.
*
* The `StylingMapArray` type is a sorted key/value array of entries. This means
* that when a new entry is inserted it must be placed at the right spot in the
* array. This function figures out exactly where to place it.
*/
export function addItemToStylingMap(
stylingMapArr: StylingMapArray, prop: string, value: string | boolean | null,
allowOverwrite?: boolean) {
for (let j = StylingMapArrayIndex.ValuesStartPosition; j < stylingMapArr.length;
j += StylingMapArrayIndex.TupleSize) {
const propAtIndex = getMapProp(stylingMapArr, j);
if (prop <= propAtIndex) {
let applied = false;
if (propAtIndex === prop) {
const valueAtIndex = stylingMapArr[j];
if (allowOverwrite || !isStylingValueDefined(valueAtIndex)) {
applied = true;
setMapValue(stylingMapArr, j, value);
}
} else {
applied = true;
stylingMapArr.splice(j, 0, prop, value);
}
return applied;
}
}
stylingMapArr.push(prop, value);
return true;
}
/**
* Converts the provided styling map array into a string.
*
* Classes => `one two three`
* Styles => `prop:value; prop2:value2`
*/
export function stylingMapToString(map: StylingMapArray, isClassBased: boolean): string {
let str = '';
for (let i = StylingMapArrayIndex.ValuesStartPosition; i < map.length;
i += StylingMapArrayIndex.TupleSize) {
const prop = getMapProp(map, i);
const value = getMapValue(map, i) as string;
const attrValue = concatString(prop, isClassBased ? '' : value, ':');
str = concatString(str, attrValue, isClassBased ? ' ' : '; ');
}
return str;
}
/**
* Converts the provided styling map array into a key value map.
*/
export function stylingMapToStringMap(map: StylingMapArray | null): {[key: string]: any} {
let stringMap: {[key: string]: any} = {};
if (map) {
for (let i = StylingMapArrayIndex.ValuesStartPosition; i < map.length;
i += StylingMapArrayIndex.TupleSize) {
const prop = getMapProp(map, i);
const value = getMapValue(map, i) as string;
stringMap[prop] = value;
}
}
return stringMap;
}

View File

@ -5,12 +5,12 @@
* 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 {RElement} from '../interfaces/renderer';
import {TEMPLATE_DIRECTIVE_INDEX} from './util';
/** /**
* -------- * --------
* *
* // TODO(matsko): add updateMask info
*
* This file contains all state-based logic for styling in Angular. * This file contains all state-based logic for styling in Angular.
* *
* Styling in Angular is evaluated with a series of styling-specific * Styling in Angular is evaluated with a series of styling-specific
@ -23,92 +23,79 @@ import {TEMPLATE_DIRECTIVE_INDEX} from './util';
* exited in change detection (once all the instructions are run for * exited in change detection (once all the instructions are run for
* that element). * that element).
* *
* There are, however, situations where the state-based values
* need to be stored and used at a later point. This ONLY occurs when
* there are template-level as well as host-binding-level styling
* instructions on the same element. The example below shows exactly
* what could be:
*
* ```html
* <!-- two sources of styling: the template and the directive -->
* <div [style.width]="width" dir-that-sets-height></div>
* ```
*
* If and when this situation occurs, the current styling state is
* stored in a storage map value and then later accessed once the
* host bindings are evaluated. Once styling for the current element
* is over then the map entry will be cleared.
*
* To learn more about the algorithm see `TStylingContext`. * To learn more about the algorithm see `TStylingContext`.
* *
* -------- * --------
*/ */
let _stylingState: StylingState|null = null;
const _stateStorage = new Map<any, StylingState>();
// this value is not used outside this file and is only here
// as a caching check for when the element changes.
let _stylingElement: any = null;
/** /**
* Used as a state reference for update values between style/class binding instructions. * Used as a state reference for update values between style/class binding instructions.
*
* In addition to storing the element and bit-mask related values, the state also
* stores the `sourceIndex` value. The `sourceIndex` value is an incremented value
* that identifies what "source" (i.e. the template, a specific directive by index or
* component) is currently applying its styling bindings to the element.
*/ */
export interface StylingState { export interface StylingState {
/** The element that is currently being processed */
element: RElement|null;
/** The directive index that is currently active (`0` === template) */
directiveIndex: number;
/** The source (column) index that is currently active (`0` === template) */
sourceIndex: number;
/** The classes update bit mask value that is processed during each class binding */
classesBitMask: number; classesBitMask: number;
/** The classes update bit index value that is processed during each class binding */
classesIndex: number; classesIndex: number;
/** The styles update bit mask value that is processed during each style binding */
stylesBitMask: number; stylesBitMask: number;
/** The styles update bit index value that is processed during each style binding */
stylesIndex: number; stylesIndex: number;
} }
// these values will get filled in the very first time this is accessed... export const STYLING_INDEX_START_VALUE = 1;
const _state: StylingState = { export const BIT_MASK_START_VALUE = 0;
element: null,
directiveIndex: -1, export function getStylingState(element: any, readFromMap?: boolean): StylingState {
sourceIndex: -1, if (!_stylingElement || element !== _stylingElement) {
classesBitMask: -1, _stylingElement = element;
classesIndex: -1, if (readFromMap) {
stylesBitMask: -1, _stylingState = _stateStorage.get(element) || null;
stylesIndex: -1, ngDevMode && ngDevMode.stylingReadPersistedState++;
}
_stylingState = _stylingState || {
classesBitMask: BIT_MASK_START_VALUE,
classesIndex: STYLING_INDEX_START_VALUE,
stylesBitMask: BIT_MASK_START_VALUE,
stylesIndex: STYLING_INDEX_START_VALUE,
}; };
const BIT_MASK_START_VALUE = 0;
// the `0` start value is reserved for [map]-based entries
const INDEX_START_VALUE = 1;
/**
* Returns (or instantiates) the styling state for the given element.
*
* Styling state is accessed and processed each time a style or class binding
* is evaluated.
*
* If and when the provided `element` doesn't match the current element in the
* state then this means that styling was recently cleared or the element has
* changed in change detection. In both cases the styling state is fully reset.
*
* If and when the provided `directiveIndex` doesn't match the current directive
* index in the state then this means that a new source has introduced itself into
* the styling code (or, in other words, another directive or component has started
* to apply its styling host bindings to the element).
*/
export function getStylingState(element: RElement, directiveIndex: number): StylingState {
if (_state.element !== element) {
_state.element = element;
_state.directiveIndex = directiveIndex;
_state.sourceIndex = directiveIndex === TEMPLATE_DIRECTIVE_INDEX ? 0 : 1;
_state.classesBitMask = BIT_MASK_START_VALUE;
_state.classesIndex = INDEX_START_VALUE;
_state.stylesBitMask = BIT_MASK_START_VALUE;
_state.stylesIndex = INDEX_START_VALUE;
} else if (_state.directiveIndex !== directiveIndex) {
_state.directiveIndex = directiveIndex;
_state.sourceIndex++;
} }
return _state; return _stylingState !;
} }
/**
* Clears the styling state so that it can be used by another element's styling code.
*/
export function resetStylingState() { export function resetStylingState() {
_state.element = null; _stylingState = null;
_stylingElement = null;
}
export function storeStylingState(element: any, state: StylingState) {
ngDevMode && ngDevMode.stylingWritePersistedState++;
_stateStorage.set(element, state);
}
export function deleteStylingStateFromStorage(element: any) {
_stateStorage.delete(element);
}
export function resetAllStylingState() {
resetStylingState();
_stateStorage.clear();
} }

View File

@ -7,13 +7,14 @@
*/ */
import {StyleSanitizeFn} from '../../sanitization/style_sanitizer'; import {StyleSanitizeFn} from '../../sanitization/style_sanitizer';
import {RElement} from '../interfaces/renderer'; import {RElement} from '../interfaces/renderer';
import {LView} from '../interfaces/view';
import {getCurrentStyleSanitizer} from '../state'; import {getCurrentStyleSanitizer} from '../state';
import {attachDebugObject} from '../util/debug_utils'; import {attachDebugObject} from '../util/debug_utils';
import {applyStylingViaContext} from './bindings'; import {applyStyling} from './bindings';
import {ApplyStylingFn, LStylingData, TStylingConfig, TStylingContext, TStylingContextIndex} from './interfaces'; import {ApplyStylingFn, LStylingData, TStylingContext, TStylingContextIndex} from './interfaces';
import {activateStylingMapFeature} from './map_based_bindings'; import {activateStylingMapFeature} from './map_based_bindings';
import {allowDirectStyling as _allowDirectStyling, getDefaultValue, getGuardMask, getProp, getPropValuesStartPosition, getValuesCount, hasConfig, isContextLocked, isMapBased, isSanitizationRequired} from './util'; import {getDefaultValue, getGuardMask, getProp, getValuesCount, isContextLocked, isMapBased, isSanitizationRequired} from './util';
@ -52,32 +53,20 @@ export interface DebugStyling {
/** The associated TStylingContext instance */ /** The associated TStylingContext instance */
context: TStylingContext; context: TStylingContext;
/** Which configuration flags are active (see `TStylingContextConfig`) */
config: {
hasMapBindings: boolean; //
hasPropBindings: boolean; //
hasCollisions: boolean; //
hasTemplateBindings: boolean; //
hasHostBindings: boolean; //
templateBindingsLocked: boolean; //
hostBindingsLocked: boolean; //
allowDirectStyling: boolean; //
};
/** /**
* A summarization of each style/class property * A summarization of each style/class property
* present in the context * present in the context.
*/ */
summary: {[propertyName: string]: LStylingSummary}; summary: {[key: string]: LStylingSummary};
/** /**
* A key/value map of all styling properties and their * A key/value map of all styling properties and their
* runtime values * runtime values.
*/ */
values: {[propertyName: string]: string | number | null | boolean}; values: {[key: string]: string | number | null | boolean};
/** /**
* Overrides the sanitizer used to process styles * Overrides the sanitizer used to process styles.
*/ */
overrideSanitizer(sanitizer: StyleSanitizeFn|null): void; overrideSanitizer(sanitizer: StyleSanitizeFn|null): void;
} }
@ -94,15 +83,9 @@ export interface TStylingTupleSummary {
/** /**
* The bit guard mask that is used to compare and protect against * The bit guard mask that is used to compare and protect against
* styling changes when any template style/class bindings update * styling changes when and styling bindings update
*/ */
templateBitMask: number; guardMask: number;
/**
* The bit guard mask that is used to compare and protect against
* styling changes when any host style/class bindings update
*/
hostBindingsBitMask: number;
/** /**
* Whether or not the entry requires sanitization * Whether or not the entry requires sanitization
@ -110,18 +93,18 @@ export interface TStylingTupleSummary {
sanitizationRequired: boolean; sanitizationRequired: boolean;
/** /**
* The default value that will be applied if any bindings are falsy * The default value that will be applied if any bindings are falsy.
*/ */
defaultValue: string|boolean|null; defaultValue: string|boolean|null;
/** /**
* All bindingIndex sources that have been registered for this style * All bindingIndex sources that have been registered for this style.
*/ */
sources: (number|null|string)[]; sources: (number|null|string)[];
} }
/** /**
* Instantiates and attaches an instance of `TStylingContextDebug` to the provided context * Instantiates and attaches an instance of `TStylingContextDebug` to the provided context.
*/ */
export function attachStylingDebugObject(context: TStylingContext) { export function attachStylingDebugObject(context: TStylingContext) {
const debug = new TStylingContextDebug(context); const debug = new TStylingContextDebug(context);
@ -138,8 +121,7 @@ export function attachStylingDebugObject(context: TStylingContext) {
class TStylingContextDebug { class TStylingContextDebug {
constructor(public readonly context: TStylingContext) {} constructor(public readonly context: TStylingContext) {}
get isTemplateLocked() { return isContextLocked(this.context, true); } get isLocked() { return isContextLocked(this.context); }
get isHostBindingsLocked() { return isContextLocked(this.context, false); }
/** /**
* Returns a detailed summary of each styling entry in the context. * Returns a detailed summary of each styling entry in the context.
@ -148,36 +130,30 @@ class TStylingContextDebug {
*/ */
get entries(): {[prop: string]: TStylingTupleSummary} { get entries(): {[prop: string]: TStylingTupleSummary} {
const context = this.context; const context = this.context;
const totalColumns = getValuesCount(context);
const entries: {[prop: string]: TStylingTupleSummary} = {}; const entries: {[prop: string]: TStylingTupleSummary} = {};
const start = getPropValuesStartPosition(context); const start = TStylingContextIndex.MapBindingsPosition;
let i = start; let i = start;
while (i < context.length) { while (i < context.length) {
const valuesCount = getValuesCount(context, i);
// the context may contain placeholder values which are populated ahead of time,
// but contain no actual binding values. In this situation there is no point in
// classifying this as an "entry" since no real data is stored here yet.
if (valuesCount) {
const prop = getProp(context, i); const prop = getProp(context, i);
const templateBitMask = getGuardMask(context, i, false); const guardMask = getGuardMask(context, i);
const hostBindingsBitMask = getGuardMask(context, i, true);
const defaultValue = getDefaultValue(context, i); const defaultValue = getDefaultValue(context, i);
const sanitizationRequired = isSanitizationRequired(context, i); const sanitizationRequired = isSanitizationRequired(context, i);
const bindingsStartPosition = i + TStylingContextIndex.BindingsStartOffset; const bindingsStartPosition = i + TStylingContextIndex.BindingsStartOffset;
const sources: (number | string | null)[] = []; const sources: (number | string | null)[] = [];
for (let j = 0; j < valuesCount; j++) {
for (let j = 0; j < totalColumns; j++) { sources.push(context[bindingsStartPosition + j] as number | string | null);
const bindingIndex = context[bindingsStartPosition + j] as number | string | null;
if (bindingIndex !== 0) {
sources.push(bindingIndex);
}
} }
entries[prop] = { entries[prop] = {prop, guardMask, sanitizationRequired, valuesCount, defaultValue, sources};
prop, }
templateBitMask,
hostBindingsBitMask,
sanitizationRequired,
valuesCount: sources.length, defaultValue, sources,
};
i += TStylingContextIndex.BindingsStartOffset + totalColumns; i += TStylingContextIndex.BindingsStartOffset + valuesCount;
} }
return entries; return entries;
} }
@ -215,29 +191,6 @@ export class NodeStylingDebug implements DebugStyling {
return entries; return entries;
} }
get config() {
const hasMapBindings = hasConfig(this.context, TStylingConfig.HasMapBindings);
const hasPropBindings = hasConfig(this.context, TStylingConfig.HasPropBindings);
const hasCollisions = hasConfig(this.context, TStylingConfig.HasCollisions);
const hasTemplateBindings = hasConfig(this.context, TStylingConfig.HasTemplateBindings);
const hasHostBindings = hasConfig(this.context, TStylingConfig.HasHostBindings);
const templateBindingsLocked = hasConfig(this.context, TStylingConfig.TemplateBindingsLocked);
const hostBindingsLocked = hasConfig(this.context, TStylingConfig.HostBindingsLocked);
const allowDirectStyling =
_allowDirectStyling(this.context, false) || _allowDirectStyling(this.context, true);
return {
hasMapBindings, //
hasPropBindings, //
hasCollisions, //
hasTemplateBindings, //
hasHostBindings, //
templateBindingsLocked, //
hostBindingsLocked, //
allowDirectStyling, //
};
}
/** /**
* Returns a key/value map of all the styles/classes that were last applied to the element. * Returns a key/value map of all the styles/classes that were last applied to the element.
*/ */
@ -252,23 +205,16 @@ export class NodeStylingDebug implements DebugStyling {
// element is only used when the styling algorithm attempts to // element is only used when the styling algorithm attempts to
// style the value (and we mock out the stylingApplyFn anyway). // style the value (and we mock out the stylingApplyFn anyway).
const mockElement = {} as any; const mockElement = {} as any;
const hasMaps = hasConfig(this.context, TStylingConfig.HasMapBindings); const hasMaps = getValuesCount(this.context, TStylingContextIndex.MapBindingsPosition) > 0;
if (hasMaps) { if (hasMaps) {
activateStylingMapFeature(); activateStylingMapFeature();
} }
const mapFn: ApplyStylingFn = const mapFn: ApplyStylingFn =
(renderer: any, element: RElement, prop: string, value: string | null, (renderer: any, element: RElement, prop: string, value: string | null,
bindingIndex?: number | null) => fn(prop, value, bindingIndex || null); bindingIndex?: number | null) => { fn(prop, value, bindingIndex || null); };
const sanitizer = this._isClassBased ? null : (this._sanitizer || getCurrentStyleSanitizer()); const sanitizer = this._isClassBased ? null : (this._sanitizer || getCurrentStyleSanitizer());
applyStyling(this.context, null, mockElement, this._data, true, mapFn, sanitizer);
// run the template bindings
applyStylingViaContext(
this.context, null, mockElement, this._data, true, mapFn, sanitizer, false);
// and also the host bindings
applyStylingViaContext(
this.context, null, mockElement, this._data, true, mapFn, sanitizer, true);
} }
} }

View File

@ -5,35 +5,12 @@
* 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 {unwrapSafeValue} from '../../sanitization/bypass';
import {TNode, TNodeFlags} from '../interfaces/node'; import {TNode, TNodeFlags} from '../interfaces/node';
import {NO_CHANGE} from '../tokens'; import {NO_CHANGE} from '../tokens';
import {StylingMapArray, StylingMapArrayIndex, TStylingConfigFlags, TStylingContext, TStylingContextIndex, TStylingContextPropConfigFlags} from './interfaces';
import {LStylingData, StylingMapArray, StylingMapArrayIndex, TStylingConfig, TStylingContext, TStylingContextIndex, TStylingContextPropConfigFlags} from './interfaces'; const MAP_BASED_ENTRY_PROP_NAME = '--MAP--';
const TEMPLATE_DIRECTIVE_INDEX = 0;
export const MAP_BASED_ENTRY_PROP_NAME = '[MAP]';
export const TEMPLATE_DIRECTIVE_INDEX = 0;
/**
* Default fallback value for a styling binding.
*
* A value of `null` is used here which signals to the styling algorithm that
* the styling value is not present. This way if there are no other values
* detected then it will be removed once the style/class property is dirty and
* diffed within the styling algorithm present in `flushStyling`.
*/
export const DEFAULT_BINDING_VALUE = null;
export const DEFAULT_BINDING_INDEX = 0;
const DEFAULT_TOTAL_SOURCES = 1;
// The first bit value reflects a map-based binding value's bit.
// The reason why it's always activated for every entry in the map
// is so that if any map-binding values update then all other prop
// based bindings will pass the guard check automatically without
// any extra code or flags.
export const DEFAULT_GUARD_MASK_VALUE = 0b1;
/** /**
* Creates a new instance of the `TStylingContext`. * Creates a new instance of the `TStylingContext`.
@ -44,83 +21,84 @@ export const DEFAULT_GUARD_MASK_VALUE = 0b1;
* `TStylingContext` with the initial values (see `interfaces.ts` for more info). * `TStylingContext` with the initial values (see `interfaces.ts` for more info).
*/ */
export function allocTStylingContext(initialStyling?: StylingMapArray | null): TStylingContext { export function allocTStylingContext(initialStyling?: StylingMapArray | null): TStylingContext {
initialStyling = initialStyling || allocStylingMapArray(); // because map-based bindings deal with a dynamic set of values, there
// is no way to know ahead of time whether or not sanitization is required.
// For this reason the configuration will always mark sanitization as active
// (this means that when map-based values are applied then sanitization will
// be checked against each property).
const mapBasedConfig = TStylingContextPropConfigFlags.SanitizationRequired;
return [ return [
TStylingConfig.Initial, // 1) config for the styling context initialStyling || [''], // empty initial-styling map value
DEFAULT_TOTAL_SOURCES, // 2) total amount of styling sources (template, directives, etc...) TStylingConfigFlags.Initial,
initialStyling, // 3) initial styling values TEMPLATE_DIRECTIVE_INDEX,
mapBasedConfig,
0,
MAP_BASED_ENTRY_PROP_NAME,
]; ];
} }
export function allocStylingMapArray(): StylingMapArray { /**
return ['']; * Sets the provided directive as the last directive index in the provided `TStylingContext`.
*
* Styling in Angular can be applied from the template as well as multiple sources of
* host bindings. This means that each binding function (the template function or the
* hostBindings functions) will generate styling instructions as well as a styling
* apply function (i.e. `stylingApply()`). Because host bindings functions and the
* template function are independent from one another this means that the styling apply
* function will be called multiple times. By tracking the last directive index (which
* is what happens in this function) the styling algorithm knows exactly when to flush
* styling (which is when the last styling apply function is executed).
*/
export function updateLastDirectiveIndex(
context: TStylingContext, lastDirectiveIndex: number): void {
if (lastDirectiveIndex === TEMPLATE_DIRECTIVE_INDEX) {
const currentValue = context[TStylingContextIndex.LastDirectiveIndexPosition];
if (currentValue > TEMPLATE_DIRECTIVE_INDEX) {
// This means that a directive or two contained a host bindings function, but
// now the template function also contains styling. When this combination of sources
// comes up then we need to tell the context to store the state between updates
// (because host bindings evaluation happens after template binding evaluation).
markContextToPersistState(context);
}
} else {
context[TStylingContextIndex.LastDirectiveIndexPosition] = lastDirectiveIndex;
}
} }
export function getConfig(context: TStylingContext) { function getConfig(context: TStylingContext) {
return context[TStylingContextIndex.ConfigPosition]; return context[TStylingContextIndex.ConfigPosition];
} }
export function hasConfig(context: TStylingContext, flag: TStylingConfig) { export function setConfig(context: TStylingContext, value: number) {
return (getConfig(context) & flag) !== 0;
}
/**
* Determines whether or not to apply styles/classes directly or via context resolution.
*
* There are three cases that are matched here:
* 1. context is locked for template or host bindings (depending on `hostBindingsMode`)
* 2. There are no collisions (i.e. properties with more than one binding)
* 3. There are only "prop" or "map" bindings present, but not both
*/
export function allowDirectStyling(context: TStylingContext, hostBindingsMode: boolean): boolean {
const config = getConfig(context);
return ((config & getLockedConfig(hostBindingsMode)) !== 0) &&
((config & TStylingConfig.HasCollisions) === 0) &&
((config & TStylingConfig.HasPropAndMapBindings) !== TStylingConfig.HasPropAndMapBindings);
}
export function setConfig(context: TStylingContext, value: TStylingConfig): void {
context[TStylingContextIndex.ConfigPosition] = value; context[TStylingContextIndex.ConfigPosition] = value;
} }
export function patchConfig(context: TStylingContext, flag: TStylingConfig): void { export function getProp(context: TStylingContext, index: number) {
context[TStylingContextIndex.ConfigPosition] |= flag;
}
export function getProp(context: TStylingContext, index: number): string {
return context[index + TStylingContextIndex.PropOffset] as string; return context[index + TStylingContextIndex.PropOffset] as string;
} }
function getPropConfig(context: TStylingContext, index: number): number { function getPropConfig(context: TStylingContext, index: number): number {
return (context[index + TStylingContextIndex.ConfigOffset] as number) & return (context[index + TStylingContextIndex.ConfigAndGuardOffset] as number) &
TStylingContextPropConfigFlags.Mask; TStylingContextPropConfigFlags.Mask;
} }
export function isSanitizationRequired(context: TStylingContext, index: number): boolean { export function isSanitizationRequired(context: TStylingContext, index: number) {
return (getPropConfig(context, index) & TStylingContextPropConfigFlags.SanitizationRequired) !== return (getPropConfig(context, index) & TStylingContextPropConfigFlags.SanitizationRequired) > 0;
0;
} }
export function getGuardMask( export function getGuardMask(context: TStylingContext, index: number) {
context: TStylingContext, index: number, isHostBinding: boolean): number { const configGuardValue = context[index + TStylingContextIndex.ConfigAndGuardOffset] as number;
const position = index + (isHostBinding ? TStylingContextIndex.HostBindingsBitGuardOffset : return configGuardValue >> TStylingContextPropConfigFlags.TotalBits;
TStylingContextIndex.TemplateBitGuardOffset);
return context[position] as number;
} }
export function setGuardMask( export function setGuardMask(context: TStylingContext, index: number, maskValue: number) {
context: TStylingContext, index: number, maskValue: number, isHostBinding: boolean) { const config = getPropConfig(context, index);
const position = index + (isHostBinding ? TStylingContextIndex.HostBindingsBitGuardOffset : const guardMask = maskValue << TStylingContextPropConfigFlags.TotalBits;
TStylingContextIndex.TemplateBitGuardOffset); context[index + TStylingContextIndex.ConfigAndGuardOffset] = config | guardMask;
context[position] = maskValue;
} }
export function getValuesCount(context: TStylingContext): number { export function getValuesCount(context: TStylingContext, index: number) {
return getTotalSources(context) + 1; return context[index + TStylingContextIndex.ValuesCountOffset] as number;
}
export function getTotalSources(context: TStylingContext): number {
return context[TStylingContextIndex.TotalSourcesPosition];
} }
export function getBindingValue(context: TStylingContext, index: number, offset: number) { export function getBindingValue(context: TStylingContext, index: number, offset: number) {
@ -128,44 +106,39 @@ export function getBindingValue(context: TStylingContext, index: number, offset:
} }
export function getDefaultValue(context: TStylingContext, index: number): string|boolean|null { export function getDefaultValue(context: TStylingContext, index: number): string|boolean|null {
return context[index + TStylingContextIndex.BindingsStartOffset + getTotalSources(context)] as const valuesCount = getValuesCount(context, index);
string | return context[index + TStylingContextIndex.BindingsStartOffset + valuesCount - 1] as string |
boolean | null; boolean | null;
} }
export function setDefaultValue( /**
context: TStylingContext, index: number, value: string | boolean | null) { * Temporary function which determines whether or not a context is
return context[index + TStylingContextIndex.BindingsStartOffset + getTotalSources(context)] = * allowed to be flushed based on the provided directive index.
value; */
export function allowStylingFlush(context: TStylingContext | null, index: number) {
return (context && index === context[TStylingContextIndex.LastDirectiveIndexPosition]) ? true :
false;
} }
export function setValue(data: LStylingData, bindingIndex: number, value: any) { export function lockContext(context: TStylingContext) {
data[bindingIndex] = value; setConfig(context, getConfig(context) | TStylingConfigFlags.Locked);
} }
export function getValue<T = any>(data: LStylingData, bindingIndex: number): T|null { export function isContextLocked(context: TStylingContext): boolean {
return bindingIndex > 0 ? data[bindingIndex] as T : null; return (getConfig(context) & TStylingConfigFlags.Locked) > 0;
} }
export function lockContext(context: TStylingContext, hostBindingsMode: boolean): void { export function stateIsPersisted(context: TStylingContext): boolean {
patchConfig(context, getLockedConfig(hostBindingsMode)); return (getConfig(context) & TStylingConfigFlags.PersistStateValues) > 0;
} }
export function isContextLocked(context: TStylingContext, hostBindingsMode: boolean): boolean { export function markContextToPersistState(context: TStylingContext) {
return hasConfig(context, getLockedConfig(hostBindingsMode)); setConfig(context, getConfig(context) | TStylingConfigFlags.PersistStateValues);
}
export function getLockedConfig(hostBindingsMode: boolean) {
return hostBindingsMode ? TStylingConfig.HostBindingsLocked :
TStylingConfig.TemplateBindingsLocked;
} }
export function getPropValuesStartPosition(context: TStylingContext) { export function getPropValuesStartPosition(context: TStylingContext) {
let startPosition = TStylingContextIndex.ValuesStartPosition; return TStylingContextIndex.MapBindingsBindingsStartPosition +
if (hasConfig(context, TStylingConfig.HasMapBindings)) { context[TStylingContextIndex.MapBindingsValuesCountPosition];
startPosition += TStylingContextIndex.BindingsStartOffset + getValuesCount(context);
}
return startPosition;
} }
export function isMapBased(prop: string) { export function isMapBased(prop: string) {
@ -224,21 +197,15 @@ export function getStylingMapArray(value: TStylingContext | StylingMapArray | nu
StylingMapArray|null { StylingMapArray|null {
return isStylingContext(value) ? return isStylingContext(value) ?
(value as TStylingContext)[TStylingContextIndex.InitialStylingValuePosition] : (value as TStylingContext)[TStylingContextIndex.InitialStylingValuePosition] :
value as StylingMapArray; value;
} }
export function isStylingContext(value: TStylingContext | StylingMapArray | null): boolean { export function isStylingContext(value: TStylingContext | StylingMapArray | null): boolean {
// the StylingMapArray is in the format of [initial, prop, string, prop, string]
// and this is the defining value to distinguish between arrays
return Array.isArray(value) && value.length >= TStylingContextIndex.ValuesStartPosition &&
typeof value[1] !== 'string';
}
export function isStylingMapArray(value: TStylingContext | StylingMapArray | null): boolean {
// the StylingMapArray is in the format of [initial, prop, string, prop, string] // the StylingMapArray is in the format of [initial, prop, string, prop, string]
// and this is the defining value to distinguish between arrays // and this is the defining value to distinguish between arrays
return Array.isArray(value) && return Array.isArray(value) &&
(typeof(value as StylingMapArray)[StylingMapArrayIndex.ValuesStartPosition] === 'string'); value.length >= TStylingContextIndex.MapBindingsBindingsStartPosition &&
typeof value[1] !== 'string';
} }
export function getInitialStylingValue(context: TStylingContext | StylingMapArray | null): string { export function getInitialStylingValue(context: TStylingContext | StylingMapArray | null): string {
@ -258,13 +225,6 @@ export function getMapProp(map: StylingMapArray, index: number): string {
return map[index + StylingMapArrayIndex.PropOffset] as string; return map[index + StylingMapArrayIndex.PropOffset] as string;
} }
const MAP_DIRTY_VALUE =
typeof ngDevMode !== 'undefined' && ngDevMode ? {} : {MAP_DIRTY_VALUE: true};
export function setMapAsDirty(map: StylingMapArray): void {
map[StylingMapArrayIndex.RawValuePosition] = MAP_DIRTY_VALUE;
}
export function setMapValue( export function setMapValue(
map: StylingMapArray, index: number, value: string | boolean | null): void { map: StylingMapArray, index: number, value: string | boolean | null): void {
map[index + StylingMapArrayIndex.ValueOffset] = value; map[index + StylingMapArrayIndex.ValueOffset] = value;
@ -293,130 +253,3 @@ export function forceStylesAsString(styles: {[key: string]: any} | null | undefi
} }
return str; return str;
} }
export function isHostStylingActive(directiveOrSourceId: number): boolean {
return directiveOrSourceId !== TEMPLATE_DIRECTIVE_INDEX;
}
/**
* Converts the provided styling map array into a string.
*
* Classes => `one two three`
* Styles => `prop:value; prop2:value2`
*/
export function stylingMapToString(map: StylingMapArray, isClassBased: boolean): string {
let str = '';
for (let i = StylingMapArrayIndex.ValuesStartPosition; i < map.length;
i += StylingMapArrayIndex.TupleSize) {
const prop = getMapProp(map, i);
const value = getMapValue(map, i) as string;
const attrValue = concatString(prop, isClassBased ? '' : value, ':');
str = concatString(str, attrValue, isClassBased ? ' ' : '; ');
}
return str;
}
/**
* Converts the provided styling map array into a key value map.
*/
export function stylingMapToStringMap(map: StylingMapArray | null): {[key: string]: any} {
let stringMap: {[key: string]: any} = {};
if (map) {
for (let i = StylingMapArrayIndex.ValuesStartPosition; i < map.length;
i += StylingMapArrayIndex.TupleSize) {
const prop = getMapProp(map, i);
const value = getMapValue(map, i) as string;
stringMap[prop] = value;
}
}
return stringMap;
}
/**
* Inserts the provided item into the provided styling array at the right spot.
*
* The `StylingMapArray` type is a sorted key/value array of entries. This means
* that when a new entry is inserted it must be placed at the right spot in the
* array. This function figures out exactly where to place it.
*/
export function addItemToStylingMap(
stylingMapArr: StylingMapArray, prop: string, value: string | boolean | null,
allowOverwrite?: boolean) {
for (let j = StylingMapArrayIndex.ValuesStartPosition; j < stylingMapArr.length;
j += StylingMapArrayIndex.TupleSize) {
const propAtIndex = getMapProp(stylingMapArr, j);
if (prop <= propAtIndex) {
let applied = false;
if (propAtIndex === prop) {
const valueAtIndex = stylingMapArr[j];
if (allowOverwrite || !isStylingValueDefined(valueAtIndex)) {
applied = true;
setMapValue(stylingMapArr, j, value);
}
} else {
applied = true;
stylingMapArr.splice(j, 0, prop, value);
}
return applied;
}
}
stylingMapArr.push(prop, value);
return true;
}
/**
* Used to convert a {key:value} map into a `StylingMapArray` array.
*
* This function will either generate a new `StylingMapArray` instance
* or it will patch the provided `newValues` map value into an
* existing `StylingMapArray` value (this only happens if `bindingValue`
* is an instance of `StylingMapArray`).
*
* If a new key/value map is provided with an old `StylingMapArray`
* value then all properties will be overwritten with their new
* values or with `null`. This means that the array will never
* shrink in size (but it will also not be created and thrown
* away whenever the `{key:value}` map entries change).
*/
export function normalizeIntoStylingMap(
bindingValue: null | StylingMapArray,
newValues: {[key: string]: any} | string | null | undefined,
normalizeProps?: boolean): StylingMapArray {
const stylingMapArr: StylingMapArray = Array.isArray(bindingValue) ? bindingValue : [null];
stylingMapArr[StylingMapArrayIndex.RawValuePosition] = newValues || null;
// because the new values may not include all the properties
// that the old ones had, all values are set to `null` before
// the new values are applied. This way, when flushed, the
// styling algorithm knows exactly what style/class values
// to remove from the element (since they are `null`).
for (let j = StylingMapArrayIndex.ValuesStartPosition; j < stylingMapArr.length;
j += StylingMapArrayIndex.TupleSize) {
setMapValue(stylingMapArr, j, null);
}
let props: string[]|null = null;
let map: {[key: string]: any}|undefined|null;
let allValuesTrue = false;
if (typeof newValues === 'string') { // [class] bindings allow string values
if (newValues.length) {
props = newValues.split(/\s+/);
allValuesTrue = true;
}
} else {
props = newValues ? Object.keys(newValues) : null;
map = newValues;
}
if (props) {
for (let i = 0; i < props.length; i++) {
const prop = props[i] as string;
const newProp = normalizeProps ? hyphenate(prop) : prop;
const value = allValuesTrue ? true : map ![prop];
addItemToStylingMap(stylingMapArr, newProp, value, true);
}
}
return stylingMapArr;
}

View File

@ -61,7 +61,9 @@ export interface SafeResourceUrl extends SafeValue {}
abstract class SafeValueImpl implements SafeValue { abstract class SafeValueImpl implements SafeValue {
constructor(public changingThisBreaksApplicationSecurity: string) {} constructor(public changingThisBreaksApplicationSecurity: string) {
// empty
}
abstract getTypeName(): string; abstract getTypeName(): string;
@ -87,10 +89,10 @@ class SafeResourceUrlImpl extends SafeValueImpl implements SafeResourceUrl {
getTypeName() { return BypassType.ResourceUrl; } getTypeName() { return BypassType.ResourceUrl; }
} }
export function unwrapSafeValue(value: string | SafeValue): string { export function unwrapSafeValue(value: SafeValue): string {
return value instanceof SafeValueImpl ? return value instanceof SafeValueImpl ?
(value as SafeValueImpl).changingThisBreaksApplicationSecurity : (value as SafeValueImpl).changingThisBreaksApplicationSecurity :
(value as string); '';
} }

View File

@ -45,6 +45,8 @@ declare global {
flushStyling: number; flushStyling: number;
classesApplied: number; classesApplied: number;
stylesApplied: number; stylesApplied: number;
stylingWritePersistedState: number;
stylingReadPersistedState: number;
} }
} }
@ -85,6 +87,8 @@ export function ngDevModeResetPerfCounters(): NgDevModePerfCounters {
flushStyling: 0, flushStyling: 0,
classesApplied: 0, classesApplied: 0,
stylesApplied: 0, stylesApplied: 0,
stylingWritePersistedState: 0,
stylingReadPersistedState: 0,
}; };
// Make sure to refer to ngDevMode as ['ngDevMode'] for closure. // Make sure to refer to ngDevMode as ['ngDevMode'] for closure.

View File

@ -15,8 +15,6 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
import {onlyInIvy} from '@angular/private/testing'; import {onlyInIvy} from '@angular/private/testing';
describe('new styling integration', () => { describe('new styling integration', () => {
beforeEach(() => resetStylingCounters());
onlyInIvy('ivy resolves styling across directives, components and templates in unison') onlyInIvy('ivy resolves styling across directives, components and templates in unison')
.it('should apply single property styles/classes to the element and default to any static styling values', .it('should apply single property styles/classes to the element and default to any static styling values',
() => { () => {
@ -126,74 +124,6 @@ describe('new styling integration', () => {
expect(element.style.width).toEqual('600px'); expect(element.style.width).toEqual('600px');
}); });
onlyInIvy('ivy resolves styling across directives, components and templates in unison')
.it('should only run stylingFlush once when there are no collisions between styling properties',
() => {
@Directive({selector: '[dir-with-styling]'})
class DirWithStyling {
@HostBinding('style.font-size') public fontSize = '100px';
}
@Component({selector: 'comp-with-styling'})
class CompWithStyling {
@HostBinding('style.width') public width = '900px';
@HostBinding('style.height') public height = '900px';
}
@Component({
template: `
<comp-with-styling
[style.opacity]="opacity"
dir-with-styling>...</comp-with-styling>
`
})
class Cmp {
opacity: string|null = '0.5';
@ViewChild(CompWithStyling, {static: true})
compWithStyling: CompWithStyling|null = null;
@ViewChild(DirWithStyling, {static: true}) dirWithStyling: DirWithStyling|null = null;
}
TestBed.configureTestingModule({declarations: [Cmp, DirWithStyling, CompWithStyling]});
const fixture = TestBed.createComponent(Cmp);
fixture.detectChanges();
const component = fixture.componentInstance;
const element = fixture.nativeElement.querySelector('comp-with-styling');
const node = getDebugNode(element) !;
const styles = node.styles !;
const config = styles.config;
expect(config.hasCollisions).toBeFalsy();
expect(config.hasMapBindings).toBeFalsy();
expect(config.hasPropBindings).toBeTruthy();
expect(config.allowDirectStyling).toBeTruthy();
expect(element.style.opacity).toEqual('0.5');
expect(element.style.width).toEqual('900px');
expect(element.style.height).toEqual('900px');
expect(element.style.fontSize).toEqual('100px');
// once for the template flush and again for the host bindings
expect(ngDevMode !.flushStyling).toEqual(2);
resetStylingCounters();
component.opacity = '0.6';
component.compWithStyling !.height = '100px';
component.compWithStyling !.width = '100px';
component.dirWithStyling !.fontSize = '50px';
fixture.detectChanges();
expect(element.style.opacity).toEqual('0.6');
expect(element.style.width).toEqual('100px');
expect(element.style.height).toEqual('100px');
expect(element.style.fontSize).toEqual('50px');
// there is no need to flush styling since the styles are applied directly
expect(ngDevMode !.flushStyling).toEqual(0);
});
onlyInIvy('ivy resolves styling across directives, components and templates in unison') onlyInIvy('ivy resolves styling across directives, components and templates in unison')
.it('should combine all styling across the template, directive and component host bindings', .it('should combine all styling across the template, directive and component host bindings',
() => { () => {
@ -310,7 +240,6 @@ describe('new styling integration', () => {
fixture.componentInstance.w3 = null; fixture.componentInstance.w3 = null;
fixture.detectChanges(); fixture.detectChanges();
expect(styles.values).toEqual({ expect(styles.values).toEqual({
'width': '200px', 'width': '200px',
}); });
@ -380,22 +309,16 @@ describe('new styling integration', () => {
.it('should apply map-based style and class entries', () => { .it('should apply map-based style and class entries', () => {
@Component({template: '<div [style]="s" [class]="c"></div>'}) @Component({template: '<div [style]="s" [class]="c"></div>'})
class Cmp { class Cmp {
public c: {[key: string]: any}|null = null; public c !: {[key: string]: any};
updateClasses(classes: string) { updateClasses(prop: string) {
const c = this.c || (this.c = {}); this.c = {...this.c || {}};
Object.keys(this.c).forEach(className => { c[className] = false; }); this.c[prop] = true;
classes.split(/\s+/).forEach(className => { c[className] = true; });
} }
public s: {[key: string]: any}|null = null; public s !: {[key: string]: any};
updateStyles(prop: string, value: string|number|null) { updateStyles(prop: string, value: string|number|null) {
const s = this.s || (this.s = {}); this.s = {...this.s || {}};
Object.assign(s, {[prop]: value}); this.s[prop] = value;
}
reset() {
this.s = null;
this.c = null;
} }
} }
@ -409,47 +332,22 @@ describe('new styling integration', () => {
const element = fixture.nativeElement.querySelector('div'); const element = fixture.nativeElement.querySelector('div');
const node = getDebugNode(element) !; const node = getDebugNode(element) !;
let styles = node.styles !; const styles = node.styles !;
let classes = node.classes !; const classes = node.classes !;
let stylesSummary = styles.summary; const stylesSummary = styles.summary;
let widthSummary = stylesSummary['width']; const widthSummary = stylesSummary['width'];
expect(widthSummary.prop).toEqual('width'); expect(widthSummary.prop).toEqual('width');
expect(widthSummary.value).toEqual('100px'); expect(widthSummary.value).toEqual('100px');
let heightSummary = stylesSummary['height']; const heightSummary = stylesSummary['height'];
expect(heightSummary.prop).toEqual('height'); expect(heightSummary.prop).toEqual('height');
expect(heightSummary.value).toEqual('200px'); expect(heightSummary.value).toEqual('200px');
let classesSummary = classes.summary; const classesSummary = classes.summary;
let abcSummary = classesSummary['abc']; const abcSummary = classesSummary['abc'];
expect(abcSummary.prop).toEqual('abc'); expect(abcSummary.prop).toEqual('abc');
expect(abcSummary.value).toBeTruthy(); expect(abcSummary.value as any).toEqual(true);
comp.reset();
comp.updateStyles('width', '500px');
comp.updateStyles('height', null);
comp.updateClasses('def');
fixture.detectChanges();
styles = node.styles !;
classes = node.classes !;
stylesSummary = styles.summary;
widthSummary = stylesSummary['width'];
expect(widthSummary.value).toEqual('500px');
heightSummary = stylesSummary['height'];
expect(heightSummary.value).toEqual(null);
classesSummary = classes.summary;
abcSummary = classesSummary['abc'];
expect(abcSummary.prop).toEqual('abc');
expect(abcSummary.value).toBeFalsy();
let defSummary = classesSummary['def'];
expect(defSummary.prop).toEqual('def');
expect(defSummary.value).toBeTruthy();
}); });
onlyInIvy('ivy resolves styling across directives, components and templates in unison') onlyInIvy('ivy resolves styling across directives, components and templates in unison')
@ -487,7 +385,6 @@ describe('new styling integration', () => {
const node = getDebugNode(element) !; const node = getDebugNode(element) !;
const styles = node.styles !; const styles = node.styles !;
expect(styles.values).toEqual({ expect(styles.values).toEqual({
'width': '555px', 'width': '555px',
'color': 'red', 'color': 'red',
@ -612,8 +509,7 @@ describe('new styling integration', () => {
resetStylingCounters(); resetStylingCounters();
fixture.detectChanges(); fixture.detectChanges();
// the width is applied both in TEMPLATE and in HOST_BINDINGS mode assertStyleCounters(1, 0);
assertStyleCounters(2, 0);
assertStyle(element, 'width', '999px'); assertStyle(element, 'width', '999px');
assertStyle(element, 'height', '123px'); assertStyle(element, 'height', '123px');
@ -621,8 +517,8 @@ describe('new styling integration', () => {
resetStylingCounters(); resetStylingCounters();
fixture.detectChanges(); fixture.detectChanges();
// the width is only applied once // both are applied because the map was altered
assertStyleCounters(1, 0); assertStyleCounters(2, 0);
assertStyle(element, 'width', '0px'); assertStyle(element, 'width', '0px');
assertStyle(element, 'height', '123px'); assertStyle(element, 'height', '123px');
@ -630,8 +526,8 @@ describe('new styling integration', () => {
resetStylingCounters(); resetStylingCounters();
fixture.detectChanges(); fixture.detectChanges();
// only the width and color have changed // all three are applied because the map was altered
assertStyleCounters(2, 0); assertStyleCounters(3, 0);
assertStyle(element, 'width', '1000px'); assertStyle(element, 'width', '1000px');
assertStyle(element, 'height', '123px'); assertStyle(element, 'height', '123px');
assertStyle(element, 'color', 'red'); assertStyle(element, 'color', 'red');
@ -640,9 +536,7 @@ describe('new styling integration', () => {
resetStylingCounters(); resetStylingCounters();
fixture.detectChanges(); fixture.detectChanges();
// height gets applied twice and all other assertStyleCounters(1, 0);
// values get applied
assertStyleCounters(4, 0);
assertStyle(element, 'width', '1000px'); assertStyle(element, 'width', '1000px');
assertStyle(element, 'height', '1000px'); assertStyle(element, 'height', '1000px');
assertStyle(element, 'color', 'red'); assertStyle(element, 'color', 'red');
@ -651,7 +545,8 @@ describe('new styling integration', () => {
resetStylingCounters(); resetStylingCounters();
fixture.detectChanges(); fixture.detectChanges();
assertStyleCounters(5, 0); // all four are applied because the map was altered
assertStyleCounters(4, 0);
assertStyle(element, 'width', '2000px'); assertStyle(element, 'width', '2000px');
assertStyle(element, 'height', '1000px'); assertStyle(element, 'height', '1000px');
assertStyle(element, 'color', 'blue'); assertStyle(element, 'color', 'blue');
@ -662,13 +557,62 @@ describe('new styling integration', () => {
fixture.detectChanges(); fixture.detectChanges();
// all four are applied because the map was altered // all four are applied because the map was altered
assertStyleCounters(4, 1); assertStyleCounters(3, 1);
assertStyle(element, 'width', '2000px'); assertStyle(element, 'width', '2000px');
assertStyle(element, 'height', '1000px'); assertStyle(element, 'height', '1000px');
assertStyle(element, 'color', 'blue'); assertStyle(element, 'color', 'blue');
assertStyle(element, 'opacity', ''); assertStyle(element, 'opacity', '');
}); });
onlyInIvy('ivy resolves styling across directives, components and templates in unison')
.it('should only persist state values in a local map if template AND host styling is used together',
() => {
@Directive({selector: '[dir-that-sets-styling]'})
class Dir {
@HostBinding('style.width') w = '100px';
}
@Component({
template: `
<div #a dir-that-sets-styling></div>
<div #b [style.width]="w"></div>
<div #c dir-that-sets-styling [style.width]="w"></div>
`
})
class Cmp {
w = '200px';
@ViewChild('a', {read: Dir, static: true}) a !: Dir;
@ViewChild('c', {read: Dir, static: true}) c !: Dir;
}
TestBed.configureTestingModule({declarations: [Cmp, Dir]});
const fixture = TestBed.createComponent(Cmp);
const comp = fixture.componentInstance;
fixture.detectChanges();
resetStylingCounters();
comp.a.w = '999px';
comp.w = '999px';
comp.c.w = '999px';
fixture.detectChanges();
expect(ngDevMode !.stylingWritePersistedState).toEqual(totalUpdates(1));
comp.a.w = '888px';
fixture.detectChanges();
expect(ngDevMode !.stylingWritePersistedState).toEqual(totalUpdates(2));
comp.c.w = '777px';
fixture.detectChanges();
expect(ngDevMode !.stylingWritePersistedState).toEqual(totalUpdates(3));
function totalUpdates(value: number) {
// this is doubled because detectChanges is run twice to
// see to check for checkNoChanges
return value * 2;
}
});
onlyInIvy('only ivy has style/class bindings debugging support') onlyInIvy('only ivy has style/class bindings debugging support')
.it('should sanitize style values before writing them', () => { .it('should sanitize style values before writing them', () => {
@Component({ @Component({
@ -966,6 +910,7 @@ describe('new styling integration', () => {
TestBed.configureTestingModule({declarations: [Cmp, DirOne, DirTwo]}); TestBed.configureTestingModule({declarations: [Cmp, DirOne, DirTwo]});
const fixture = TestBed.createComponent(Cmp); const fixture = TestBed.createComponent(Cmp);
const comp = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
const dirOne = fixture.nativeElement.querySelector('dir-one'); const dirOne = fixture.nativeElement.querySelector('dir-one');

View File

@ -628,6 +628,7 @@ describe('styling', () => {
expect(childDir.parent).toBeAnInstanceOf(TestDir); expect(childDir.parent).toBeAnInstanceOf(TestDir);
expect(testDirDiv.classList).not.toContain('with-button'); expect(testDirDiv.classList).not.toContain('with-button');
expect(fixture.debugElement.nativeElement.textContent).toContain('Hello'); expect(fixture.debugElement.nativeElement.textContent).toContain('Hello');
}); });
}); });

View File

@ -155,9 +155,6 @@
{ {
"name": "_currentNamespace" "name": "_currentNamespace"
}, },
{
"name": "_elementExitFn"
},
{ {
"name": "_global" "name": "_global"
}, },
@ -168,7 +165,7 @@
"name": "_selectedIndex" "name": "_selectedIndex"
}, },
{ {
"name": "_state" "name": "_stateStorage"
}, },
{ {
"name": "addComponentLogic" "name": "addComponentLogic"
@ -179,9 +176,6 @@
{ {
"name": "addToViewTree" "name": "addToViewTree"
}, },
{
"name": "allocStylingMapArray"
},
{ {
"name": "appendChild" "name": "appendChild"
}, },
@ -260,9 +254,6 @@
{ {
"name": "executeContentQueries" "name": "executeContentQueries"
}, },
{
"name": "executeElementExitFn"
},
{ {
"name": "executeInitAndCheckHooks" "name": "executeInitAndCheckHooks"
}, },
@ -404,9 +395,6 @@
{ {
"name": "getStylingMapArray" "name": "getStylingMapArray"
}, },
{
"name": "hasActiveElementFlag"
},
{ {
"name": "hasClassInput" "name": "hasClassInput"
}, },
@ -584,6 +572,9 @@
{ {
"name": "renderView" "name": "renderView"
}, },
{
"name": "resetAllStylingState"
},
{ {
"name": "resetComponentState" "name": "resetComponentState"
}, },

View File

@ -134,9 +134,6 @@
{ {
"name": "__window" "name": "__window"
}, },
{
"name": "_elementExitFn"
},
{ {
"name": "_global" "name": "_global"
}, },
@ -147,7 +144,7 @@
"name": "_selectedIndex" "name": "_selectedIndex"
}, },
{ {
"name": "_state" "name": "_stateStorage"
}, },
{ {
"name": "addToViewTree" "name": "addToViewTree"
@ -212,9 +209,6 @@
{ {
"name": "executeCheckHooks" "name": "executeCheckHooks"
}, },
{
"name": "executeElementExitFn"
},
{ {
"name": "executeInitAndCheckHooks" "name": "executeInitAndCheckHooks"
}, },
@ -320,9 +314,6 @@
{ {
"name": "getSelectedIndex" "name": "getSelectedIndex"
}, },
{
"name": "hasActiveElementFlag"
},
{ {
"name": "hasParentInjector" "name": "hasParentInjector"
}, },
@ -428,6 +419,9 @@
{ {
"name": "renderView" "name": "renderView"
}, },
{
"name": "resetAllStylingState"
},
{ {
"name": "resetComponentState" "name": "resetComponentState"
}, },

View File

@ -44,9 +44,6 @@
{ {
"name": "DECLARATION_VIEW" "name": "DECLARATION_VIEW"
}, },
{
"name": "DEFAULT_BINDING_INDEX"
},
{ {
"name": "DEFAULT_BINDING_VALUE" "name": "DEFAULT_BINDING_VALUE"
}, },
@ -54,7 +51,7 @@
"name": "DEFAULT_GUARD_MASK_VALUE" "name": "DEFAULT_GUARD_MASK_VALUE"
}, },
{ {
"name": "DEFAULT_TOTAL_SOURCES" "name": "DEFAULT_SIZE_VALUE"
}, },
{ {
"name": "DefaultIterableDiffer" "name": "DefaultIterableDiffer"
@ -95,9 +92,6 @@
{ {
"name": "HOST" "name": "HOST"
}, },
{
"name": "INDEX_START_VALUE"
},
{ {
"name": "INJECTOR" "name": "INJECTOR"
}, },
@ -117,7 +111,7 @@
"name": "MAP_BASED_ENTRY_PROP_NAME" "name": "MAP_BASED_ENTRY_PROP_NAME"
}, },
{ {
"name": "MAP_DIRTY_VALUE" "name": "MIN_DIRECTIVE_ID"
}, },
{ {
"name": "MONKEY_PATCH_KEY_NAME" "name": "MONKEY_PATCH_KEY_NAME"
@ -221,6 +215,9 @@
{ {
"name": "STYLING_INDEX_FOR_MAP_BINDING" "name": "STYLING_INDEX_FOR_MAP_BINDING"
}, },
{
"name": "STYLING_INDEX_START_VALUE"
},
{ {
"name": "SWITCH_ELEMENT_REF_FACTORY" "name": "SWITCH_ELEMENT_REF_FACTORY"
}, },
@ -230,9 +227,6 @@
{ {
"name": "SWITCH_VIEW_CONTAINER_REF_FACTORY" "name": "SWITCH_VIEW_CONTAINER_REF_FACTORY"
}, },
{
"name": "SafeValueImpl"
},
{ {
"name": "SkipSelf" "name": "SkipSelf"
}, },
@ -404,9 +398,6 @@
{ {
"name": "_devMode" "name": "_devMode"
}, },
{
"name": "_elementExitFn"
},
{ {
"name": "_global" "name": "_global"
}, },
@ -417,7 +408,16 @@
"name": "_selectedIndex" "name": "_selectedIndex"
}, },
{ {
"name": "_state" "name": "_stateStorage"
},
{
"name": "_stylingElement"
},
{
"name": "_stylingProp"
},
{
"name": "_stylingState"
}, },
{ {
"name": "_symbolIterator" "name": "_symbolIterator"
@ -425,6 +425,12 @@
{ {
"name": "activeDirectiveId" "name": "activeDirectiveId"
}, },
{
"name": "activeDirectiveSuperClassDepthPosition"
},
{
"name": "activeDirectiveSuperClassHeight"
},
{ {
"name": "addBindingIntoContext" "name": "addBindingIntoContext"
}, },
@ -434,9 +440,6 @@
{ {
"name": "addItemToStylingMap" "name": "addItemToStylingMap"
}, },
{
"name": "addNewSourceColumn"
},
{ {
"name": "addRemoveViewFromContainer" "name": "addRemoveViewFromContainer"
}, },
@ -446,9 +449,6 @@
{ {
"name": "addToViewTree" "name": "addToViewTree"
}, },
{
"name": "allocStylingMapArray"
},
{ {
"name": "allocTStylingContext" "name": "allocTStylingContext"
}, },
@ -456,7 +456,7 @@
"name": "allocateNewContextEntry" "name": "allocateNewContextEntry"
}, },
{ {
"name": "allowDirectStyling" "name": "allowStylingFlush"
}, },
{ {
"name": "appendChild" "name": "appendChild"
@ -473,15 +473,6 @@
{ {
"name": "applyStyling" "name": "applyStyling"
}, },
{
"name": "applyStylingValue"
},
{
"name": "applyStylingValueDirectly"
},
{
"name": "applyStylingViaContext"
},
{ {
"name": "applyToElementOrContainer" "name": "applyToElementOrContainer"
}, },
@ -536,6 +527,9 @@
{ {
"name": "containerInternal" "name": "containerInternal"
}, },
{
"name": "contextHasUpdates"
},
{ {
"name": "contextLView" "name": "contextLView"
}, },
@ -593,6 +587,18 @@
{ {
"name": "defaultScheduler" "name": "defaultScheduler"
}, },
{
"name": "deferBindingRegistration"
},
{
"name": "deferStylingUpdate"
},
{
"name": "deferredBindingQueue"
},
{
"name": "deleteStylingStateFromStorage"
},
{ {
"name": "destroyLView" "name": "destroyLView"
}, },
@ -626,9 +632,6 @@
{ {
"name": "executeContentQueries" "name": "executeContentQueries"
}, },
{
"name": "executeElementExitFn"
},
{ {
"name": "executeInitAndCheckHooks" "name": "executeInitAndCheckHooks"
}, },
@ -666,10 +669,10 @@
"name": "findExistingListener" "name": "findExistingListener"
}, },
{ {
"name": "findInitialStylingValue" "name": "findViaComponent"
}, },
{ {
"name": "findViaComponent" "name": "flushDeferredBindings"
}, },
{ {
"name": "flushStyling" "name": "flushStyling"
@ -689,6 +692,15 @@
{ {
"name": "getActiveDirectiveId" "name": "getActiveDirectiveId"
}, },
{
"name": "getActiveDirectiveStylingIndex"
},
{
"name": "getActiveDirectiveSuperClassDepth"
},
{
"name": "getActiveDirectiveSuperClassHeight"
},
{ {
"name": "getBeforeNodeForView" "name": "getBeforeNodeForView"
}, },
@ -737,9 +749,6 @@
{ {
"name": "getDebugContext" "name": "getDebugContext"
}, },
{
"name": "getDefaultValue"
},
{ {
"name": "getDirectiveDef" "name": "getDirectiveDef"
}, },
@ -779,9 +788,6 @@
{ {
"name": "getLViewParent" "name": "getLViewParent"
}, },
{
"name": "getLockedConfig"
},
{ {
"name": "getMapProp" "name": "getMapProp"
}, },
@ -890,33 +896,21 @@
{ {
"name": "getTViewCleanup" "name": "getTViewCleanup"
}, },
{
"name": "getTotalSources"
},
{ {
"name": "getTypeName" "name": "getTypeName"
}, },
{ {
"name": "getTypeNameForDebugging" "name": "getTypeNameForDebugging"
}, },
{
"name": "getValue"
},
{ {
"name": "getValuesCount" "name": "getValuesCount"
}, },
{ {
"name": "handleError" "name": "handleError"
}, },
{
"name": "hasActiveElementFlag"
},
{ {
"name": "hasClassInput" "name": "hasClassInput"
}, },
{
"name": "hasConfig"
},
{ {
"name": "hasDirectives" "name": "hasDirectives"
}, },
@ -1022,12 +1016,6 @@
{ {
"name": "isForwardRef" "name": "isForwardRef"
}, },
{
"name": "isHostStyling"
},
{
"name": "isHostStylingActive"
},
{ {
"name": "isJsObject" "name": "isJsObject"
}, },
@ -1100,21 +1088,24 @@
{ {
"name": "markAsComponentHost" "name": "markAsComponentHost"
}, },
{
"name": "markContextToPersistState"
},
{ {
"name": "markDirty" "name": "markDirty"
}, },
{ {
"name": "markDirtyIfOnPush" "name": "markDirtyIfOnPush"
}, },
{
"name": "markStylingStateAsDirty"
},
{ {
"name": "markViewDirty" "name": "markViewDirty"
}, },
{ {
"name": "matchTemplateAttribute" "name": "matchTemplateAttribute"
}, },
{
"name": "maybeApplyStyling"
},
{ {
"name": "namespaceHTMLInternal" "name": "namespaceHTMLInternal"
}, },
@ -1151,9 +1142,6 @@
{ {
"name": "normalizeBitMaskValue" "name": "normalizeBitMaskValue"
}, },
{
"name": "patchConfig"
},
{ {
"name": "postProcessBaseDirective" "name": "postProcessBaseDirective"
}, },
@ -1217,9 +1205,6 @@
{ {
"name": "renderDetachView" "name": "renderDetachView"
}, },
{
"name": "renderHostBindingsAsStale"
},
{ {
"name": "renderInitialStyling" "name": "renderInitialStyling"
}, },
@ -1232,6 +1217,9 @@
{ {
"name": "renderView" "name": "renderView"
}, },
{
"name": "resetAllStylingState"
},
{ {
"name": "resetComponentState" "name": "resetComponentState"
}, },
@ -1265,9 +1253,6 @@
{ {
"name": "selectView" "name": "selectView"
}, },
{
"name": "setActiveElementFlag"
},
{ {
"name": "setActiveHostElement" "name": "setActiveHostElement"
}, },
@ -1280,6 +1265,9 @@
{ {
"name": "setClass" "name": "setClass"
}, },
{
"name": "setConfig"
},
{ {
"name": "setCurrentDirectiveDef" "name": "setCurrentDirectiveDef"
}, },
@ -1289,15 +1277,9 @@
{ {
"name": "setCurrentStyleSanitizer" "name": "setCurrentStyleSanitizer"
}, },
{
"name": "setDefaultValue"
},
{ {
"name": "setDirectiveStylingInput" "name": "setDirectiveStylingInput"
}, },
{
"name": "setElementExitFn"
},
{ {
"name": "setGuardMask" "name": "setGuardMask"
}, },
@ -1319,9 +1301,6 @@
{ {
"name": "setIsNotParent" "name": "setIsNotParent"
}, },
{
"name": "setMapAsDirty"
},
{ {
"name": "setMapValue" "name": "setMapValue"
}, },
@ -1340,15 +1319,18 @@
{ {
"name": "setUpAttributes" "name": "setUpAttributes"
}, },
{
"name": "setValue"
},
{ {
"name": "shouldSearchParent" "name": "shouldSearchParent"
}, },
{
"name": "stateIsPersisted"
},
{ {
"name": "storeCleanupFn" "name": "storeCleanupFn"
}, },
{
"name": "storeStylingState"
},
{ {
"name": "stringify" "name": "stringify"
}, },
@ -1358,9 +1340,6 @@
{ {
"name": "stylingMapToString" "name": "stylingMapToString"
}, },
{
"name": "stylingProp"
},
{ {
"name": "syncViewWithBlueprint" "name": "syncViewWithBlueprint"
}, },
@ -1382,23 +1361,26 @@
{ {
"name": "unwrapRNode" "name": "unwrapRNode"
}, },
{
"name": "unwrapSafeValue"
},
{ {
"name": "updateBindingData" "name": "updateBindingData"
}, },
{ {
"name": "updateClassViaContext" "name": "updateClassBinding"
}, },
{ {
"name": "updateInitialStylingOnContext" "name": "updateInitialStylingOnContext"
}, },
{
"name": "updateLastDirectiveIndex"
},
{
"name": "updateLastDirectiveIndex"
},
{ {
"name": "updateRawValueOnContext" "name": "updateRawValueOnContext"
}, },
{ {
"name": "updateStyleViaContext" "name": "updateStyleBinding"
}, },
{ {
"name": "viewAttachedToChangeDetector" "name": "viewAttachedToChangeDetector"
@ -1457,6 +1439,12 @@
{ {
"name": "ɵɵrestoreView" "name": "ɵɵrestoreView"
}, },
{
"name": "ɵɵstyling"
},
{
"name": "ɵɵstylingApply"
},
{ {
"name": "ɵɵtemplate" "name": "ɵɵtemplate"
}, },

View File

@ -9,7 +9,7 @@
import {NgForOfContext} from '@angular/common'; import {NgForOfContext} from '@angular/common';
import {ɵɵdefineComponent} from '../../src/render3/definition'; import {ɵɵdefineComponent} from '../../src/render3/definition';
import {RenderFlags, ɵɵattribute, ɵɵclassMap, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵproperty, ɵɵselect, ɵɵstyleMap, ɵɵstyleProp, ɵɵstyleSanitizer, ɵɵtemplate, ɵɵtext, ɵɵtextInterpolate1} from '../../src/render3/index'; import {RenderFlags, ɵɵattribute, ɵɵclassMap, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵproperty, ɵɵselect, ɵɵstyleMap, ɵɵstyleProp, ɵɵstyleSanitizer, ɵɵstyling, ɵɵstylingApply, ɵɵtemplate, ɵɵtext, ɵɵtextInterpolate1} from '../../src/render3/index';
import {AttributeMarker} from '../../src/render3/interfaces/node'; import {AttributeMarker} from '../../src/render3/interfaces/node';
import {bypassSanitizationTrustHtml, bypassSanitizationTrustResourceUrl, bypassSanitizationTrustScript, bypassSanitizationTrustStyle, bypassSanitizationTrustUrl, getSanitizationBypassType, unwrapSafeValue} from '../../src/sanitization/bypass'; import {bypassSanitizationTrustHtml, bypassSanitizationTrustResourceUrl, bypassSanitizationTrustScript, bypassSanitizationTrustStyle, bypassSanitizationTrustUrl, getSanitizationBypassType, unwrapSafeValue} from '../../src/sanitization/bypass';
import {ɵɵdefaultStyleSanitizer, ɵɵsanitizeHtml, ɵɵsanitizeResourceUrl, ɵɵsanitizeScript, ɵɵsanitizeStyle, ɵɵsanitizeUrl} from '../../src/sanitization/sanitization'; import {ɵɵdefaultStyleSanitizer, ɵɵsanitizeHtml, ɵɵsanitizeResourceUrl, ɵɵsanitizeScript, ɵɵsanitizeStyle, ɵɵsanitizeUrl} from '../../src/sanitization/sanitization';
@ -20,7 +20,11 @@ import {NgForOf} from './common_with_def';
import {ComponentFixture, TemplateFixture} from './render_util'; import {ComponentFixture, TemplateFixture} from './render_util';
describe('instructions', () => { describe('instructions', () => {
function createAnchor() { ɵɵelement(0, 'a'); } function createAnchor() {
ɵɵelementStart(0, 'a');
ɵɵstyling();
ɵɵelementEnd();
}
function createDiv(initialClasses?: string[] | null, initialStyles?: string[] | null) { function createDiv(initialClasses?: string[] | null, initialStyles?: string[] | null) {
const attrs: any[] = []; const attrs: any[] = [];
@ -30,7 +34,9 @@ describe('instructions', () => {
if (initialStyles) { if (initialStyles) {
attrs.push(AttributeMarker.Styles, ...initialStyles); attrs.push(AttributeMarker.Styles, ...initialStyles);
} }
ɵɵelement(0, 'div', attrs); ɵɵelementStart(0, 'div', attrs);
ɵɵstyling();
ɵɵelementEnd();
} }
function createScript() { ɵɵelement(0, 'script'); } function createScript() { ɵɵelement(0, 'script'); }
@ -150,6 +156,7 @@ describe('instructions', () => {
t.update(() => { t.update(() => {
ɵɵstyleSanitizer(ɵɵdefaultStyleSanitizer); ɵɵstyleSanitizer(ɵɵdefaultStyleSanitizer);
ɵɵstyleProp('background-image', 'url("http://server")'); ɵɵstyleProp('background-image', 'url("http://server")');
ɵɵstylingApply();
}); });
// nothing is set because sanitizer suppresses it. // nothing is set because sanitizer suppresses it.
expect(t.html).toEqual('<div></div>'); expect(t.html).toEqual('<div></div>');
@ -157,6 +164,7 @@ describe('instructions', () => {
t.update(() => { t.update(() => {
ɵɵstyleSanitizer(ɵɵdefaultStyleSanitizer); ɵɵstyleSanitizer(ɵɵdefaultStyleSanitizer);
ɵɵstyleProp('background-image', bypassSanitizationTrustStyle('url("http://server2")')); ɵɵstyleProp('background-image', bypassSanitizationTrustStyle('url("http://server2")'));
ɵɵstylingApply();
}); });
expect((t.hostElement.firstChild as HTMLElement).style.getPropertyValue('background-image')) expect((t.hostElement.firstChild as HTMLElement).style.getPropertyValue('background-image'))
.toEqual('url("http://server2")'); .toEqual('url("http://server2")');
@ -165,12 +173,17 @@ describe('instructions', () => {
describe('styleMap', () => { describe('styleMap', () => {
function createDivWithStyle() { function createDivWithStyle() {
ɵɵelement(0, 'div', [AttributeMarker.Styles, 'height', '10px']); ɵɵelementStart(0, 'div', [AttributeMarker.Styles, 'height', '10px']);
ɵɵstyling();
ɵɵelementEnd();
} }
it('should add style', () => { it('should add style', () => {
const fixture = new TemplateFixture(createDivWithStyle, () => {}, 1); const fixture = new TemplateFixture(createDivWithStyle, () => {}, 1);
fixture.update(() => { ɵɵstyleMap({'background-color': 'red'}); }); fixture.update(() => {
ɵɵstyleMap({'background-color': 'red'});
ɵɵstylingApply();
});
expect(fixture.html).toEqual('<div style="background-color: red; height: 10px;"></div>'); expect(fixture.html).toEqual('<div style="background-color: red; height: 10px;"></div>');
}); });
@ -192,6 +205,7 @@ describe('instructions', () => {
'filter': 'filter', 'filter': 'filter',
'width': 'width' 'width': 'width'
}); });
ɵɵstylingApply();
}); });
const props = detectedValues.sort(); const props = detectedValues.sort();
@ -202,11 +216,18 @@ describe('instructions', () => {
}); });
describe('elementClass', () => { describe('elementClass', () => {
function createDivWithStyling() { ɵɵelement(0, 'div'); } function createDivWithStyling() {
ɵɵelementStart(0, 'div');
ɵɵstyling();
ɵɵelementEnd();
}
it('should add class', () => { it('should add class', () => {
const fixture = new TemplateFixture(createDivWithStyling, () => {}, 1); const fixture = new TemplateFixture(createDivWithStyling, () => {}, 1);
fixture.update(() => { ɵɵclassMap('multiple classes'); }); fixture.update(() => {
ɵɵclassMap('multiple classes');
ɵɵstylingApply();
});
expect(fixture.html).toEqual('<div class="classes multiple"></div>'); expect(fixture.html).toEqual('<div class="classes multiple"></div>');
}); });
}); });

View File

@ -9,7 +9,7 @@
import {RendererType2} from '../../src/render/api'; import {RendererType2} from '../../src/render/api';
import {getLContext} from '../../src/render3/context_discovery'; import {getLContext} from '../../src/render3/context_discovery';
import {AttributeMarker, ɵɵadvance, ɵɵattribute, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵhostProperty, ɵɵproperty} from '../../src/render3/index'; import {AttributeMarker, ɵɵadvance, ɵɵattribute, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵhostProperty, ɵɵproperty} from '../../src/render3/index';
import {ɵɵallocHostVars, ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵprojection, ɵɵprojectionDef, ɵɵtemplate, ɵɵtext, ɵɵtextInterpolate} from '../../src/render3/instructions/all'; import {ɵɵallocHostVars, ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵprojection, ɵɵprojectionDef, ɵɵstyling, ɵɵstylingApply, ɵɵtemplate, ɵɵtext, ɵɵtextInterpolate} from '../../src/render3/instructions/all';
import {MONKEY_PATCH_KEY_NAME} from '../../src/render3/interfaces/context'; import {MONKEY_PATCH_KEY_NAME} from '../../src/render3/interfaces/context';
import {RenderFlags} from '../../src/render3/interfaces/definition'; import {RenderFlags} from '../../src/render3/interfaces/definition';
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from '../../src/render3/interfaces/renderer'; import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from '../../src/render3/interfaces/renderer';
@ -630,7 +630,12 @@ describe('element discovery', () => {
vars: 0, vars: 0,
template: (rf: RenderFlags, ctx: StructuredComp) => { template: (rf: RenderFlags, ctx: StructuredComp) => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
ɵɵelement(0, 'section'); ɵɵelementStart(0, 'section');
ɵɵstyling();
ɵɵelementEnd();
}
if (rf & RenderFlags.Update) {
ɵɵstylingApply();
} }
} }
}); });

View File

@ -6,11 +6,11 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ɵɵadvance} from '../../../../src/render3/instructions/advance'; import {ɵɵadvance} from '../../../../src/render3/instructions/advance';
import {ɵɵelement, ɵɵelementEnd, ɵɵelementStart} from '../../../../src/render3/instructions/element'; import {ɵɵelementEnd, ɵɵelementStart} from '../../../../src/render3/instructions/element';
import {refreshView} from '../../../../src/render3/instructions/shared'; import {refreshView} from '../../../../src/render3/instructions/shared';
import {RenderFlags} from '../../../../src/render3/interfaces/definition'; import {RenderFlags} from '../../../../src/render3/interfaces/definition';
import {TVIEW} from '../../../../src/render3/interfaces/view'; import {TVIEW} from '../../../../src/render3/interfaces/view';
import {ɵɵclassMap, ɵɵstyleMap} from '../../../../src/render3/styling_next/instructions'; import {ɵɵclassMap, ɵɵstyleMap, ɵɵstyling, ɵɵstylingApply} from '../../../../src/render3/styling_next/instructions';
import {setupRootViewWithEmbeddedViews} from '../setup'; import {setupRootViewWithEmbeddedViews} from '../setup';
`<ng-template> `<ng-template>
@ -30,49 +30,79 @@ import {setupRootViewWithEmbeddedViews} from '../setup';
function testTemplate(rf: RenderFlags, ctx: any) { function testTemplate(rf: RenderFlags, ctx: any) {
if (rf & 1) { if (rf & 1) {
ɵɵelementStart(0, 'div'); ɵɵelementStart(0, 'div');
ɵɵelement(1, 'div'); ɵɵelementStart(1, 'div');
ɵɵelement(2, 'div'); ɵɵstyling();
ɵɵelement(3, 'div'); ɵɵelementEnd();
ɵɵelement(4, 'div'); ɵɵelementStart(2, 'div');
ɵɵelement(5, 'div'); ɵɵstyling();
ɵɵelement(6, 'div'); ɵɵelementEnd();
ɵɵelement(7, 'div'); ɵɵelementStart(3, 'div');
ɵɵelement(8, 'div'); ɵɵstyling();
ɵɵelement(9, 'div'); ɵɵelementEnd();
ɵɵelement(10, 'div'); ɵɵelementStart(4, 'div');
ɵɵstyling();
ɵɵelementEnd();
ɵɵelementStart(5, 'div');
ɵɵstyling();
ɵɵelementEnd();
ɵɵelementStart(6, 'div');
ɵɵstyling();
ɵɵelementEnd();
ɵɵelementStart(7, 'div');
ɵɵstyling();
ɵɵelementEnd();
ɵɵelementStart(8, 'div');
ɵɵstyling();
ɵɵelementEnd();
ɵɵelementStart(9, 'div');
ɵɵstyling();
ɵɵelementEnd();
ɵɵelementStart(10, 'div');
ɵɵstyling();
ɵɵelementEnd();
ɵɵelementEnd(); ɵɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
ɵɵadvance(1); ɵɵadvance(1);
ɵɵstyleMap({width: '0px', height: '0px'}); ɵɵstyleMap({width: '0px', height: '0px'});
ɵɵclassMap('one two'); ɵɵclassMap('one two');
ɵɵstylingApply();
ɵɵadvance(1); ɵɵadvance(1);
ɵɵstyleMap({width: '10px', height: '100px'}); ɵɵstyleMap({width: '10px', height: '100px'});
ɵɵclassMap('one two'); ɵɵclassMap('one two');
ɵɵstylingApply();
ɵɵadvance(1); ɵɵadvance(1);
ɵɵstyleMap({width: '20px', height: '200px'}); ɵɵstyleMap({width: '20px', height: '200px'});
ɵɵclassMap('one two'); ɵɵclassMap('one two');
ɵɵstylingApply();
ɵɵadvance(1); ɵɵadvance(1);
ɵɵstyleMap({width: '30px', height: '300px'}); ɵɵstyleMap({width: '30px', height: '300px'});
ɵɵclassMap('one two'); ɵɵclassMap('one two');
ɵɵstylingApply();
ɵɵadvance(1); ɵɵadvance(1);
ɵɵstyleMap({width: '40px', height: '400px'}); ɵɵstyleMap({width: '40px', height: '400px'});
ɵɵclassMap('one two'); ɵɵclassMap('one two');
ɵɵstylingApply();
ɵɵadvance(1); ɵɵadvance(1);
ɵɵstyleMap({width: '50px', height: '500px'}); ɵɵstyleMap({width: '50px', height: '500px'});
ɵɵclassMap('one two'); ɵɵclassMap('one two');
ɵɵstylingApply();
ɵɵadvance(1); ɵɵadvance(1);
ɵɵstyleMap({width: '60px', height: '600px'}); ɵɵstyleMap({width: '60px', height: '600px'});
ɵɵclassMap('one two'); ɵɵclassMap('one two');
ɵɵstylingApply();
ɵɵadvance(1); ɵɵadvance(1);
ɵɵstyleMap({width: '70px', height: '700px'}); ɵɵstyleMap({width: '70px', height: '700px'});
ɵɵclassMap('one two'); ɵɵclassMap('one two');
ɵɵstylingApply();
ɵɵadvance(1); ɵɵadvance(1);
ɵɵstyleMap({width: '80px', height: '800px'}); ɵɵstyleMap({width: '80px', height: '800px'});
ɵɵclassMap('one two'); ɵɵclassMap('one two');
ɵɵstylingApply();
ɵɵadvance(1); ɵɵadvance(1);
ɵɵstyleMap({width: '90px', height: '900px'}); ɵɵstyleMap({width: '90px', height: '900px'});
ɵɵclassMap('one two'); ɵɵclassMap('one two');
ɵɵstylingApply();
} }
} }

View File

@ -6,12 +6,12 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ɵɵadvance} from '../../../../src/render3/instructions/advance'; import {ɵɵadvance} from '../../../../src/render3/instructions/advance';
import {ɵɵelement, ɵɵelementEnd, ɵɵelementStart} from '../../../../src/render3/instructions/element'; import {ɵɵelementEnd, ɵɵelementStart} from '../../../../src/render3/instructions/element';
import {refreshView} from '../../../../src/render3/instructions/shared'; import {refreshView} from '../../../../src/render3/instructions/shared';
import {RenderFlags} from '../../../../src/render3/interfaces/definition'; import {RenderFlags} from '../../../../src/render3/interfaces/definition';
import {AttributeMarker} from '../../../../src/render3/interfaces/node'; import {AttributeMarker} from '../../../../src/render3/interfaces/node';
import {TVIEW} from '../../../../src/render3/interfaces/view'; import {TVIEW} from '../../../../src/render3/interfaces/view';
import {ɵɵclassProp, ɵɵstyleProp} from '../../../../src/render3/styling_next/instructions'; import {ɵɵclassProp, ɵɵstyleProp, ɵɵstyling, ɵɵstylingApply} from '../../../../src/render3/styling_next/instructions';
import {setupRootViewWithEmbeddedViews} from '../setup'; import {setupRootViewWithEmbeddedViews} from '../setup';
`<ng-template> `<ng-template>
@ -31,50 +31,89 @@ import {setupRootViewWithEmbeddedViews} from '../setup';
function testTemplate(rf: RenderFlags, ctx: any) { function testTemplate(rf: RenderFlags, ctx: any) {
if (rf & 1) { if (rf & 1) {
ɵɵelementStart(0, 'div', [AttributeMarker.Classes, 'list']); ɵɵelementStart(0, 'div', [AttributeMarker.Classes, 'list']);
ɵɵelement(1, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']); ɵɵelementStart(
ɵɵelement(2, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']); 1, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']);
ɵɵelement(3, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']); ɵɵstyling();
ɵɵelement(4, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']); ɵɵelementEnd();
ɵɵelement(5, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']); ɵɵelementStart(
ɵɵelement(6, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']); 2, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']);
ɵɵelement(7, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']); ɵɵstyling();
ɵɵelement(8, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']); ɵɵelementEnd();
ɵɵelement(9, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']); ɵɵelementStart(
ɵɵelement( 3, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']);
ɵɵstyling();
ɵɵelementEnd();
ɵɵelementStart(
4, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']);
ɵɵstyling();
ɵɵelementEnd();
ɵɵelementStart(
5, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']);
ɵɵstyling();
ɵɵelementEnd();
ɵɵelementStart(
6, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']);
ɵɵstyling();
ɵɵelementEnd();
ɵɵelementStart(
7, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']);
ɵɵstyling();
ɵɵelementEnd();
ɵɵelementStart(
8, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']);
ɵɵstyling();
ɵɵelementEnd();
ɵɵelementStart(
9, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']);
ɵɵstyling();
ɵɵelementEnd();
ɵɵelementStart(
10, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']); 10, 'div', [AttributeMarker.Classes, 'item', AttributeMarker.Styles, 'width', '50px']);
ɵɵstyling();
ɵɵelementEnd();
ɵɵelementEnd(); ɵɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
ɵɵadvance(1); ɵɵadvance(1);
ɵɵstyleProp('width', '0px'); ɵɵstyleProp('width', '0px');
ɵɵclassProp('scale', true); ɵɵclassProp('scale', true);
ɵɵstylingApply();
ɵɵadvance(1); ɵɵadvance(1);
ɵɵstyleProp('width', '100px'); ɵɵstyleProp('width', '100px');
ɵɵclassProp('scale', true); ɵɵclassProp('scale', true);
ɵɵstylingApply();
ɵɵadvance(1); ɵɵadvance(1);
ɵɵstyleProp('width', '200px'); ɵɵstyleProp('width', '200px');
ɵɵclassProp('scale', true); ɵɵclassProp('scale', true);
ɵɵstylingApply();
ɵɵadvance(1); ɵɵadvance(1);
ɵɵstyleProp('width', '300px'); ɵɵstyleProp('width', '300px');
ɵɵclassProp('scale', true); ɵɵclassProp('scale', true);
ɵɵstylingApply();
ɵɵadvance(1); ɵɵadvance(1);
ɵɵstyleProp('width', '400px'); ɵɵstyleProp('width', '400px');
ɵɵclassProp('scale', true); ɵɵclassProp('scale', true);
ɵɵstylingApply();
ɵɵadvance(1); ɵɵadvance(1);
ɵɵstyleProp('width', '500px'); ɵɵstyleProp('width', '500px');
ɵɵclassProp('scale', true); ɵɵclassProp('scale', true);
ɵɵstylingApply();
ɵɵadvance(1); ɵɵadvance(1);
ɵɵstyleProp('width', '600px'); ɵɵstyleProp('width', '600px');
ɵɵclassProp('scale', true); ɵɵclassProp('scale', true);
ɵɵstylingApply();
ɵɵadvance(1); ɵɵadvance(1);
ɵɵstyleProp('width', '700px'); ɵɵstyleProp('width', '700px');
ɵɵclassProp('scale', true); ɵɵclassProp('scale', true);
ɵɵstylingApply();
ɵɵadvance(1); ɵɵadvance(1);
ɵɵstyleProp('width', '800px'); ɵɵstyleProp('width', '800px');
ɵɵclassProp('scale', true); ɵɵclassProp('scale', true);
ɵɵstylingApply();
ɵɵadvance(1); ɵɵadvance(1);
ɵɵstyleProp('width', '900px'); ɵɵstyleProp('width', '900px');
ɵɵclassProp('scale', true); ɵɵclassProp('scale', true);
ɵɵstylingApply();
} }
} }

View File

@ -6,11 +6,11 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ɵɵadvance} from '../../../../src/render3/instructions/advance'; import {ɵɵadvance} from '../../../../src/render3/instructions/advance';
import {ɵɵelement, ɵɵelementEnd, ɵɵelementStart} from '../../../../src/render3/instructions/element'; import {ɵɵelementEnd, ɵɵelementStart} from '../../../../src/render3/instructions/element';
import {refreshView} from '../../../../src/render3/instructions/shared'; import {refreshView} from '../../../../src/render3/instructions/shared';
import {RenderFlags} from '../../../../src/render3/interfaces/definition'; import {RenderFlags} from '../../../../src/render3/interfaces/definition';
import {TVIEW} from '../../../../src/render3/interfaces/view'; import {TVIEW} from '../../../../src/render3/interfaces/view';
import {ɵɵstyleProp} from '../../../../src/render3/styling_next/instructions'; import {ɵɵstyleProp, ɵɵstyling, ɵɵstylingApply} from '../../../../src/render3/styling_next/instructions';
import {setupRootViewWithEmbeddedViews} from '../setup'; import {setupRootViewWithEmbeddedViews} from '../setup';
`<ng-template> `<ng-template>
@ -30,39 +30,69 @@ import {setupRootViewWithEmbeddedViews} from '../setup';
function testTemplate(rf: RenderFlags, ctx: any) { function testTemplate(rf: RenderFlags, ctx: any) {
if (rf & 1) { if (rf & 1) {
ɵɵelementStart(0, 'div'); ɵɵelementStart(0, 'div');
ɵɵelement(1, 'button'); ɵɵelementStart(1, 'button');
ɵɵelement(2, 'button'); ɵɵstyling();
ɵɵelement(3, 'button'); ɵɵelementEnd();
ɵɵelement(4, 'button'); ɵɵelementStart(2, 'button');
ɵɵelement(5, 'button'); ɵɵstyling();
ɵɵelement(6, 'button'); ɵɵelementEnd();
ɵɵelement(7, 'button'); ɵɵelementStart(3, 'button');
ɵɵelement(8, 'button'); ɵɵstyling();
ɵɵelement(9, 'button'); ɵɵelementEnd();
ɵɵelement(10, 'button'); ɵɵelementStart(4, 'button');
ɵɵstyling();
ɵɵelementEnd();
ɵɵelementStart(5, 'button');
ɵɵstyling();
ɵɵelementEnd();
ɵɵelementStart(6, 'button');
ɵɵstyling();
ɵɵelementEnd();
ɵɵelementStart(7, 'button');
ɵɵstyling();
ɵɵelementEnd();
ɵɵelementStart(8, 'button');
ɵɵstyling();
ɵɵelementEnd();
ɵɵelementStart(9, 'button');
ɵɵstyling();
ɵɵelementEnd();
ɵɵelementStart(10, 'button');
ɵɵstyling();
ɵɵelementEnd();
ɵɵelementEnd(); ɵɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
ɵɵadvance(1); ɵɵadvance(1);
ɵɵstyleProp('background-color', 'color1'); ɵɵstyleProp('background-color', 'color1');
ɵɵstylingApply();
ɵɵadvance(1); ɵɵadvance(1);
ɵɵstyleProp('background-color', 'color2'); ɵɵstyleProp('background-color', 'color2');
ɵɵstylingApply();
ɵɵadvance(1); ɵɵadvance(1);
ɵɵstyleProp('background-color', 'color3'); ɵɵstyleProp('background-color', 'color3');
ɵɵstylingApply();
ɵɵadvance(1); ɵɵadvance(1);
ɵɵstyleProp('background-color', 'color4'); ɵɵstyleProp('background-color', 'color4');
ɵɵstylingApply();
ɵɵadvance(1); ɵɵadvance(1);
ɵɵstyleProp('background-color', 'color5'); ɵɵstyleProp('background-color', 'color5');
ɵɵstylingApply();
ɵɵadvance(1); ɵɵadvance(1);
ɵɵstyleProp('background-color', 'color6'); ɵɵstyleProp('background-color', 'color6');
ɵɵstylingApply();
ɵɵadvance(1); ɵɵadvance(1);
ɵɵstyleProp('background-color', 'color7'); ɵɵstyleProp('background-color', 'color7');
ɵɵstylingApply();
ɵɵadvance(1); ɵɵadvance(1);
ɵɵstyleProp('background-color', 'color8'); ɵɵstyleProp('background-color', 'color8');
ɵɵstylingApply();
ɵɵadvance(1); ɵɵadvance(1);
ɵɵstyleProp('background-color', 'color9'); ɵɵstyleProp('background-color', 'color9');
ɵɵstylingApply();
ɵɵadvance(1); ɵɵadvance(1);
ɵɵstyleProp('background-color', 'color10'); ɵɵstyleProp('background-color', 'color10');
ɵɵstylingApply();
} }
} }

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 {normalizeIntoStylingMap as createMap} from '../../../src/render3/styling_next/util'; import {normalizeIntoStylingMap as createMap} from '../../../src/render3/styling_next/map_based_bindings';
describe('map-based bindings', () => { describe('map-based bindings', () => {
describe('StylingMapArray construction', () => { describe('StylingMapArray construction', () => {

View File

@ -5,10 +5,8 @@
* 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 {registerBinding} from '@angular/core/src/render3/styling_next/bindings'; import {DEFAULT_GUARD_MASK_VALUE, registerBinding} from '@angular/core/src/render3/styling_next/bindings';
import {attachStylingDebugObject} from '@angular/core/src/render3/styling_next/styling_debug'; import {attachStylingDebugObject} from '@angular/core/src/render3/styling_next/styling_debug';
import {DEFAULT_GUARD_MASK_VALUE} from '@angular/core/src/render3/styling_next/util';
import {allocTStylingContext} from '../../../src/render3/styling_next/util'; import {allocTStylingContext} from '../../../src/render3/styling_next/util';
describe('styling context', () => { describe('styling context', () => {
@ -17,36 +15,33 @@ describe('styling context', () => {
const context = debug.context; const context = debug.context;
expect(debug.entries).toEqual({}); expect(debug.entries).toEqual({});
registerBinding(context, 1, 0, 'width', '100px'); registerBinding(context, 1, 'width', '100px');
expect(debug.entries['width']).toEqual({ expect(debug.entries['width']).toEqual({
prop: 'width', prop: 'width',
valuesCount: 1, valuesCount: 1,
sanitizationRequired: false, sanitizationRequired: false,
templateBitMask: buildGuardMask(), guardMask: buildGuardMask(),
hostBindingsBitMask: buildGuardMask(),
defaultValue: '100px', defaultValue: '100px',
sources: ['100px'], sources: ['100px'],
}); });
registerBinding(context, 2, 0, 'width', 20); registerBinding(context, 2, 'width', 20);
expect(debug.entries['width']).toEqual({ expect(debug.entries['width']).toEqual({
prop: 'width', prop: 'width',
sanitizationRequired: false, sanitizationRequired: false,
valuesCount: 2, valuesCount: 2,
templateBitMask: buildGuardMask(2), guardMask: buildGuardMask(2),
hostBindingsBitMask: buildGuardMask(),
defaultValue: '100px', defaultValue: '100px',
sources: [20, '100px'], sources: [20, '100px'],
}); });
registerBinding(context, 3, 0, 'height', 10); registerBinding(context, 3, 'height', 10);
registerBinding(context, 4, 1, 'height', 15); registerBinding(context, 4, 'height', 15);
expect(debug.entries['height']).toEqual({ expect(debug.entries['height']).toEqual({
prop: 'height', prop: 'height',
valuesCount: 3, valuesCount: 3,
sanitizationRequired: false, sanitizationRequired: false,
templateBitMask: buildGuardMask(3), guardMask: buildGuardMask(3, 4),
hostBindingsBitMask: buildGuardMask(4),
defaultValue: null, defaultValue: null,
sources: [10, 15, null], sources: [10, 15, null],
}); });
@ -57,14 +52,13 @@ describe('styling context', () => {
const context = debug.context; const context = debug.context;
expect(debug.entries).toEqual({}); expect(debug.entries).toEqual({});
registerBinding(context, 1, 0, 'width', 123); registerBinding(context, 1, 'width', 123);
registerBinding(context, 1, 0, 'width', 123); registerBinding(context, 1, 'width', 123);
expect(debug.entries['width']).toEqual({ expect(debug.entries['width']).toEqual({
prop: 'width', prop: 'width',
valuesCount: 2, valuesCount: 2,
sanitizationRequired: false, sanitizationRequired: false,
templateBitMask: buildGuardMask(1), guardMask: buildGuardMask(1),
hostBindingsBitMask: buildGuardMask(),
defaultValue: null, defaultValue: null,
sources: [123, null], sources: [123, null],
}); });
@ -74,36 +68,33 @@ describe('styling context', () => {
const debug = makeContextWithDebug(); const debug = makeContextWithDebug();
const context = debug.context; const context = debug.context;
registerBinding(context, 1, 0, 'width', null); registerBinding(context, 1, 'width', null);
const x = debug.entries['width']; const x = debug.entries['width'];
expect(debug.entries['width']).toEqual({ expect(debug.entries['width']).toEqual({
prop: 'width', prop: 'width',
valuesCount: 1, valuesCount: 1,
sanitizationRequired: false, sanitizationRequired: false,
templateBitMask: buildGuardMask(), guardMask: buildGuardMask(),
hostBindingsBitMask: buildGuardMask(),
defaultValue: null, defaultValue: null,
sources: [null] sources: [null]
}); });
registerBinding(context, 1, 0, 'width', '100px'); registerBinding(context, 1, 'width', '100px');
expect(debug.entries['width']).toEqual({ expect(debug.entries['width']).toEqual({
prop: 'width', prop: 'width',
valuesCount: 1, valuesCount: 1,
sanitizationRequired: false, sanitizationRequired: false,
templateBitMask: buildGuardMask(), guardMask: buildGuardMask(),
hostBindingsBitMask: buildGuardMask(),
defaultValue: '100px', defaultValue: '100px',
sources: ['100px'] sources: ['100px']
}); });
registerBinding(context, 1, 0, 'width', '200px'); registerBinding(context, 1, 'width', '200px');
expect(debug.entries['width']).toEqual({ expect(debug.entries['width']).toEqual({
prop: 'width', prop: 'width',
valuesCount: 1, valuesCount: 1,
sanitizationRequired: false, sanitizationRequired: false,
templateBitMask: buildGuardMask(), guardMask: buildGuardMask(),
hostBindingsBitMask: buildGuardMask(),
defaultValue: '100px', defaultValue: '100px',
sources: ['100px'] sources: ['100px']
}); });

View File

@ -18,7 +18,7 @@ describe('styling debugging tools', () => {
const data: any[] = []; const data: any[] = [];
const d = new NodeStylingDebug(context, data); const d = new NodeStylingDebug(context, data);
registerBinding(context, 0, 0, 'width', null); registerBinding(context, 0, 'width', null);
expect(d.summary).toEqual({ expect(d.summary).toEqual({
width: { width: {
prop: 'width', prop: 'width',
@ -27,7 +27,7 @@ describe('styling debugging tools', () => {
}, },
}); });
registerBinding(context, 0, 0, 'width', '100px'); registerBinding(context, 0, 'width', '100px');
expect(d.summary).toEqual({ expect(d.summary).toEqual({
width: { width: {
prop: 'width', prop: 'width',
@ -39,7 +39,7 @@ describe('styling debugging tools', () => {
const someBindingIndex1 = 1; const someBindingIndex1 = 1;
data[someBindingIndex1] = '200px'; data[someBindingIndex1] = '200px';
registerBinding(context, 0, 0, 'width', someBindingIndex1); registerBinding(context, 0, 'width', someBindingIndex1);
expect(d.summary).toEqual({ expect(d.summary).toEqual({
width: { width: {
prop: 'width', prop: 'width',
@ -51,7 +51,7 @@ describe('styling debugging tools', () => {
const someBindingIndex2 = 2; const someBindingIndex2 = 2;
data[someBindingIndex2] = '500px'; data[someBindingIndex2] = '500px';
registerBinding(context, 0, 1, 'width', someBindingIndex2); registerBinding(context, 0, 'width', someBindingIndex2);
expect(d.summary).toEqual({ expect(d.summary).toEqual({
width: { width: {
prop: 'width', prop: 'width',

View File

@ -1076,6 +1076,10 @@ export declare function ɵɵstylePropInterpolateV(prop: string, values: any[], v
export declare function ɵɵstyleSanitizer(sanitizer: StyleSanitizeFn | null): void; export declare function ɵɵstyleSanitizer(sanitizer: StyleSanitizeFn | null): void;
export declare function ɵɵstyling(): void;
export declare function ɵɵstylingApply(): void;
export declare function ɵɵtemplate(index: number, templateFn: ComponentTemplate<any> | null, consts: number, vars: number, tagName?: string | null, attrs?: TAttributes | null, localRefs?: string[] | null, localRefExtractor?: LocalRefExtractor): void; export declare function ɵɵtemplate(index: number, templateFn: ComponentTemplate<any> | null, consts: number, vars: number, tagName?: string | null, attrs?: TAttributes | null, localRefs?: string[] | null, localRefExtractor?: LocalRefExtractor): void;
export declare function ɵɵtemplateRefExtractor(tNode: TNode, currentView: LView): ViewEngine_TemplateRef<unknown> | null; export declare function ɵɵtemplateRefExtractor(tNode: TNode, currentView: LView): ViewEngine_TemplateRef<unknown> | null;