Angular Team Tech Talk
June 2019
style="prop:value"
class="classOne classTwo"
[style.prop]="myExp"
[class.name]="myExp"
[ngStyle]="myStylesExp"
[ngClass]="myClassesExp"
<!--
- how does Angular parse this?
- how to it efficiently render this?
- how does it handle collisions?
- how is this better in Ivy?
-->
<header class="big">I love styling!</div>
<div class="content">
<p>Lorem ipsum dolor sit amet,
consectetur adipiscing elit. Integer mattis,
mi nec sodales pellentesque, nisi ex feugiat leo,
porttitor tempor nisi enim eu quam...</p>
<p [style.display]="showMore ?
'block' : 'none'">
TNunc vitae fringilla libero. Integer in dolor tellus.
Duis ultricies luctus luctus. Suspendisse condimentum
suscipit bibendum. Phasellus ut magna ex. Aenean urna
sapien, pellentesque a posuere at, dignissim vel lectus.
Proin fringilla maximus justo in bibendum.
</p>
<button (click)="toggleShowMore()">
Show More
</button>
</div>
<div [ngClass]="myClassExp"
[ngStyle]="myStyleExp">
...
</div>
@Directive({
selector: '[ngStyle]'
})
class NgStyleDirective {
@Input('ngStyle')
ngStyleVal: string;
@Input('style')
styleVal: string;
}
class="classOne classTwo"
[class.name]="x"
[ngClass]="classesStr"
[attr.class]="classesStr"
renderer.addClass()
renderer.removeClass()
renderer.setAttribute()
style="key:value"
[style.prop]="x"
[ngStyle]="{key:value}"
[attr.style]="key: value"
renderer.setStyle()
renderer.removeStyle()
renderer.setAttribute()
<div
[style.width]="w1"
[ngStyle]="{width:w2}"
dir-that-sets-width>
How does the view
engine handle this?
</div>
// <div [style.width]="w">
stylesContextForDiv = [
/* header data */
config, 'width', '200px',
// ...
]
<div *ngFor="let item of items"
[style.width]="w">
{{ item }}
</div>
<!-- context -->
<div style="width:200px">...</div>
<!-- context -->
<div style="width:200px">...</div>
<!-- context -->
<div style="width:200px">...</div>
<!-- context -->
<div style="width:200px">...</div>
<div [binding]
attr="value">
... text ...
<p *ngIf></p>
</div>
<script>
function templateFn(){
instruction();
instruction();
instruction();
instruction();
}
</script>
<!--
templateFn for my-component.ts
-->
<div class="modal-box"
[style.width]="width"
[style.height]="height"
[class.disabled]="isDisabled">
My Fancy Modal Box
</div>
// Generated Ng code for my-component.html
myComponentTplFn = function(rf, ctx) {
if (rf & CREATE) {
elementStart(0, 'div', [2, 'modal-box']);
styling();
elementEnd();
text(1, 'My Fancy Modal Box');
}
if (rf & UPDATE) {
classProp('disabled', ctx.isDisabled);
styleProp('width', ctx.width);
styleProp('height', ctx.height);
stylingApply();
}
}
Binding | Ivy Instruction |
---|---|
[style.prop]="value" | styleProp(ctx.value) |
[class.name]="value" | classProp(ctx.value) |
[style]="{key:value}" | styleMap({key:value}) |
[class]=" 'one two three' " | classMap("one two three") |
<div style="key:value"> | element('div', [1, 'key', 'value']) |
<div class="classOne classTwo"> | element('div', [2, 'classOne', 'classTwo']) |
[ngStyle] => [style] [ngClass] => [class]
classProp('disabled', ctx.isDisabled);
styleProp('width', ctx.width);
styleProp('height', ctx.height);
styleMap({ width: '200px' });
classMap('active');
fixed => static/AOT dynamic => arrays
// Fastest
data = {name:null};
data.name = "Matias";
// Slow
data = {};
data.name = "Matias";
// Fastest
data = {name:null};
data.name = "Matias";
// Not As Fast
data = [null];
data[NAME_POSITION] = "Matias";
// Fastest
data = [null];
data[NAME_POSITION] = 'Matias';
// Not as fast
data = new Map();
data = new Set();
setValue(data, 'name', 'Matias');
state/data => array keys => const/enums
class StyleValues {
private _dirty = false;
updateValue(prop: string,
value: string|null) {
//...
this._dirty = true;
}
isDirty() {
return this._dirty;
}
}
var StyleValues = function() {
function t() {
this._dirty = !1
}
return t.prototype.updateValue =
function(t, i) {
this._dirty = !0
}, t.prototype.isDirty =
function() {
return this._dirty
}, t
}();
const enum StyleValuesIndex {
DirtyFlagPosition = 0,
ValuesStartPosition = 1,
}
interface StyleValues extends Array<string|null|number>{
[0]: boolean; //dirty flag
}
function markDirty(styles: StyleValues, yes: boolean) {
styles[StyleValuesIndex.DirtyFlagPosition] = yes;
}
function updateValue(styles: StyleValues, prop: string,
value: string) {
markDirty(styles, true);
// add the value to the array
}
function d(t, a) {
t[0] = a
}
function u(t, a, i) {
d(t, !0)
}
ES6 will makes things better...
classesContext = [
"initial value",
0b11101,
1, 1, "--MAP--", 30,
1, 2, "width", 23, null,
1, 2, "height", 24, "100px",
]
//...
if (classBitMask & updateMask) {
setClass(className, value);
}
if (styleBitMask & updateMask) {
setStyle(prop, value);
}
// list-item.ts
@Component({
selector: 'list-item',
template: `
<h2>{{ title }}</h2>
`
})
class ListItemComp {
@Input('title');
public title: 'string';
}
<!-- app.html -->
<list-item title="one">
</list-item>
<list-item title="two">
</list-item>
<list-item title="three">
</list-item>
<list-item title="four">
</list-item>
tNode => definition lView => instances
<!--
templateFn for my-component.ts
-->
<div class="modal-box"
[style.width]="width"
[style.height]="height"
[class.disabled="isDisabled">
My Fancy Modal Box
</div>
/* class="modal-box"
[style.width]="width"
[style.height]="height"
[class.disabled="isDisabled"> */
tNode1 = {
attributes: [2, 'modal-box'],
bindings: [
'style.width',
'style.height',
'class.disabled'],
}
// <div class="modal-box"...>1</div>
// <div class="modal-box"...>2</div>
lView = [
// ...
'100px', // width value for 1
'200px', // height value for 1
true, // disabled value for 1
//...
'200px', // width value for 2
'400px', // height value for 2
false, // disabled value for 2
]
<!--
templateFn for my-component.ts
-->
<div class="modal-box"
[style.width]="width"
[style.height]="height"
[class.disabled]="isDisabled">
My Fancy Modal Box
</div>
// styles are width and height
tNode.styles = [
bitMask, total, 'height', 31, null,
bitMask, total, 'width', 30, null,
];
// classes are modal-box and disabled
tNode.classes = [
bitMask, total, 'disabled', 30, null,
bitMask, total, 'modal-box', true,
];
<!--
templateFn for my-component.ts
-->
<div class="modal-box"
[style.width]="width"
[style.height]="height"
[class.disabled]="isDisabled">
My Fancy Modal Box
</div>
/*
styles
1. width // lView index = 32
2. height // lView index = 32
classes
1. modal-box // only a default value
2. disabled // lView index = 31
*/
let widthGuardMask = 0b001; // style index 0.
let heightGuardMask = 0b010; // style index 1.
let modalBoxGuardMask = 0b001; // class index 0.
let disabledGuardMask = 0b010; // class index 1.
/*
width = '999px'; (index = 0, bindingIndex = 31)
height = '0px'; (index = 1, bindingIndex = 32)
disabled = true; (index = 0, bindingIndex = 33)
*/
let stylesUpdateMask = 0;
let classesUpdateMask = 0;
stylesUpdateMask |= 1 << 0; // width index is 0
lView[31] = '999px';
stylesUpdateMask |= 1 << 1; // height index is 1
lView[32] = '0px';
classesUpdateMask |= 1 << 0; // disabled index is 0
lView[33] = true;
let i = 0;
while (i < styleContext.length) {
const guardMask = styleContext[i++];
const valuesCount = styleContext[i++];
const prop = styleContext[i++];
if (guardMask & styleUpdateMask) {
for (let j = 0; j < valuesCount; j++) {
const bindingIndex = stylingContext[i + j];
let value = lView[bindingIndex];
if (typeof bindingIndex === 'number' && !value) {
continue;
}
setStyle(element, prop, value);
}
}
i += valuesCount;
}
let i = 0;
while (i < styleContext.length) {
const guardMask = styleContext[i++];
const valuesCount = styleContext[i++];
const className = styleContext[i++];
if (guardMask & classUpdateMask) {
for (let j = 0; j < valuesCount; j++) {
const bindingIndex = stylingContext[i + j];
let value = lView[bindingIndex];
if (typeof bindingIndex === 'number' && !value) {
continue;
}
setClass(element, className, value);
}
}
i += valuesCount;
}
/*
<div
style="width:200px"
[style.width]="w" # binding index = 33
dir-that-sets-width # binding index = 34
dir-that-sets-width-also> # binding index = 35
...
</div>
*/
tNode.styles [
//...
0b111, 3, 'width', 33, 34, 35, '200px'
//...
];
<div
[style.width]="w"
[style.height]="w"
[style]="{width:'200px'}"
[class.active]="a"
[class.disabled]="d"
[class]=" 'one two three' ">
...
</div>
<!--
templateFn for my-component.ts
style = {width:'200px',height:'100px'};
class = 'foo bar';
-->
<div [style]="myStyles"
[class]="myClasses">
Lots of styling...
</div>
function templateFn(ctx, rf) {
if (rf & CREATE) {
styling();
}
if (rf & UPDATE) {
styleMap(ctx.myStyles);
classMap(ctx.myClasses);
stylingApply();
}
}
stylesArrayMap = [
{...}, 'height', '100px', 'width', '200px',
];
classesArrayMap = [
'foo bar', 'bar', true, 'foo', true,
];
lView = [
// ...
stylesArrayMap, // value for [style]
classesArrayMap, // value for [class]
]
stylesMap1 = [ // binding index = 23
{...}, 'height', '100px', 'width', '200px',
];
stylesMap2 = [ // binding index = 24
{...}, 'color', 'blue',
];
tNode.styles = [
0b0001, 2, '--MAP--', 23, 24,
0b0010, 2, 'color', 21, null,
0b0100, 2, 'width', 20, null,
0b1000, 2, 'height', 22, '999px',
];
<div
#ref
[style]="myStyles"
[style.width]="width"
[style.height]="height"
[class]="{active:true}"
[class.loading]="loading"
transform-item-directive>
...
</div>
window.ng.debugStyles(ref);
// {
// bindings: ['width', ...]
<width-comp
[style]="{width:200px}"
[style.width]="w"
dir-that-sets-width
another-dir-that-sets-width>
What is the width!!!?
</width-comp>
1. [style.width]="w"
2. [style]="{width:200px}"
3. dir-that-sets-width
4. another-dir-that-sets-width
5. width-comp
If nothing is found then either the
static `width` value (`style=""`)
is applied or just `null`.
<width-comp
[style]="{width:200px}"
[style.width]="w"
dir-that-sets-width
another-dir-that-sets-width>
What is the width!!!?
</width-comp>
// this API might change (pseudocode for now)
import {styleDebug} from '@angular/core/testing';
it('should have the correct value for `width`', () => {
// create the element and
// component in your test first
const styles = styleDebug(myTargetElement);
expect(styles.values['width']).toEqual(expectedWidth);
expect(styles.summary['width']).toEqual({
value: expectedWidth,
defaultValue: null,
bindingValues: [,
/* each of the binding values for the element */
]
});
});
// this API might change (pseudocode for now)
const styles = window.ng.debugStyles(targetElement);
console.log(styles.values['width'])
/* => expectedWidth; */
console.log(styles.summary['width']) /* => {
value: expectedWidth,
defaultValue: null,
bindingValues: [,
// each of the binding values for the element
]
}
*/
<!--
templateFn for my-component.ts
-->
<div class="modal-box"
[style.width]="width"
[style.height]="height"
[class.disabled="isDisabled">
My Fancy Modal Box
</div>
<!--
notice the `animate` pipe
-->
<div
class="modal-box"
[style.width]="width | animate:1000"
[style.height]="height | animate:1000"
[class.disabled="isDisabled | animate:1000">
My Fancy Modal Box
</div>