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

Commit 9148e87

Browse files
authored
feat(core): add ability to override style building (#884)
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
1 parent 3a0ec5d commit 9148e87

File tree

16 files changed

+738
-391
lines changed

16 files changed

+738
-391
lines changed

src/lib/core/base/base.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
import {ResponsiveActivation, KeyOptions} from '../responsive-activation/responsive-activation';
2222
import {MediaMonitor} from '../media-monitor/media-monitor';
2323
import {MediaQuerySubscriber} from '../media-change';
24+
import {StyleBuilder} from '../style-builder/style-builder';
2425

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

5960
protected constructor(protected _mediaMonitor: MediaMonitor,
6061
protected _elementRef: ElementRef,
61-
protected _styler: StyleUtils) {
62+
protected _styler: StyleUtils,
63+
protected _styleBuilder?: StyleBuilder) {
6264
}
6365

6466
/**
@@ -107,6 +109,11 @@ export abstract class BaseDirective implements OnDestroy, OnChanges {
107109
return this._elementRef.nativeElement;
108110
}
109111

112+
protected addStyles(input: string, parent?: Object) {
113+
const styles: StyleDefinition = this._styleBuilder!.buildStyles(input, parent);
114+
this._applyStyleToElement(styles);
115+
}
116+
110117
/** Access the current value (if any) of the @Input property */
111118
protected _queryInput(key: string) {
112119
return this._inputMap[key];
@@ -206,7 +213,7 @@ export abstract class BaseDirective implements OnDestroy, OnChanges {
206213
}
207214

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

src/lib/core/public-api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ export * from './observable-media/index';
2020

2121
export * from './responsive-activation/responsive-activation';
2222
export * from './style-utils/style-utils';
23+
export * from './style-builder/style-builder';
2324
export * from './basis-validator/basis-validator';
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
import {Injectable} from '@angular/core';
9+
import {StyleDefinition} from '../style-utils/style-utils';
10+
11+
@Injectable()
12+
export abstract class StyleBuilder {
13+
abstract buildStyles(input: string, parent?: Object): StyleDefinition;
14+
}

src/lib/flex/flex-align/flex-align.ts

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,38 @@ import {
1313
OnChanges,
1414
OnDestroy,
1515
SimpleChanges,
16+
Injectable,
1617
} from '@angular/core';
17-
import {BaseDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core';
18+
import {
19+
BaseDirective,
20+
MediaChange,
21+
MediaMonitor,
22+
StyleBuilder,
23+
StyleDefinition,
24+
StyleUtils
25+
} from '@angular/flex-layout/core';
26+
27+
@Injectable({providedIn: 'root'})
28+
export class FlexAlignStyleBuilder implements StyleBuilder {
29+
buildStyles(input: string): StyleDefinition {
30+
const css: {[key: string]: string | number} = {};
1831

32+
// Cross-axis
33+
switch (input) {
34+
case 'start':
35+
css['align-self'] = 'flex-start';
36+
break;
37+
case 'end':
38+
css['align-self'] = 'flex-end';
39+
break;
40+
default:
41+
css['align-self'] = input;
42+
break;
43+
}
44+
45+
return css;
46+
}
47+
}
1948

2049
/**
2150
* 'flex-align' flexbox styling directive
@@ -53,8 +82,9 @@ export class FlexAlignDirective extends BaseDirective implements OnInit, OnChang
5382
/* tslint:enable */
5483
constructor(monitor: MediaMonitor,
5584
elRef: ElementRef,
56-
styleUtils: StyleUtils) {
57-
super(monitor, elRef, styleUtils);
85+
styleUtils: StyleUtils,
86+
styleBuilder: FlexAlignStyleBuilder) {
87+
super(monitor, elRef, styleUtils, styleBuilder);
5888
}
5989

6090

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

96-
this._applyStyleToElement(this._buildCSS(value));
97-
}
98-
99-
protected _buildCSS(align: string | number = '') {
100-
let css: {[key: string]: string | number} = {};
101-
102-
// Cross-axis
103-
switch (align) {
104-
case 'start':
105-
css['align-self'] = 'flex-start';
106-
break;
107-
case 'end':
108-
css['align-self'] = 'flex-end';
109-
break;
110-
default:
111-
css['align-self'] = align;
112-
break;
113-
}
114-
115-
return css;
126+
this.addStyles(value && (value + '') || '');
116127
}
117128
}

src/lib/flex/flex-fill/flex-fill.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8-
import {Directive, ElementRef} from '@angular/core';
9-
import {BaseDirective, MediaMonitor, StyleUtils} from '@angular/flex-layout/core';
10-
8+
import {Directive, ElementRef, Injectable} from '@angular/core';
9+
import {
10+
BaseDirective,
11+
MediaMonitor,
12+
StyleBuilder,
13+
StyleDefinition,
14+
StyleUtils,
15+
} from '@angular/flex-layout/core';
1116

1217
const FLEX_FILL_CSS = {
1318
'margin': 0,
@@ -17,6 +22,13 @@ const FLEX_FILL_CSS = {
1722
'min-height': '100%'
1823
};
1924

25+
@Injectable({providedIn: 'root'})
26+
export class FlexFillStyleBuilder implements StyleBuilder {
27+
buildStyles(_input: string): StyleDefinition {
28+
return FLEX_FILL_CSS;
29+
}
30+
}
31+
2032
/**
2133
* 'fxFill' flexbox styling directive
2234
* Maximizes width and height of element in a layout container
@@ -30,8 +42,9 @@ const FLEX_FILL_CSS = {
3042
export class FlexFillDirective extends BaseDirective {
3143
constructor(monitor: MediaMonitor,
3244
public elRef: ElementRef,
33-
styleUtils: StyleUtils) {
34-
super(monitor, elRef, styleUtils);
35-
this._applyStyleToElement(FLEX_FILL_CSS);
45+
styleUtils: StyleUtils,
46+
styleBuilder: FlexFillStyleBuilder) {
47+
super(monitor, elRef, styleUtils, styleBuilder);
48+
this.addStyles('');
3649
}
3750
}

src/lib/flex/flex-offset/flex-offset.spec.ts

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,16 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8-
import {Component, PLATFORM_ID} from '@angular/core';
8+
import {Component, Injectable, PLATFORM_ID} from '@angular/core';
99
import {CommonModule, isPlatformServer} from '@angular/common';
10-
import {ComponentFixture, inject, TestBed} from '@angular/core/testing';
10+
import {async, ComponentFixture, inject, TestBed} from '@angular/core/testing';
1111
import {DIR_DOCUMENT} from '@angular/cdk/bidi';
12-
import {SERVER_TOKEN, StyleUtils} from '@angular/flex-layout/core';
12+
import {
13+
MockMatchMediaProvider,
14+
SERVER_TOKEN,
15+
StyleBuilder,
16+
StyleUtils,
17+
} from '@angular/flex-layout/core';
1318

1419
import {FlexLayoutModule} from '../../module';
1520
import {customMatchers} from '../../utils/testing/custom-matchers';
@@ -19,6 +24,8 @@ import {
1924
expectEl,
2025
expectNativeEl,
2126
} from '../../utils/testing/helpers';
27+
import {FlexModule} from '../module';
28+
import {FlexOffsetStyleBuilder} from './flex-offset';
2229

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

178185
});
179186

187+
describe('with custom builder', () => {
188+
beforeEach(() => {
189+
jasmine.addMatchers(customMatchers);
190+
191+
// Configure testbed to prepare services
192+
TestBed.configureTestingModule({
193+
imports: [
194+
CommonModule,
195+
FlexLayoutModule.withConfig({
196+
useColumnBasisZero: false,
197+
serverLoaded: true,
198+
}),
199+
],
200+
declarations: [TestFlexComponent],
201+
providers: [
202+
MockMatchMediaProvider,
203+
{
204+
provide: FlexOffsetStyleBuilder,
205+
useClass: MockFlexOffsetStyleBuilder,
206+
}
207+
]
208+
});
209+
});
210+
211+
it('should set flex offset not to input', async(() => {
212+
componentWithTemplate(`
213+
<div fxLayout='column'>
214+
<div fxFlexOffset="25"></div>
215+
</div>
216+
`);
217+
fixture.detectChanges();
218+
let element = queryFor(fixture, '[fxFlexOffset]')[0];
219+
expectEl(element).toHaveStyle({'margin-top': '10px'}, styler);
220+
}));
221+
});
222+
180223
});
181224

225+
@Injectable({providedIn: FlexModule})
226+
export class MockFlexOffsetStyleBuilder implements StyleBuilder {
227+
buildStyles(_input: string) {
228+
return {'margin-top': '10px'};
229+
}
230+
}
231+
182232

183233
// *****************************************************************
184234
// Template Component

src/lib/flex/flex-offset/flex-offset.ts

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@ import {
1515
Optional,
1616
SimpleChanges,
1717
SkipSelf,
18+
Injectable,
1819
} from '@angular/core';
1920
import {Directionality} from '@angular/cdk/bidi';
2021
import {
2122
BaseDirective,
2223
MediaChange,
2324
MediaMonitor,
25+
StyleBuilder,
2426
StyleDefinition,
2527
StyleUtils,
2628
} from '@angular/flex-layout/core';
@@ -29,6 +31,26 @@ import {Subscription} from 'rxjs';
2931
import {Layout, LayoutDirective} from '../layout/layout';
3032
import {isFlowHorizontal} from '../../utils/layout-validator';
3133

34+
interface FlexOffsetParent {
35+
layout: string;
36+
isRtl: boolean;
37+
}
38+
39+
@Injectable({providedIn: 'root'})
40+
export class FlexOffsetStyleBuilder implements StyleBuilder {
41+
buildStyles(offset: string, parent: FlexOffsetParent): StyleDefinition {
42+
const isPercent = String(offset).indexOf('%') > -1;
43+
const isPx = String(offset).indexOf('px') > -1;
44+
if (!isPx && !isPercent && !isNaN(+offset)) {
45+
offset = offset + '%';
46+
}
47+
const horizontalLayoutKey = parent.isRtl ? 'margin-right' : 'margin-left';
48+
49+
return isFlowHorizontal(parent.layout) ? {[horizontalLayoutKey]: `${offset}`} :
50+
{'margin-top': `${offset}`};
51+
}
52+
}
53+
3254
/**
3355
* 'flex-offset' flexbox styling directive
3456
* Configures the 'margin-left' of the element in a layout container
@@ -65,8 +87,9 @@ export class FlexOffsetDirective extends BaseDirective implements OnInit, OnChan
6587
elRef: ElementRef,
6688
@Optional() @SkipSelf() protected _container: LayoutDirective,
6789
private _directionality: Directionality,
68-
styleUtils: StyleUtils) {
69-
super(monitor, elRef, styleUtils);
90+
styleUtils: StyleUtils,
91+
styleBuilder: FlexOffsetStyleBuilder) {
92+
super(monitor, elRef, styleUtils, styleBuilder);
7093

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

162-
this._applyStyleToElement(this._buildCSS(value));
163-
}
164-
165-
protected _buildCSS(offset: string|number = ''): StyleDefinition {
166-
let isPercent = String(offset).indexOf('%') > -1;
167-
let isPx = String(offset).indexOf('px') > -1;
168-
if (!isPx && !isPercent && !isNaN(+offset)) {
169-
offset = offset + '%';
170-
}
171-
172185
// The flex-direction of this element's flex container. Defaults to 'row'.
173-
const isRtl = this._directionality.value === 'rtl';
174186
const layout = this._getFlexFlowDirection(this.parentElement, true);
175-
const horizontalLayoutKey = isRtl ? 'margin-right' : 'margin-left';
176-
177-
return isFlowHorizontal(layout) ? {[horizontalLayoutKey]: `${offset}`} :
178-
{'margin-top': `${offset}`};
187+
const isRtl = this._directionality.value === 'rtl';
188+
this.addStyles((value && (value + '') || ''), {layout, isRtl});
179189
}
180190
}

src/lib/flex/flex-order/flex-order.ts

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,24 @@ import {
1313
OnChanges,
1414
OnDestroy,
1515
SimpleChanges,
16+
Injectable,
1617
} from '@angular/core';
17-
import {BaseDirective, MediaChange, MediaMonitor, StyleUtils} from '@angular/flex-layout/core';
18+
import {
19+
BaseDirective,
20+
MediaChange,
21+
MediaMonitor,
22+
StyleBuilder,
23+
StyleDefinition,
24+
StyleUtils
25+
} from '@angular/flex-layout/core';
1826

27+
@Injectable({providedIn: 'root'})
28+
export class FlexOrderStyleBuilder implements StyleBuilder {
29+
buildStyles(value: string): StyleDefinition {
30+
const val = parseInt(value, 10);
31+
return {order: isNaN(val) ? 0 : val};
32+
}
33+
}
1934

2035
/**
2136
* 'flex-order' flexbox styling directive
@@ -51,8 +66,9 @@ export class FlexOrderDirective extends BaseDirective implements OnInit, OnChang
5166
/* tslint:enable */
5267
constructor(monitor: MediaMonitor,
5368
elRef: ElementRef,
54-
styleUtils: StyleUtils) {
55-
super(monitor, elRef, styleUtils);
69+
styleUtils: StyleUtils,
70+
styleBuilder: FlexOrderStyleBuilder) {
71+
super(monitor, elRef, styleUtils, styleBuilder);
5672
}
5773

5874
// *********************************************
@@ -90,12 +106,6 @@ export class FlexOrderDirective extends BaseDirective implements OnInit, OnChang
90106
value = this._mqActivation.activatedInput;
91107
}
92108

93-
this._applyStyleToElement(this._buildCSS(value));
94-
}
95-
96-
97-
protected _buildCSS(value: string = '') {
98-
const val = parseInt(value, 10);
99-
return {order: isNaN(val) ? 0 : val};
109+
this.addStyles(value || '');
100110
}
101111
}

0 commit comments

Comments
 (0)