@yearofmoo
github.com/matsko
www.yearofmoo.com
Matias Niemelä
Used to apply inline styles to an element.
With Angular there are various ways to do this using bindings.
Used to add, remove and toggle classes on a DOM element.
With Angular there are various ways to do this using bindings.
style="width: 100px"
style="width: {{ 100 }}px"
[ngStyle]="{width:'100px'}"
[style.width.px]="100"
renderer.setStyle('width', '100px')
element.style.width="..."
[attr.style]="'width: 100px'"
class="foo"
class="{{ 'foo' }}"
[ngClass]="{foo:true}"
[class.foo]="true"
renderer.addClass('foo')
element.classList.add('foo')
[attr.className]="'foo'"
✓ style="width: 100px"
✓ style="width: {{ 100 }}px"
✓ [ngStyle]="{width:'100px}"
✓ [style.width.px]="100"
✗ renderer.setStyle('width', '100px')
✗ element.style.width="..."
~ [attr.style]="'width: 100px'"
✓ class="foo"
✓ class="{{ 'foo' }}"
✓ [ngClass]="{foo:true}"
✓ [class.foo]="true"
✗ renderer.addClass('foo')
✗ element.classList.add('foo')
~ [attr.className]="'foo'"
Style | Class | Description |
---|---|---|
style="..." | class="..." | Handled at compile time |
style="{{ x }}" | class="{{ x }}" | Compile + Runtime change for entire value through the renderer |
[style.value]="x" | [class.name]="x" | Runtime binding that toggles the value through the renderer |
[attr.style]="x" | [attr.className]="x" | Rewrites the entire value through setAttribute |
[ngStyle] | [ngClass] | Special Directives |
<div
style="width:100px"
[style.width]="w1"
[ngStyle]="{width:w2}"
[@widthAnim]="state">
The width gets
updated in the
animation as well!
</div>
<div style="width: 100px; height:200px"
[ngStyle]="{width:w1}"
[style.height.px]="h1"
[@expandCollapse]="state"
class="ready {{ active ? 'active' : '' }}"
[class.disabled]="disabledExp"
[ngClass]="{ ready: false }"
[attr.style]="editMode
? generateEditStyles() : ''">
...
How does all this function?
...
</div>
- [style] and [class] are the future
- Or [style.prop] and [class.name] still do work
- style="{{ x }}" and class="{{ y }}" still do work
- [style], [style.prop], [class], [class.prop] are in core
<!-- styles are handled together -->
<div style="width:100px"
[style]="{width:'200px'}"
[style.width]="
closed ? 0 : null">
<!-- classes too -->
<div class="
active {{loading?'loading':''}}"
[style]="{active:false}"
[style.active]="isActive">
- Template compiler produces instructions
- Instructions read expression values
- values are compared / evaluated
- ONLY the changed values are applied
- No function closures, no intermediate data structures
<!-- template code -->
<div style="width:100px"
[style]="x1"
[style.width]="x2"
[class.active]="c1">
<!-- AOT template code -->
if (flags & 1) {
elementStart(0)
elementStyling(['active'],
['width', 0, 'width', '100px'])
elementEnd();
}
if (flags & 2) {
elementStylingMap(0, null, ctx.x1);
elementStyleProp(0, 0, ctx.x2);
elementClassProp(0, 0, ctx.c1);
elementStylingApply();
}
- Every element has a styling context array
- All classes/styles are stored here for diffing
- Uses bit shifting to allow for efficient lookups
- No key/value maps are ever used
- No classes are used for style/class storage
// [style]="{width}", [class.active], style="width:100px"
StylingContext = [
// configuration values
null, null, [null, null, '100px'],
MASTER_FLAG, 1, 0, null, null,
// width map value
FLAG, 'width', x1, null,
// active class map value
FLAG, 'active', null, null,
// width prop value
FLAG, 'width', x2, null,
// active class prop value
FLAG, 'active', c1, null
];
//
// x2 is changed to '200px'
// <div [style.width]="x2"
//
StylingContext = [
// ...
~MASTER_FLAG, //...
// width map value
FLAG, 'width', x1, null,
//...
// width prop value
~FLAG, 'width', x3, null, // FLAG is set to dirty
//...
];
// the FLAG values contain the data
// 32 bit number
FLAG =
// initial value index addr (13 bits)
0000000000000
// map/prop value index addr (13 bits)
0000000000000
// control flags (5 bits)
00000;
initialIndex = (FLAG >> 13) & ob1111111111111;
mapOrPropIndex = (FLAG >> 26) & ob1111111111111;
config = FLAG & 0b11111;
function elementStylingApply() {
// loops over the entire array
for (let i = VALUES_START_INDEX;
i < context.length; i++) {
if (FLAG & DIRTY) {
if (FLAG & CLASS_BASED) {
addClass(element, 'active');
} else {
setStyle(element, 'width', c2);
}
FLAG &= ~DIRTY;
}
}
}
Operation | Normal Case | Worst Case |
---|---|---|
elementStyling (TNode) | o(k) | o(k) |
elementStyling (LNode) | o(k) | o(k) |
elementStyleMap | o(k) | * o(n^2) |
elementStyleProp | o(1) | o(n-k) |
elementClassProp | o(1) | o(n-k) |
elementStylingApply | o(0) or o(k) | o(n) |
n = total styling values / k = size of map or changes
* = this only happens once
Operation | Normal Case | Worst Case |
---|---|---|
initial render | o(k) | o(n^2) |
re-render (no changes) | o(1) | o(1) |
re-render (changes) | o(k) | o(n) |
n = total styling values / k = size of map or changes
Operation | [ngClass] | [class] |
---|---|---|
initial render | ~100% | ~100% |
re-render (no changes) | ~100% | ~ 50% |
re-render (changes) | ~100% | ~ 50% |
n = total styling values / k = size of map or changes
Operation | NgClass | [class] |
---|---|---|
initial render | 100% | 50% |
re-render (no changes) | 100% | 50% |
n = total styling values / k = size of map or changes
- Animations are just styles occurring over time
- They should act as an extension to styling
- [style] and [class] bindings should animate easily
@Component({
styles: [`
.box { height: 200px; }
`],
template: `
<header (click)="toggle()">{{ title }}</header>
<div class="box" [style.height]="
isOpen ? '0px' : null">
I will open and close
</div>
`
})
class OpenClosePanel {
title = 'OpenClose';
toggle() {
this.isOpen = !this.isOpen;
}
}
<!-- simple style transitions -->
<div class="box" [style.height]="
(isOpen ? '0px' : null)
| animate:'300ms ease-out'">
I will open and close
</div>
<div
style="width:100px; height:100px"
[style]="{width:w} | animate:'300ms ease-out'"
[style.opacity]="o | animate:'500ms ease-out'"
[class]="{active:active}|animate:1000"
[class.disabled]="
isDisabled | animate:'300ms 0.5s cubic-bezier()">
... this element sure knows how to animate
</div>
- Works on any style and class
- Works with media queries
- Automatically computes heights / widths
- Fully dynamic
- Animations can be lazy loaded