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

Commit 887e063

Browse files
committed
feat(ssr): enhance support for Universal and SSR with stylesheets
* Add StyleUtils class to manage application and retrieval of styles from elements in a platform-agnostic manner * Add virtual stylesheet to store server styles, which applies default styles when breakpoint overrides are not present * Intercept all style calls and reroute them to the virtual stylesheet while not in the browser * Add a new type of MediaQueryList similar to the MockMediaQueryList for the server that allows for manual activation/deactivation of breakpoints
1 parent 39c78be commit 887e063

25 files changed

+493
-178
lines changed

src/lib/api/core/base-adapter.spec.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
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 {ElementRef, Renderer2} from '@angular/core';
8+
import {ElementRef} from '@angular/core';
99
import {BaseFxDirectiveAdapter} from './base-adapter';
1010
import {expect} from '../../utils/testing/custom-matchers';
11-
import {MediaMonitor} from '@angular/flex-layout/media-query';
11+
import {MediaMonitor} from '../../media-query/media-monitor';
12+
import {StyleUtils} from '../../utils/style-utils';
1213

1314
export class MockElementRef extends ElementRef {
1415
constructor() {
@@ -21,7 +22,7 @@ export class MockElementRef extends ElementRef {
2122
describe('BaseFxDirectiveAdapter class', () => {
2223
let component;
2324
beforeEach(() => {
24-
component = new BaseFxDirectiveAdapter('', {} as MediaMonitor, new MockElementRef(), {} as Renderer2, {}); // tslint:disable-line:max-line-length
25+
component = new BaseFxDirectiveAdapter('', {} as MediaMonitor, new MockElementRef(), {} as StyleUtils); // tslint:disable-line:max-line-length
2526
});
2627
describe('cacheInput', () => {
2728
it('should call _cacheInputArray when source is an array', () => {

src/lib/api/core/base-adapter.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
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 {ElementRef, Inject, PLATFORM_ID, Renderer2} from '@angular/core';
8+
import {ElementRef} from '@angular/core';
99

1010
import {BaseFxDirective} from './base';
1111
import {ResponsiveActivation} from './responsive-activation';
1212
import {MediaQuerySubscriber} from '../../media-query/media-change';
1313
import {MediaMonitor} from '../../media-query/media-monitor';
14+
import {StyleUtils} from '../../utils/style-utils';
1415

1516

1617
/**
@@ -48,9 +49,8 @@ export class BaseFxDirectiveAdapter extends BaseFxDirective {
4849
constructor(protected _baseKey: string, // non-responsive @Input property name
4950
protected _mediaMonitor: MediaMonitor,
5051
protected _elementRef: ElementRef,
51-
protected _renderer: Renderer2,
52-
@Inject(PLATFORM_ID) protected _platformId: Object) {
53-
super(_mediaMonitor, _elementRef, _renderer, _platformId);
52+
protected _styleUtils: StyleUtils) {
53+
super(_mediaMonitor, _elementRef, _styleUtils);
5454
}
5555

5656
/**

src/lib/api/core/base.ts

+19-22
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,12 @@ import {
1111
SimpleChanges,
1212
OnChanges,
1313
SimpleChange,
14-
Renderer2,
15-
Inject,
16-
PLATFORM_ID,
1714
} from '@angular/core';
1815

1916
import {buildLayoutCSS} from '../../utils/layout-validator';
2017
import {
2118
StyleDefinition,
22-
lookupStyle,
23-
lookupInlineStyle,
24-
applyStyleToElement,
25-
applyStyleToElements,
26-
lookupAttributeValue,
19+
StyleUtils,
2720
} from '../../utils/style-utils';
2821

2922
import {ResponsiveActivation, KeyOptions} from '../core/responsive-activation';
@@ -70,8 +63,7 @@ export abstract class BaseFxDirective implements OnDestroy, OnChanges {
7063
*/
7164
constructor(protected _mediaMonitor: MediaMonitor,
7265
protected _elementRef: ElementRef,
73-
protected _renderer: Renderer2,
74-
@Inject(PLATFORM_ID) protected _platformId: Object) {
66+
protected _styleUtils: StyleUtils) {
7567
}
7668

7769
// *********************************************
@@ -137,19 +129,20 @@ export abstract class BaseFxDirective implements OnDestroy, OnChanges {
137129

138130
/**
139131
* Quick accessor to the current HTMLElement's `display` style
140-
* Note: this allows use to preserve the original style
132+
* Note: this allows us to preserve the original style
141133
* and optional restore it when the mediaQueries deactivate
142134
*/
143135
protected _getDisplayStyle(source: HTMLElement = this.nativeElement): string {
144-
return lookupStyle(this._platformId, source || this.nativeElement, 'display');
136+
const query = 'display';
137+
return this._styleUtils.lookupStyle(source, query);
145138
}
146139

147140
/**
148141
* Quick accessor to raw attribute value on the target DOM element
149142
*/
150143
protected _getAttributeValue(attribute: string,
151144
source: HTMLElement = this.nativeElement): string {
152-
return lookupAttributeValue(source || this.nativeElement, attribute);
145+
return this._styleUtils.lookupAttributeValue(source, attribute);
153146
}
154147

155148
/**
@@ -158,15 +151,20 @@ export abstract class BaseFxDirective implements OnDestroy, OnChanges {
158151
* Check inline style first then check computed (stylesheet) style.
159152
* And optionally add the flow value to element's inline style.
160153
*/
161-
protected _getFlowDirection(target: any, addIfMissing = false): string {
154+
protected _getFlowDirection(target: HTMLElement, addIfMissing = false): string {
162155
let value = 'row';
156+
let hasInlineValue = '';
157+
const query = 'flex-direction';
163158

164159
if (target) {
165-
value = lookupStyle(this._platformId, target, 'flex-direction') || 'row';
166-
let hasInlineValue = lookupInlineStyle(target, 'flex-direction');
160+
161+
value = this._styleUtils.lookupStyle(target, query) || 'row';
162+
hasInlineValue = this._styleUtils.lookupInlineStyle(target, query);
167163

168164
if (!hasInlineValue && addIfMissing) {
169-
applyStyleToElements(this._renderer, buildLayoutCSS(value), [target]);
165+
const style = buildLayoutCSS(value);
166+
const elements = [target];
167+
this._styleUtils.applyStyleToElements(style, elements);
170168
}
171169
}
172170

@@ -178,16 +176,15 @@ export abstract class BaseFxDirective implements OnDestroy, OnChanges {
178176
*/
179177
protected _applyStyleToElement(style: StyleDefinition,
180178
value?: string | number,
181-
nativeElement: any = this.nativeElement) {
182-
let element = nativeElement || this.nativeElement;
183-
applyStyleToElement(this._renderer, element, style, value);
179+
element: HTMLElement = this.nativeElement) {
180+
this._styleUtils.applyStyleToElement(element, style, value);
184181
}
185182

186183
/**
187184
* Applies styles given via string pair or object map to the directive's element.
188185
*/
189-
protected _applyStyleToElements(style: StyleDefinition, elements: HTMLElement[ ]) {
190-
applyStyleToElements(this._renderer, style, elements || []);
186+
protected _applyStyleToElements(style: StyleDefinition, elements: HTMLElement[]) {
187+
this._styleUtils.applyStyleToElements(style, elements);
191188
}
192189

193190
/**

src/lib/api/ext/class.spec.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import {BreakPointRegistry} from '../../media-query/breakpoints/break-point-regi
2222

2323
import {ClassDirective} from './class';
2424
import {MediaQueriesModule} from '../../media-query/_module';
25+
import {ServerStylesheet} from '../../utils/server-stylesheet';
26+
import {StyleUtils} from '../../utils/style-utils';
2527

2628
describe('class directive', () => {
2729
let fixture: ComponentFixture<any>;
@@ -47,7 +49,9 @@ describe('class directive', () => {
4749
declarations: [TestClassComponent, ClassDirective],
4850
providers: [
4951
BreakPointRegistry, DEFAULT_BREAKPOINTS_PROVIDER,
50-
{provide: MatchMedia, useClass: MockMatchMedia}
52+
{provide: MatchMedia, useClass: MockMatchMedia},
53+
ServerStylesheet,
54+
StyleUtils,
5155
]
5256
});
5357
});

src/lib/api/ext/class.ts

+7-5
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ import {
1919
SimpleChanges,
2020
Self,
2121
OnInit,
22-
Inject,
23-
PLATFORM_ID,
2422
} from '@angular/core';
2523
import {NgClass} from '@angular/common';
2624

@@ -29,6 +27,7 @@ import {BaseFxDirectiveAdapter} from '../core/base-adapter';
2927
import {MediaChange} from '../../media-query/media-change';
3028
import {MediaMonitor} from '../../media-query/media-monitor';
3129
import {RendererAdapter} from '../core/renderer-adapter';
30+
import {StyleUtils} from '../../utils/style-utils';
3231

3332
/** NgClass allowed inputs **/
3433
export type NgClassType = string | string[] | Set<string> | {[klass: string]: any};
@@ -95,8 +94,8 @@ export class ClassDirective extends BaseFxDirective
9594
protected _ngEl: ElementRef,
9695
protected _renderer: Renderer2,
9796
@Optional() @Self() private _ngClassInstance: NgClass,
98-
@Inject(PLATFORM_ID) protected _platformId: Object) {
99-
super(monitor, _ngEl, _renderer, _platformId);
97+
protected _styleUtils: StyleUtils) {
98+
super(monitor, _ngEl, _styleUtils);
10099
this._configureAdapters();
101100
}
102101

@@ -139,7 +138,10 @@ export class ClassDirective extends BaseFxDirective
139138
*/
140139
protected _configureAdapters() {
141140
this._base = new BaseFxDirectiveAdapter(
142-
'ngClass', this.monitor, this._ngEl, this._renderer, this._platformId
141+
'ngClass',
142+
this.monitor,
143+
this._ngEl,
144+
this._styleUtils
143145
);
144146
if (!this._ngClassInstance) {
145147
// Create an instance NgClass Directive instance only if `ngClass=""` has NOT been defined on

src/lib/api/ext/hide.spec.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import {
2323
} from '../../utils/testing/helpers';
2424
import {ShowHideDirective} from './show-hide';
2525
import {MediaQueriesModule} from '../../media-query/_module';
26+
import {ServerStylesheet} from '../../utils/server-stylesheet';
27+
import {StyleUtils} from '../../utils/style-utils';
2628

2729
describe('hide directive', () => {
2830
let fixture: ComponentFixture<any>;
@@ -60,7 +62,9 @@ describe('hide directive', () => {
6062
declarations: [TestHideComponent, ShowHideDirective],
6163
providers: [
6264
BreakPointRegistry, DEFAULT_BREAKPOINTS_PROVIDER,
63-
{provide: MatchMedia, useClass: MockMatchMedia}
65+
{provide: MatchMedia, useClass: MockMatchMedia},
66+
ServerStylesheet,
67+
StyleUtils,
6468
]
6569
});
6670
});

src/lib/api/ext/img-src.ts

+7-8
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,11 @@ import {
1212
OnInit,
1313
OnChanges,
1414
Renderer2,
15-
Inject,
16-
PLATFORM_ID,
1715
} from '@angular/core';
1816

1917
import {BaseFxDirective} from '../core/base';
2018
import {MediaMonitor} from '../../media-query/media-monitor';
19+
import {StyleUtils} from '../../utils/style-utils';
2120

2221
/**
2322
* This directive provides a responsive API for the HTML <img> 'src' attribute
@@ -57,12 +56,12 @@ export class ImgSrcDirective extends BaseFxDirective implements OnInit, OnChange
5756
@Input('src.gt-lg') set srcGtLg(val) { this._cacheInput('srcGtLg', val); }
5857
/* tslint:enable */
5958

60-
constructor(elRef: ElementRef,
61-
renderer: Renderer2,
62-
monitor: MediaMonitor,
63-
@Inject(PLATFORM_ID) platformId: Object) {
64-
super(monitor, elRef, renderer, platformId);
65-
this._cacheInput('src', elRef.nativeElement.getAttribute('src') || '');
59+
constructor(protected _elRef: ElementRef,
60+
protected _renderer: Renderer2,
61+
protected _monitor: MediaMonitor,
62+
protected _styleUtils: StyleUtils) {
63+
super(_monitor, _elRef, _styleUtils);
64+
this._cacheInput('src', _elRef.nativeElement.getAttribute('src') || '');
6665
}
6766

6867
/**

src/lib/api/ext/show-hide.ts

+7-10
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,9 @@ import {
1212
OnInit,
1313
OnChanges,
1414
OnDestroy,
15-
Renderer2,
1615
SimpleChanges,
1716
Self,
1817
Optional,
19-
Inject,
20-
PLATFORM_ID,
2118
} from '@angular/core';
2219

2320
import {Subscription} from 'rxjs/Subscription';
@@ -26,6 +23,7 @@ import {BaseFxDirective} from '../core/base';
2623
import {MediaChange} from '../../media-query/media-change';
2724
import {MediaMonitor} from '../../media-query/media-monitor';
2825
import {LayoutDirective} from '../flexbox/layout';
26+
import {StyleUtils} from '../../utils/style-utils';
2927

3028
const FALSY = ['false', false, 0];
3129

@@ -104,19 +102,18 @@ export class ShowHideDirective extends BaseFxDirective implements OnInit, OnChan
104102
*
105103
*/
106104
constructor(monitor: MediaMonitor,
107-
@Optional() @Self() protected _layout: LayoutDirective,
105+
@Optional() @Self() protected layout: LayoutDirective,
108106
protected elRef: ElementRef,
109-
protected renderer: Renderer2,
110-
@Inject(PLATFORM_ID) protected platformId: Object) {
107+
protected styleUtils: StyleUtils) {
111108

112-
super(monitor, elRef, renderer, platformId);
109+
super(monitor, elRef, styleUtils);
113110

114-
if (_layout) {
111+
if (layout) {
115112
/**
116113
* The Layout can set the display:flex (and incorrectly affect the Hide/Show directives.
117114
* Whenever Layout [on the same element] resets its CSS, then update the Hide/Show CSS
118115
*/
119-
this._layoutWatcher = _layout.layout$.subscribe(() => this._updateWithValue());
116+
this._layoutWatcher = layout.layout$.subscribe(() => this._updateWithValue());
120117
}
121118
}
122119

@@ -130,7 +127,7 @@ export class ShowHideDirective extends BaseFxDirective implements OnInit, OnChan
130127
* unless it was already explicitly specified inline or in a CSS stylesheet.
131128
*/
132129
protected _getDisplayStyle(): string {
133-
return this._layout ? 'flex' : super._getDisplayStyle();
130+
return this.layout ? 'flex' : super._getDisplayStyle();
134131
}
135132

136133

src/lib/api/ext/show.spec.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import {FlexLayoutModule} from '../../module';
1818

1919
import {customMatchers} from '../../utils/testing/custom-matchers';
2020
import {makeCreateTestComponent, expectNativeEl} from '../../utils/testing/helpers';
21+
import {ServerStylesheet} from '../../utils/server-stylesheet';
22+
import {StyleUtils} from '../../utils/style-utils';
2123

2224
describe('show directive', () => {
2325
let fixture: ComponentFixture<any>;
@@ -39,7 +41,9 @@ describe('show directive', () => {
3941
declarations: [TestShowComponent],
4042
providers: [
4143
BreakPointRegistry, DEFAULT_BREAKPOINTS_PROVIDER,
42-
{provide: MatchMedia, useClass: MockMatchMedia}
44+
{provide: MatchMedia, useClass: MockMatchMedia},
45+
ServerStylesheet,
46+
StyleUtils,
4347
]
4448
});
4549
});

src/lib/api/ext/style.spec.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import {customMatchers} from '../../utils/testing/custom-matchers';
2222
import {
2323
makeCreateTestComponent, expectNativeEl
2424
} from '../../utils/testing/helpers';
25+
import {ServerStylesheet} from '../../utils/server-stylesheet';
26+
import {StyleUtils} from '../../utils/style-utils';
2527

2628
describe('style directive', () => {
2729
let fixture: ComponentFixture<any>;
@@ -43,7 +45,9 @@ describe('style directive', () => {
4345
declarations: [TestStyleComponent, LayoutDirective, StyleDirective],
4446
providers: [
4547
BreakPointRegistry, DEFAULT_BREAKPOINTS_PROVIDER,
46-
{provide: MatchMedia, useClass: MockMatchMedia}
48+
{provide: MatchMedia, useClass: MockMatchMedia},
49+
ServerStylesheet,
50+
StyleUtils,
4751
]
4852
});
4953
});

0 commit comments

Comments
 (0)