Skip to content

Commit

Permalink
feat(core): add ability to override style building (#884)
Browse files Browse the repository at this point in the history
This change decouples the style-generation from the actual directives, meaning that the library is now composed of the following:

* Directives that respond to inputs and `matchMedia` events
* Style generation providers that return styles when triggered by directives

This allows for end-users or library authors to provide their own unique style generation (even by borrowing or extending our existing library code) for any directive. This is entirely non-mandatory for use of the `BaseDirective`, since the `BaseDirective` need not always use a de-coupled style provider to function.

The canonical example is the following:
```ts
@Injectable()
export class CustomStyleBuilder extends StyleBuilder {
  buildStyles(input: string) {
	return {
		'style1': 'value1',
	};
  }
}

@NgModule({
	...
	providers: [
		provide: <the style builder to orverride, e.g. FlexStyleBuilder>,
		useClass: CustomStyleBuilder,
	],
})
export class MyAppModule {}
```

Fixes #689
  • Loading branch information
CaerusKaru authored Nov 14, 2018
1 parent 3a0ec5d commit 9148e87
Show file tree
Hide file tree
Showing 16 changed files with 738 additions and 391 deletions.
11 changes: 9 additions & 2 deletions src/lib/core/base/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
import {ResponsiveActivation, KeyOptions} from '../responsive-activation/responsive-activation';
import {MediaMonitor} from '../media-monitor/media-monitor';
import {MediaQuerySubscriber} from '../media-change';
import {StyleBuilder} from '../style-builder/style-builder';

/** Abstract base class for the Layout API styling directives. */
export abstract class BaseDirective implements OnDestroy, OnChanges {
Expand Down Expand Up @@ -58,7 +59,8 @@ export abstract class BaseDirective implements OnDestroy, OnChanges {

protected constructor(protected _mediaMonitor: MediaMonitor,
protected _elementRef: ElementRef,
protected _styler: StyleUtils) {
protected _styler: StyleUtils,
protected _styleBuilder?: StyleBuilder) {
}

/**
Expand Down Expand Up @@ -107,6 +109,11 @@ export abstract class BaseDirective implements OnDestroy, OnChanges {
return this._elementRef.nativeElement;
}

protected addStyles(input: string, parent?: Object) {
const styles: StyleDefinition = this._styleBuilder!.buildStyles(input, parent);
this._applyStyleToElement(styles);
}

/** Access the current value (if any) of the @Input property */
protected _queryInput(key: string) {
return this._inputMap[key];
Expand Down Expand Up @@ -206,7 +213,7 @@ export abstract class BaseDirective implements OnDestroy, OnChanges {
}

/** Special accessor to query for all child 'element' nodes regardless of type, class, etc */
protected get childrenNodes() {
protected get childrenNodes(): HTMLElement[] {
const obj = this.nativeElement.children;
const buffer: any[] = [];

Expand Down
1 change: 1 addition & 0 deletions src/lib/core/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ export * from './observable-media/index';

export * from './responsive-activation/responsive-activation';
export * from './style-utils/style-utils';
export * from './style-builder/style-builder';
export * from './basis-validator/basis-validator';
14 changes: 14 additions & 0 deletions src/lib/core/style-builder/style-builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* 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()
export abstract class StyleBuilder {
abstract buildStyles(input: string, parent?: Object): StyleDefinition;
}
57 changes: 34 additions & 23 deletions src/lib/flex/flex-align/flex-align.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,38 @@ import {
OnChanges,
OnDestroy,
SimpleChanges,
Injectable,
} from '@angular/core';
import {BaseDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core';
import {
BaseDirective,
MediaChange,
MediaMonitor,
StyleBuilder,
StyleDefinition,
StyleUtils
} from '@angular/flex-layout/core';

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

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

return css;
}
}

/**
* 'flex-align' flexbox styling directive
Expand Down Expand Up @@ -53,8 +82,9 @@ export class FlexAlignDirective extends BaseDirective implements OnInit, OnChang
/* tslint:enable */
constructor(monitor: MediaMonitor,
elRef: ElementRef,
styleUtils: StyleUtils) {
super(monitor, elRef, styleUtils);
styleUtils: StyleUtils,
styleBuilder: FlexAlignStyleBuilder) {
super(monitor, elRef, styleUtils, styleBuilder);
}


Expand Down Expand Up @@ -93,25 +123,6 @@ export class FlexAlignDirective extends BaseDirective implements OnInit, OnChang
value = this._mqActivation.activatedInput;
}

this._applyStyleToElement(this._buildCSS(value));
}

protected _buildCSS(align: string | number = '') {
let css: {[key: string]: string | number} = {};

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

return css;
this.addStyles(value && (value + '') || '');
}
}
25 changes: 19 additions & 6 deletions src/lib/flex/flex-fill/flex-fill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@
* 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 {Directive, ElementRef} from '@angular/core';
import {BaseDirective, MediaMonitor, StyleUtils} from '@angular/flex-layout/core';

import {Directive, ElementRef, Injectable} from '@angular/core';
import {
BaseDirective,
MediaMonitor,
StyleBuilder,
StyleDefinition,
StyleUtils,
} from '@angular/flex-layout/core';

const FLEX_FILL_CSS = {
'margin': 0,
Expand All @@ -17,6 +22,13 @@ const FLEX_FILL_CSS = {
'min-height': '100%'
};

@Injectable({providedIn: 'root'})
export class FlexFillStyleBuilder implements StyleBuilder {
buildStyles(_input: string): StyleDefinition {
return FLEX_FILL_CSS;
}
}

/**
* 'fxFill' flexbox styling directive
* Maximizes width and height of element in a layout container
Expand All @@ -30,8 +42,9 @@ const FLEX_FILL_CSS = {
export class FlexFillDirective extends BaseDirective {
constructor(monitor: MediaMonitor,
public elRef: ElementRef,
styleUtils: StyleUtils) {
super(monitor, elRef, styleUtils);
this._applyStyleToElement(FLEX_FILL_CSS);
styleUtils: StyleUtils,
styleBuilder: FlexFillStyleBuilder) {
super(monitor, elRef, styleUtils, styleBuilder);
this.addStyles('');
}
}
56 changes: 53 additions & 3 deletions src/lib/flex/flex-offset/flex-offset.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,16 @@
* 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 {Component, PLATFORM_ID} from '@angular/core';
import {Component, Injectable, PLATFORM_ID} from '@angular/core';
import {CommonModule, isPlatformServer} from '@angular/common';
import {ComponentFixture, inject, TestBed} from '@angular/core/testing';
import {async, ComponentFixture, inject, TestBed} from '@angular/core/testing';
import {DIR_DOCUMENT} from '@angular/cdk/bidi';
import {SERVER_TOKEN, StyleUtils} from '@angular/flex-layout/core';
import {
MockMatchMediaProvider,
SERVER_TOKEN,
StyleBuilder,
StyleUtils,
} from '@angular/flex-layout/core';

import {FlexLayoutModule} from '../../module';
import {customMatchers} from '../../utils/testing/custom-matchers';
Expand All @@ -19,6 +24,8 @@ import {
expectEl,
expectNativeEl,
} from '../../utils/testing/helpers';
import {FlexModule} from '../module';
import {FlexOffsetStyleBuilder} from './flex-offset';

describe('flex-offset directive', () => {
let fixture: ComponentFixture<any>;
Expand Down Expand Up @@ -177,8 +184,51 @@ describe('flex-offset directive', () => {

});

describe('with custom builder', () => {
beforeEach(() => {
jasmine.addMatchers(customMatchers);

// Configure testbed to prepare services
TestBed.configureTestingModule({
imports: [
CommonModule,
FlexLayoutModule.withConfig({
useColumnBasisZero: false,
serverLoaded: true,
}),
],
declarations: [TestFlexComponent],
providers: [
MockMatchMediaProvider,
{
provide: FlexOffsetStyleBuilder,
useClass: MockFlexOffsetStyleBuilder,
}
]
});
});

it('should set flex offset not to input', async(() => {
componentWithTemplate(`
<div fxLayout='column'>
<div fxFlexOffset="25"></div>
</div>
`);
fixture.detectChanges();
let element = queryFor(fixture, '[fxFlexOffset]')[0];
expectEl(element).toHaveStyle({'margin-top': '10px'}, styler);
}));
});

});

@Injectable({providedIn: FlexModule})
export class MockFlexOffsetStyleBuilder implements StyleBuilder {
buildStyles(_input: string) {
return {'margin-top': '10px'};
}
}


// *****************************************************************
// Template Component
Expand Down
44 changes: 27 additions & 17 deletions src/lib/flex/flex-offset/flex-offset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ import {
Optional,
SimpleChanges,
SkipSelf,
Injectable,
} from '@angular/core';
import {Directionality} from '@angular/cdk/bidi';
import {
BaseDirective,
MediaChange,
MediaMonitor,
StyleBuilder,
StyleDefinition,
StyleUtils,
} from '@angular/flex-layout/core';
Expand All @@ -29,6 +31,26 @@ import {Subscription} from 'rxjs';
import {Layout, LayoutDirective} from '../layout/layout';
import {isFlowHorizontal} from '../../utils/layout-validator';

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

@Injectable({providedIn: 'root'})
export class FlexOffsetStyleBuilder implements StyleBuilder {
buildStyles(offset: string, parent: FlexOffsetParent): StyleDefinition {
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}`} :
{'margin-top': `${offset}`};
}
}

/**
* 'flex-offset' flexbox styling directive
* Configures the 'margin-left' of the element in a layout container
Expand Down Expand Up @@ -65,8 +87,9 @@ export class FlexOffsetDirective extends BaseDirective implements OnInit, OnChan
elRef: ElementRef,
@Optional() @SkipSelf() protected _container: LayoutDirective,
private _directionality: Directionality,
styleUtils: StyleUtils) {
super(monitor, elRef, styleUtils);
styleUtils: StyleUtils,
styleBuilder: FlexOffsetStyleBuilder) {
super(monitor, elRef, styleUtils, styleBuilder);

this._directionWatcher =
this._directionality.change.subscribe(this._updateWithValue.bind(this));
Expand Down Expand Up @@ -159,22 +182,9 @@ export class FlexOffsetDirective extends BaseDirective implements OnInit, OnChan
value = this._mqActivation.activatedInput;
}

this._applyStyleToElement(this._buildCSS(value));
}

protected _buildCSS(offset: string|number = ''): StyleDefinition {
let isPercent = String(offset).indexOf('%') > -1;
let isPx = String(offset).indexOf('px') > -1;
if (!isPx && !isPercent && !isNaN(+offset)) {
offset = offset + '%';
}

// The flex-direction of this element's flex container. Defaults to 'row'.
const isRtl = this._directionality.value === 'rtl';
const layout = this._getFlexFlowDirection(this.parentElement, true);
const horizontalLayoutKey = isRtl ? 'margin-right' : 'margin-left';

return isFlowHorizontal(layout) ? {[horizontalLayoutKey]: `${offset}`} :
{'margin-top': `${offset}`};
const isRtl = this._directionality.value === 'rtl';
this.addStyles((value && (value + '') || ''), {layout, isRtl});
}
}
30 changes: 20 additions & 10 deletions src/lib/flex/flex-order/flex-order.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,24 @@ import {
OnChanges,
OnDestroy,
SimpleChanges,
Injectable,
} from '@angular/core';
import {BaseDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core';
import {
BaseDirective,
MediaChange,
MediaMonitor,
StyleBuilder,
StyleDefinition,
StyleUtils
} from '@angular/flex-layout/core';

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

/**
* 'flex-order' flexbox styling directive
Expand Down Expand Up @@ -51,8 +66,9 @@ export class FlexOrderDirective extends BaseDirective implements OnInit, OnChang
/* tslint:enable */
constructor(monitor: MediaMonitor,
elRef: ElementRef,
styleUtils: StyleUtils) {
super(monitor, elRef, styleUtils);
styleUtils: StyleUtils,
styleBuilder: FlexOrderStyleBuilder) {
super(monitor, elRef, styleUtils, styleBuilder);
}

// *********************************************
Expand Down Expand Up @@ -90,12 +106,6 @@ export class FlexOrderDirective extends BaseDirective implements OnInit, OnChang
value = this._mqActivation.activatedInput;
}

this._applyStyleToElement(this._buildCSS(value));
}


protected _buildCSS(value: string = '') {
const val = parseInt(value, 10);
return {order: isNaN(val) ? 0 : val};
this.addStyles(value || '');
}
}
Loading

0 comments on commit 9148e87

Please sign in to comment.