Skip to content
This repository was archived by the owner on Jan 6, 2025. It is now read-only.

feat(core): add memoization to style generation #888

Merged
merged 3 commits into from
Nov 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions src/lib/core/base/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,22 @@ export abstract class BaseDirective implements OnDestroy, OnChanges {
return this._elementRef.nativeElement;
}

/** Add styles to the element using predefined style builder */
protected addStyles(input: string, parent?: Object) {
const styles: StyleDefinition = this._styleBuilder!.buildStyles(input, parent);
this._applyStyleToElement(styles);
const builder = this._styleBuilder!;
const useCache = builder.shouldCache;

let genStyles: StyleDefinition | undefined = this._styleCache.get(input);

if (!genStyles || !useCache) {
genStyles = builder.buildStyles(input, parent);
if (useCache) {
this._styleCache.set(input, genStyles);
}
}

this._applyStyleToElement(genStyles);
builder.sideEffect(input, genStyles, parent);
}

/** Access the current value (if any) of the @Input property */
Expand Down Expand Up @@ -246,4 +259,7 @@ export abstract class BaseDirective implements OnDestroy, OnChanges {
* getComputedStyle() during ngOnInit().
*/
protected _hasInitialized = false;

/** Cache map for style computation */
protected _styleCache: Map<string, StyleDefinition> = new Map();
}
16 changes: 14 additions & 2 deletions src/lib/core/style-builder/style-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,22 @@
* 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 {Injectable} from '@angular/core';
import {StyleDefinition} from '../style-utils/style-utils';

@Injectable()
/** A class that encapsulates CSS style generation for common directives */
export abstract class StyleBuilder {

/** Whether to cache the generated output styles */
shouldCache = true;

/** Build the styles given an input string and configuration object from a host */
abstract buildStyles(input: string, parent?: Object): StyleDefinition;

/**
* Run a side effect computation given the input string and the computed styles
* from the build task and the host configuration object
* NOTE: This should be a no-op unless an algorithm is provided in a subclass
*/
sideEffect(_input: string, _styles: StyleDefinition, _parent?: Object) {
}
}
20 changes: 12 additions & 8 deletions src/lib/flex/flex-align/flex-align.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,28 +21,28 @@ import {
MediaMonitor,
StyleBuilder,
StyleDefinition,
StyleUtils
StyleUtils,
} from '@angular/flex-layout/core';

@Injectable({providedIn: 'root'})
export class FlexAlignStyleBuilder implements StyleBuilder {
buildStyles(input: string): StyleDefinition {
const css: {[key: string]: string | number} = {};
export class FlexAlignStyleBuilder extends StyleBuilder {
buildStyles(input: string) {
const styles: StyleDefinition = {};

// Cross-axis
switch (input) {
case 'start':
css['align-self'] = 'flex-start';
styles['align-self'] = 'flex-start';
break;
case 'end':
css['align-self'] = 'flex-end';
styles['align-self'] = 'flex-end';
break;
default:
css['align-self'] = input;
styles['align-self'] = input;
break;
}

return css;
return styles;
}
}

Expand Down Expand Up @@ -125,4 +125,8 @@ export class FlexAlignDirective extends BaseDirective implements OnInit, OnChang

this.addStyles(value && (value + '') || '');
}

protected _styleCache = flexAlignCache;
}

const flexAlignCache: Map<string, StyleDefinition> = new Map();
8 changes: 6 additions & 2 deletions src/lib/flex/flex-fill/flex-fill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ const FLEX_FILL_CSS = {
};

@Injectable({providedIn: 'root'})
export class FlexFillStyleBuilder implements StyleBuilder {
buildStyles(_input: string): StyleDefinition {
export class FlexFillStyleBuilder extends StyleBuilder {
buildStyles(_input: string) {
return FLEX_FILL_CSS;
}
}
Expand All @@ -47,4 +47,8 @@ export class FlexFillDirective extends BaseDirective {
super(monitor, elRef, styleUtils, styleBuilder);
this.addStyles('');
}

protected _styleCache = flexFillCache;
}

const flexFillCache: Map<string, StyleDefinition> = new Map();
2 changes: 1 addition & 1 deletion src/lib/flex/flex-offset/flex-offset.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ describe('flex-offset directive', () => {
});

@Injectable({providedIn: FlexModule})
export class MockFlexOffsetStyleBuilder implements StyleBuilder {
export class MockFlexOffsetStyleBuilder extends StyleBuilder {
buildStyles(_input: string) {
return {'margin-top': '10px'};
}
Expand Down
25 changes: 20 additions & 5 deletions src/lib/flex/flex-offset/flex-offset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,24 @@ import {Subscription} from 'rxjs';
import {Layout, LayoutDirective} from '../layout/layout';
import {isFlowHorizontal} from '../../utils/layout-validator';

interface FlexOffsetParent {
export interface FlexOffsetParent {
layout: string;
isRtl: boolean;
}

@Injectable({providedIn: 'root'})
export class FlexOffsetStyleBuilder implements StyleBuilder {
buildStyles(offset: string, parent: FlexOffsetParent): StyleDefinition {
export class FlexOffsetStyleBuilder extends StyleBuilder {
buildStyles(offset: string, parent: FlexOffsetParent) {
const isPercent = String(offset).indexOf('%') > -1;
const isPx = String(offset).indexOf('px') > -1;
if (!isPx && !isPercent && !isNaN(+offset)) {
offset = offset + '%';
}
const horizontalLayoutKey = parent.isRtl ? 'margin-right' : 'margin-left';

return isFlowHorizontal(parent.layout) ? {[horizontalLayoutKey]: `${offset}`} :
const styles = isFlowHorizontal(parent.layout) ? {[horizontalLayoutKey]: `${offset}`} :
{'margin-top': `${offset}`};

return styles;
}
}

Expand Down Expand Up @@ -185,6 +186,20 @@ export class FlexOffsetDirective extends BaseDirective implements OnInit, OnChan
// The flex-direction of this element's flex container. Defaults to 'row'.
const layout = this._getFlexFlowDirection(this.parentElement, true);
const isRtl = this._directionality.value === 'rtl';
if (layout === 'row' && isRtl) {
this._styleCache = flexOffsetCacheRowRtl;
} else if (layout === 'row' && !isRtl) {
this._styleCache = flexOffsetCacheRowLtr;
} else if (layout === 'column' && isRtl) {
this._styleCache = flexOffsetCacheColumnRtl;
} else if (layout === 'column' && !isRtl) {
this._styleCache = flexOffsetCacheColumnLtr;
}
this.addStyles((value && (value + '') || ''), {layout, isRtl});
}
}

const flexOffsetCacheRowRtl: Map<string, StyleDefinition> = new Map();
const flexOffsetCacheColumnRtl: Map<string, StyleDefinition> = new Map();
const flexOffsetCacheRowLtr: Map<string, StyleDefinition> = new Map();
const flexOffsetCacheColumnLtr: Map<string, StyleDefinition> = new Map();
13 changes: 9 additions & 4 deletions src/lib/flex/flex-order/flex-order.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@ import {
MediaMonitor,
StyleBuilder,
StyleDefinition,
StyleUtils
StyleUtils,
} from '@angular/flex-layout/core';

@Injectable({providedIn: 'root'})
export class FlexOrderStyleBuilder implements StyleBuilder {
buildStyles(value: string): StyleDefinition {
export class FlexOrderStyleBuilder extends StyleBuilder {
buildStyles(value: string) {
const val = parseInt(value, 10);
return {order: isNaN(val) ? 0 : val};
const styles = {order: isNaN(val) ? 0 : val};
return styles;
}
}

Expand Down Expand Up @@ -108,4 +109,8 @@ export class FlexOrderDirective extends BaseDirective implements OnInit, OnChang

this.addStyles(value || '');
}

protected _styleCache = flexOrderCache;
}

const flexOrderCache: Map<string, StyleDefinition> = new Map();
11 changes: 8 additions & 3 deletions src/lib/flex/flex/flex.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,7 @@ describe('flex directive', () => {
});

describe('with column basis zero disabled', () => {
let styleBuilder: FlexStyleBuilder;
beforeEach(() => {
jasmine.addMatchers(customMatchers);

Expand All @@ -875,16 +876,20 @@ describe('flex directive', () => {
});
});

it('should set flex basis to auto', async(() => {
it('should set flex basis to auto', () => {
componentWithTemplate(`
<div fxLayout='column'>
<div fxFlex></div>
</div>
`);
styleBuilder = TestBed.get(FlexStyleBuilder);

// Reset the cache because the layout config is only set at startup
styleBuilder.shouldCache = false;
fixture.detectChanges();
let element = queryFor(fixture, '[fxFlex]')[0];
expectEl(element).toHaveStyle({'flex': '1 1 auto'}, styler);
}));
});
});

describe('with custom builder', () => {
Expand Down Expand Up @@ -926,7 +931,7 @@ describe('flex directive', () => {
});

@Injectable({providedIn: FlexModule})
export class MockFlexStyleBuilder implements StyleBuilder {
export class MockFlexStyleBuilder extends StyleBuilder {
buildStyles(_input: string) {
return {'flex': '1 1 30%'};
}
Expand Down
39 changes: 26 additions & 13 deletions src/lib/flex/flex/flex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,16 @@ export type FlexBasisAlias = 'grow' | 'initial' | 'auto' | 'none' | 'nogrow' | '
interface FlexBuilderParent {
direction: string;
hasWrap: boolean;
useColumnBasisZero: boolean | undefined;
}

@Injectable({providedIn: 'root'})
export class FlexStyleBuilder implements StyleBuilder {
buildStyles(input: string, parent: FlexBuilderParent): StyleDefinition {
let grow: string | number;
let shrink: string | number;
let basis: string | number;
[grow, shrink, basis] = input.split('_');
export class FlexStyleBuilder extends StyleBuilder {
constructor(@Inject(LAYOUT_CONFIG) protected layoutConfig: LayoutConfigOptions) {
super();
}
buildStyles(input: string, parent: FlexBuilderParent) {
let [grow, shrink, ...basisParts]: (string|number)[] = input.split(' ');
let basis = basisParts.join(' ');

// The flex-direction of this element's flex container. Defaults to 'row'.
const direction = (parent.direction.indexOf('column') > -1) ? 'column' : 'row';
Expand Down Expand Up @@ -97,7 +97,7 @@ export class FlexStyleBuilder implements StyleBuilder {
};
switch (basis || '') {
case '':
const useColumnBasisZero = parent.useColumnBasisZero !== false;
const useColumnBasisZero = this.layoutConfig.useColumnBasisZero !== false;
basis = direction === 'row' ? '0%' : (useColumnBasisZero ? '0.000000001px' : 'auto');
break;
case 'initial': // default
Expand Down Expand Up @@ -192,7 +192,7 @@ export class FlexStyleBuilder implements StyleBuilder {
}
}

return extendObject(css, {'box-sizing': 'border-box'});
return extendObject(css, {'box-sizing': 'border-box'}) as StyleDefinition;
}
}

Expand Down Expand Up @@ -310,12 +310,25 @@ export class FlexDirective extends BaseDirective implements OnInit, OnChanges, O
flexBasis = this._mqActivation.activatedInput;
}

let basis = String(flexBasis).replace(';', '');
let parts = validateBasis(basis, this._queryInput('grow'), this._queryInput('shrink'));
const basis = String(flexBasis).replace(';', '');
const parts = validateBasis(basis, this._queryInput('grow'), this._queryInput('shrink'));
const addFlexToParent = this.layoutConfig.addFlexToParent !== false;
const direction = this._getFlexFlowDirection(this.parentElement, addFlexToParent);
const hasWrap = this._layout && this._layout.wrap;
const useColumnBasisZero = this.layoutConfig.useColumnBasisZero;
this.addStyles(parts.join('_'), {direction, hasWrap, useColumnBasisZero});
if (direction === 'row' && hasWrap) {
this._styleCache = flexRowWrapCache;
} else if (direction === 'row' && !hasWrap) {
this._styleCache = flexRowCache;
} else if (direction === 'column' && hasWrap) {
this._styleCache = flexColumnWrapCache;
} else if (direction === 'column' && !hasWrap) {
this._styleCache = flexColumnCache;
}
this.addStyles(parts.join(' '), {direction, hasWrap});
}
}

const flexRowCache: Map<string, StyleDefinition> = new Map();
const flexColumnCache: Map<string, StyleDefinition> = new Map();
const flexRowWrapCache: Map<string, StyleDefinition> = new Map();
const flexColumnWrapCache: Map<string, StyleDefinition> = new Map();
3 changes: 2 additions & 1 deletion src/lib/flex/layout-align/layout-align.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,8 @@ describe('layout-align directive', () => {
});

@Injectable({providedIn: FlexModule})
export class MockLayoutAlignStyleBuilder implements StyleBuilder {
export class MockLayoutAlignStyleBuilder extends StyleBuilder {
shouldCache = false;
buildStyles(_input: string) {
return {'justify-content': 'flex-end'};
}
Expand Down
18 changes: 11 additions & 7 deletions src/lib/flex/layout-align/layout-align.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,22 @@ import {
MediaMonitor,
StyleBuilder,
StyleDefinition,
StyleUtils
StyleUtils,
} from '@angular/flex-layout/core';
import {Subscription} from 'rxjs';

import {extendObject} from '../../utils/object-extend';
import {Layout, LayoutDirective} from '../layout/layout';
import {LAYOUT_VALUES, isFlowHorizontal} from '../../utils/layout-validator';

interface LayoutAlignParent {
export interface LayoutAlignParent {
layout: string;
}

@Injectable({providedIn: 'root'})
export class LayoutAlignStyleBuilder implements StyleBuilder {
buildStyles(align: string, parent: LayoutAlignParent): StyleDefinition {
let css: {[key: string]: string} = {},
[mainAxis, crossAxis] = align.split(' ');
export class LayoutAlignStyleBuilder extends StyleBuilder {
buildStyles(align: string, parent: LayoutAlignParent) {
const css: StyleDefinition = {}, [mainAxis, crossAxis] = align.split(' ');

// Main axis
switch (mainAxis) {
Expand Down Expand Up @@ -105,7 +104,7 @@ export class LayoutAlignStyleBuilder implements StyleBuilder {
!isFlowHorizontal(parent.layout) ? '100%' : null : null,
'max-height': crossAxis === 'stretch' ?
isFlowHorizontal(parent.layout) ? '100%' : null : null,
});
}) as StyleDefinition;
}
}

Expand Down Expand Up @@ -204,6 +203,8 @@ export class LayoutAlignDirective extends BaseDirective implements OnInit, OnCha
}

const layout = this._layout || 'row';
this._styleCache = layout === 'row' ?
layoutAlignHorizontalCache : layoutAlignVerticalCache;
this.addStyles(value || '', {layout});
}

Expand All @@ -223,3 +224,6 @@ export class LayoutAlignDirective extends BaseDirective implements OnInit, OnCha
this.addStyles(value, {layout: this._layout || 'row'});
}
}

const layoutAlignHorizontalCache: Map<string, StyleDefinition> = new Map();
const layoutAlignVerticalCache: Map<string, StyleDefinition> = new Map();
2 changes: 1 addition & 1 deletion src/lib/flex/layout-gap/layout-gap.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,7 @@ describe('layout-gap directive', () => {
});

@Injectable({providedIn: FlexModule})
export class MockLayoutGapStyleBuilder implements StyleBuilder {
export class MockLayoutGapStyleBuilder extends StyleBuilder {
buildStyles(_input: string) {
return {'margin-top': '12px'};
}
Expand Down
Loading