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

Commit 6ebd21f

Browse files
committed
feat(ssr): enhance support for Universal and SSR with stylesheets
* Add `StyleService` 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 * While not in the browser (ssr), intercept all style calls and reroute them to the virtual stylesheet. * For server-side rendering, add a new type of MediaQueryList similar to the MockMediaQueryList to support manual activation/deactivation of breakpoints * Add jasmine testing mode for SSR * Add FlexLayoutServerModule to invoke SSR styling * Remove unnecessary Renderer references and replace them with DOM APIs * Add whitespace debugging mode for server styles Fixes #373. > See [Design Doc](https://docs.google.com/document/d/1fg04ihw42dJJHGd6fugdiBe39iJot8aErhiE7CjwfmQ/edit#)
1 parent 5ffb39c commit 6ebd21f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+1828
-1086
lines changed

.travis.yml

+5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ jobs:
2121
- env: "MODE=lint"
2222
- env: "MODE=aot"
2323
- env: "MODE=prerender"
24+
- env: "MODE=ssr"
2425
# Closure Compiler CI check is temporarily disabled until a new version of
2526
# the tool is released with https://github.com/google/closure-compiler/pull/2600
2627
# - env: "MODE=closure-compiler"
@@ -38,6 +39,10 @@ env:
3839
- BROWSER_PROVIDER_READY_FILE=/tmp/flex-layout-build/readyfile
3940
- BROWSER_PROVIDER_ERROR_FILE=/tmp/flex-layout-build/errorfile
4041

42+
matrix:
43+
allow_failures:
44+
- env: "MODE=ssr"
45+
4146

4247
before_install:
4348
- source ./scripts/ci/env.sh

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"lib:build:aot": "gulp ci:aot",
1818
"lib:lint": "gulp lint",
1919
"lib:test": "gulp test",
20+
"lib:test:ssr": "gulp test:ssr",
2021
"universal:build": "gulp universal:build",
2122
"universal:ci:prerender": "gulp ci:prerender"
2223
},

scripts/ci/sources/mode.sh

+4
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,7 @@ is_unit() {
2828
is_prerender() {
2929
[[ "$MODE" = prerender ]]
3030
}
31+
32+
is_ssr() {
33+
[[ "$MODE" = ssr ]]
34+
}

scripts/ci/travis-testing.sh

+2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ elif is_prerender; then
4343
$(npm bin)/gulp ci:prerender
4444
elif is_closure_compiler; then
4545
./scripts/closure-compiler/build-devapp-bundle.sh
46+
elif is_ssr; then
47+
$(npm bin)/gulp ci:ssr
4648
fi
4749

4850
teardown_tunnel

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

+4-11
Original file line numberDiff line numberDiff line change
@@ -5,23 +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 {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';
12-
13-
export class MockElementRef extends ElementRef {
14-
constructor() {
15-
const nEl = document.createElement('DIV');
16-
super(nEl);
17-
this.nativeElement = nEl;
18-
}
19-
}
11+
import {MediaMonitor} from '../../media-query/media-monitor';
12+
import {StyleService} from '../../utils/styling/styler';
2013

2114
describe('BaseFxDirectiveAdapter class', () => {
2215
let component;
2316
beforeEach(() => {
24-
component = new BaseFxDirectiveAdapter('', {} as MediaMonitor, new MockElementRef(), {} as Renderer2, {}); // tslint:disable-line:max-line-length
17+
component = new BaseFxDirectiveAdapter('', {} as MediaMonitor, {} as ElementRef, {} as StyleService); // tslint:disable-line:max-line-length
2518
});
2619
describe('cacheInput', () => {
2720
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 {StyleService} from '../../utils/styling/styler';
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 _styler: StyleService) {
53+
super(_mediaMonitor, _elementRef, _styler);
5454
}
5555

5656
/**

src/lib/api/core/base.ts

+19-25
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,13 @@ 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,
27-
} from '../../utils/style-utils';
19+
StyleService,
20+
} from '../../utils/styling/styler';
2821

2922
import {ResponsiveActivation, KeyOptions} from '../core/responsive-activation';
3023
import {MediaMonitor} from '../../media-query/media-monitor';
@@ -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 _styler: StyleService) {
7567
}
7668

7769
// *********************************************
@@ -85,7 +77,7 @@ export abstract class BaseFxDirective implements OnDestroy, OnChanges {
8577
return this._elementRef.nativeElement.parentNode;
8678
}
8779

88-
protected get nativeElement(): any {
80+
protected get nativeElement(): HTMLElement {
8981
return this._elementRef.nativeElement;
9082
}
9183

@@ -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._styler.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._styler.lookupAttributeValue(source, attribute);
153146
}
154147

155148
/**
@@ -158,36 +151,37 @@ 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 = '';
163157

164158
if (target) {
165-
value = lookupStyle(this._platformId, target, 'flex-direction') || 'row';
166-
let hasInlineValue = lookupInlineStyle(target, 'flex-direction');
159+
[value, hasInlineValue] = this._styler.getFlowDirection(target);
167160

168161
if (!hasInlineValue && addIfMissing) {
169-
applyStyleToElements(this._renderer, buildLayoutCSS(value), [target]);
162+
const style = buildLayoutCSS(value);
163+
const elements = [target];
164+
this._styler.applyStyleToElements(style, elements);
170165
}
171166
}
172167

173-
return value.trim();
168+
return value.trim() || 'row';
174169
}
175170

176171
/**
177172
* Applies styles given via string pair or object map to the directive element.
178173
*/
179174
protected _applyStyleToElement(style: StyleDefinition,
180175
value?: string | number,
181-
nativeElement: any = this.nativeElement) {
182-
let element = nativeElement || this.nativeElement;
183-
applyStyleToElement(this._renderer, element, style, value);
176+
element: HTMLElement = this.nativeElement) {
177+
this._styler.applyStyleToElement(element, style, value);
184178
}
185179

186180
/**
187181
* Applies styles given via string pair or object map to the directive's element.
188182
*/
189-
protected _applyStyleToElements(style: StyleDefinition, elements: HTMLElement[ ]) {
190-
applyStyleToElements(this._renderer, style, elements || []);
183+
protected _applyStyleToElements(style: StyleDefinition, elements: HTMLElement[]) {
184+
this._styler.applyStyleToElements(style, elements);
191185
}
192186

193187
/**

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

+18-6
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
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} from '@angular/core';
9-
import {CommonModule} from '@angular/common';
8+
import {Component, PLATFORM_ID} from '@angular/core';
9+
import {CommonModule, isPlatformServer} from '@angular/common';
1010
import {ComponentFixture, TestBed, async, inject} from '@angular/core/testing';
1111

1212
import {customMatchers, expect} from '../../utils/testing/custom-matchers';
@@ -21,15 +21,19 @@ import {BreakPointRegistry} from '../../media-query/breakpoints/break-point-regi
2121

2222
import {ClassDirective} from './class';
2323
import {MediaQueriesModule} from '../../media-query/_module';
24+
import {ServerStylesheet} from '../../utils/styling/server-stylesheet';
25+
import {StyleService} from '../../utils/styling/styler';
2426

2527
describe('class directive', () => {
2628
let fixture: ComponentFixture<any>;
2729
let matchMedia: MockMatchMedia;
30+
let platformId: Object;
2831
let createTestComponent = (template: string) => {
2932
fixture = makeCreateTestComponent(() => TestClassComponent)(template);
3033

31-
inject([MatchMedia], (_matchMedia: MockMatchMedia) => {
34+
inject([MatchMedia, PLATFORM_ID], (_matchMedia: MockMatchMedia, _platformId: Object) => {
3235
matchMedia = _matchMedia;
36+
platformId = _platformId;
3337
})();
3438
};
3539

@@ -46,7 +50,9 @@ describe('class directive', () => {
4650
declarations: [TestClassComponent, ClassDirective],
4751
providers: [
4852
BreakPointRegistry, DEFAULT_BREAKPOINTS_PROVIDER,
49-
{provide: MatchMedia, useClass: MockMatchMedia}
53+
{provide: MatchMedia, useClass: MockMatchMedia},
54+
ServerStylesheet,
55+
StyleService,
5056
]
5157
});
5258
});
@@ -224,15 +230,21 @@ describe('class directive', () => {
224230
fixture.detectChanges();
225231
let button = queryFor(fixture, '[mat-raised-button]')[0].nativeElement;
226232

227-
expect(button).toHaveCssClass('mat-raised-button');
233+
// TODO(CaerusKaru): MatButton doesn't apply host attributes on the server
234+
if (!isPlatformServer(platformId)) {
235+
expect(button).toHaveCssClass('mat-raised-button');
236+
}
228237
expect(button).toHaveCssClass('btn-xs');
229238
expect(button).toHaveCssClass('mat-primary');
230239

231240
fixture.componentInstance.formButtonXs = false;
232241
fixture.detectChanges();
233242
button = queryFor(fixture, '[mat-raised-button]')[0].nativeElement;
234243

235-
expect(button).toHaveCssClass('mat-raised-button');
244+
// TODO(CaerusKaru): MatButton doesn't apply host attributes on the server
245+
if (!isPlatformServer(platformId)) {
246+
expect(button).toHaveCssClass('mat-raised-button');
247+
}
236248
expect(button).not.toHaveCssClass('btn-xs');
237249
expect(button).toHaveCssClass('mat-primary');
238250
});

src/lib/api/ext/class.ts

+8-6
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,14 +27,15 @@ 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 {StyleService} from '../../utils/styling/styler';
3231

3332
/** NgClass allowed inputs **/
3433
export type NgClassType = string | string[] | Set<string> | {[klass: string]: any};
3534

3635
/**
3736
* Directive to add responsive support for ngClass.
3837
* This maintains the core functionality of 'ngClass' and adds responsive API
39-
*
38+
* Note: this class is a no-op when rendered on the server
4039
*/
4140
@Directive({
4241
selector: `
@@ -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 _styler: StyleService) {
98+
super(monitor, _ngEl, _styler);
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._styler
143145
);
144146
if (!this._ngClassInstance) {
145147
// Create an instance NgClass Directive instance only if `ngClass=""` has NOT been defined on

0 commit comments

Comments
 (0)