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

This patch is a final major refactor in styling Angular.

This PR includes three main fixes:

All temporary state taht is persisted between template style/class application
and style/class application in host bindings is now removed.
Removes the styling() and stylingApply() instructions.
Introduces a "direct apply" mode that is used apply prop-based
style/class in the event that there are no map-based bindings as
well as property collisions.

PR Close #32259

PR Close #32596
This commit is contained in:
Matias Niemelä
2019-09-09 13:14:26 -07:00
parent 55b55e7c97
commit 15aeab1620
46 changed files with 1728 additions and 1614 deletions

View File

@ -15,6 +15,8 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
import {onlyInIvy} from '@angular/private/testing';
describe('new styling integration', () => {
beforeEach(() => resetStylingCounters());
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',
() => {
@ -124,6 +126,74 @@ describe('new styling integration', () => {
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')
.it('should combine all styling across the template, directive and component host bindings',
() => {
@ -240,6 +310,7 @@ describe('new styling integration', () => {
fixture.componentInstance.w3 = null;
fixture.detectChanges();
expect(styles.values).toEqual({
'width': '200px',
});
@ -309,16 +380,22 @@ describe('new styling integration', () => {
.it('should apply map-based style and class entries', () => {
@Component({template: '<div [style]="s" [class]="c"></div>'})
class Cmp {
public c !: {[key: string]: any};
updateClasses(prop: string) {
this.c = {...this.c || {}};
this.c[prop] = true;
public c: {[key: string]: any}|null = null;
updateClasses(classes: string) {
const c = this.c || (this.c = {});
Object.keys(this.c).forEach(className => { c[className] = false; });
classes.split(/\s+/).forEach(className => { c[className] = true; });
}
public s !: {[key: string]: any};
public s: {[key: string]: any}|null = null;
updateStyles(prop: string, value: string|number|null) {
this.s = {...this.s || {}};
this.s[prop] = value;
const s = this.s || (this.s = {});
Object.assign(s, {[prop]: value});
}
reset() {
this.s = null;
this.c = null;
}
}
@ -332,22 +409,47 @@ describe('new styling integration', () => {
const element = fixture.nativeElement.querySelector('div');
const node = getDebugNode(element) !;
const styles = node.styles !;
const classes = node.classes !;
let styles = node.styles !;
let classes = node.classes !;
const stylesSummary = styles.summary;
const widthSummary = stylesSummary['width'];
let stylesSummary = styles.summary;
let widthSummary = stylesSummary['width'];
expect(widthSummary.prop).toEqual('width');
expect(widthSummary.value).toEqual('100px');
const heightSummary = stylesSummary['height'];
let heightSummary = stylesSummary['height'];
expect(heightSummary.prop).toEqual('height');
expect(heightSummary.value).toEqual('200px');
const classesSummary = classes.summary;
const abcSummary = classesSummary['abc'];
let classesSummary = classes.summary;
let abcSummary = classesSummary['abc'];
expect(abcSummary.prop).toEqual('abc');
expect(abcSummary.value as any).toEqual(true);
expect(abcSummary.value).toBeTruthy();
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')
@ -385,6 +487,7 @@ describe('new styling integration', () => {
const node = getDebugNode(element) !;
const styles = node.styles !;
expect(styles.values).toEqual({
'width': '555px',
'color': 'red',
@ -509,7 +612,8 @@ describe('new styling integration', () => {
resetStylingCounters();
fixture.detectChanges();
assertStyleCounters(1, 0);
// the width is applied both in TEMPLATE and in HOST_BINDINGS mode
assertStyleCounters(2, 0);
assertStyle(element, 'width', '999px');
assertStyle(element, 'height', '123px');
@ -517,8 +621,8 @@ describe('new styling integration', () => {
resetStylingCounters();
fixture.detectChanges();
// both are applied because the map was altered
assertStyleCounters(2, 0);
// the width is only applied once
assertStyleCounters(1, 0);
assertStyle(element, 'width', '0px');
assertStyle(element, 'height', '123px');
@ -526,8 +630,8 @@ describe('new styling integration', () => {
resetStylingCounters();
fixture.detectChanges();
// all three are applied because the map was altered
assertStyleCounters(3, 0);
// only the width and color have changed
assertStyleCounters(2, 0);
assertStyle(element, 'width', '1000px');
assertStyle(element, 'height', '123px');
assertStyle(element, 'color', 'red');
@ -536,7 +640,9 @@ describe('new styling integration', () => {
resetStylingCounters();
fixture.detectChanges();
assertStyleCounters(1, 0);
// height gets applied twice and all other
// values get applied
assertStyleCounters(4, 0);
assertStyle(element, 'width', '1000px');
assertStyle(element, 'height', '1000px');
assertStyle(element, 'color', 'red');
@ -545,8 +651,7 @@ describe('new styling integration', () => {
resetStylingCounters();
fixture.detectChanges();
// all four are applied because the map was altered
assertStyleCounters(4, 0);
assertStyleCounters(5, 0);
assertStyle(element, 'width', '2000px');
assertStyle(element, 'height', '1000px');
assertStyle(element, 'color', 'blue');
@ -557,62 +662,13 @@ describe('new styling integration', () => {
fixture.detectChanges();
// all four are applied because the map was altered
assertStyleCounters(3, 1);
assertStyleCounters(4, 1);
assertStyle(element, 'width', '2000px');
assertStyle(element, 'height', '1000px');
assertStyle(element, 'color', 'blue');
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')
.it('should sanitize style values before writing them', () => {
@Component({
@ -910,7 +966,6 @@ describe('new styling integration', () => {
TestBed.configureTestingModule({declarations: [Cmp, DirOne, DirTwo]});
const fixture = TestBed.createComponent(Cmp);
const comp = fixture.componentInstance;
fixture.detectChanges();
const dirOne = fixture.nativeElement.querySelector('dir-one');
@ -937,10 +992,10 @@ describe('new styling integration', () => {
@Component({
template: `
<div class="a" [style.width.px]="w" one></div>
<div class="b" [style.height.px]="h" one two></div>
<div class="c" [style.color]="c" two></div>
`
<div class="a" [style.width.px]="w" one></div>
<div class="b" [style.height.px]="h" one two></div>
<div class="c" [style.color]="c" two></div>
`
})
class Cmp {
w = 100;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,8 +5,10 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {DEFAULT_GUARD_MASK_VALUE, registerBinding} from '@angular/core/src/render3/styling_next/bindings';
import {registerBinding} from '@angular/core/src/render3/styling_next/bindings';
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';
describe('styling context', () => {
@ -15,33 +17,36 @@ describe('styling context', () => {
const context = debug.context;
expect(debug.entries).toEqual({});
registerBinding(context, 1, 'width', '100px');
registerBinding(context, 1, 0, 'width', '100px');
expect(debug.entries['width']).toEqual({
prop: 'width',
valuesCount: 1,
sanitizationRequired: false,
guardMask: buildGuardMask(),
templateBitMask: buildGuardMask(),
hostBindingsBitMask: buildGuardMask(),
defaultValue: '100px',
sources: ['100px'],
});
registerBinding(context, 2, 'width', 20);
registerBinding(context, 2, 0, 'width', 20);
expect(debug.entries['width']).toEqual({
prop: 'width',
sanitizationRequired: false,
valuesCount: 2,
guardMask: buildGuardMask(2),
templateBitMask: buildGuardMask(2),
hostBindingsBitMask: buildGuardMask(),
defaultValue: '100px',
sources: [20, '100px'],
});
registerBinding(context, 3, 'height', 10);
registerBinding(context, 4, 'height', 15);
registerBinding(context, 3, 0, 'height', 10);
registerBinding(context, 4, 1, 'height', 15);
expect(debug.entries['height']).toEqual({
prop: 'height',
valuesCount: 3,
sanitizationRequired: false,
guardMask: buildGuardMask(3, 4),
templateBitMask: buildGuardMask(3),
hostBindingsBitMask: buildGuardMask(4),
defaultValue: null,
sources: [10, 15, null],
});
@ -52,13 +57,14 @@ describe('styling context', () => {
const context = debug.context;
expect(debug.entries).toEqual({});
registerBinding(context, 1, 'width', 123);
registerBinding(context, 1, 'width', 123);
registerBinding(context, 1, 0, 'width', 123);
registerBinding(context, 1, 0, 'width', 123);
expect(debug.entries['width']).toEqual({
prop: 'width',
valuesCount: 2,
sanitizationRequired: false,
guardMask: buildGuardMask(1),
templateBitMask: buildGuardMask(1),
hostBindingsBitMask: buildGuardMask(),
defaultValue: null,
sources: [123, null],
});
@ -68,33 +74,36 @@ describe('styling context', () => {
const debug = makeContextWithDebug();
const context = debug.context;
registerBinding(context, 1, 'width', null);
registerBinding(context, 1, 0, 'width', null);
const x = debug.entries['width'];
expect(debug.entries['width']).toEqual({
prop: 'width',
valuesCount: 1,
sanitizationRequired: false,
guardMask: buildGuardMask(),
templateBitMask: buildGuardMask(),
hostBindingsBitMask: buildGuardMask(),
defaultValue: null,
sources: [null]
});
registerBinding(context, 1, 'width', '100px');
registerBinding(context, 1, 0, 'width', '100px');
expect(debug.entries['width']).toEqual({
prop: 'width',
valuesCount: 1,
sanitizationRequired: false,
guardMask: buildGuardMask(),
templateBitMask: buildGuardMask(),
hostBindingsBitMask: buildGuardMask(),
defaultValue: '100px',
sources: ['100px']
});
registerBinding(context, 1, 'width', '200px');
registerBinding(context, 1, 0, 'width', '200px');
expect(debug.entries['width']).toEqual({
prop: 'width',
valuesCount: 1,
sanitizationRequired: false,
guardMask: buildGuardMask(),
templateBitMask: buildGuardMask(),
hostBindingsBitMask: buildGuardMask(),
defaultValue: '100px',
sources: ['100px']
});

View File

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