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

Commit 0f13b14

Browse files
ThomasBurlesontinayuangao
authored andcommitted
fix(lib, media-query): support angular/universal (#353)
Use getDom() for platform-server/universal fixes Now gets document object from platform-browser by DI instead of global. > Thx to @ardatan for PR #346. Fixes #187, #354. Closes #346.
1 parent 40defac commit 0f13b14

26 files changed

+653
-81
lines changed

.travis.yml

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ jobs:
2020
include:
2121
- env: "MODE=lint"
2222
- env: "MODE=aot"
23+
- env: "MODE=prerender"
2324
- env: "MODE=closure-compiler"
2425
- env: "MODE=saucelabs_required"
2526
- env: "MODE=browserstack_required"

package.json

+8-6
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,19 @@
99
"url": "git+https://github.com/angular/flex-layout.git"
1010
},
1111
"scripts": {
12+
"api": "gulp api-docs",
1213
"build": "gulp :publish:build-releases",
14+
"closure": "./scripts/closure-compiler/build-devapp-bundle.sh",
1315
"demo-app": "gulp serve:devapp",
14-
"test": "gulp test",
15-
"tslint": "gulp lint",
16-
"stylelint": "gulp lint",
17-
"e2e": "gulp e2e",
16+
"docs": "gulp docs",
1817
"deploy": "gulp deploy:devapp",
1918
"stage": "gulp stage-deploy:devapp",
19+
"stylelint": "gulp lint",
20+
"test": "gulp test",
21+
"tslint": "gulp lint",
2022
"webdriver-manager": "webdriver-manager",
21-
"docs": "gulp docs",
22-
"api": "gulp api-docs"
23+
"universal": "gulp universal:build",
24+
"universal:test": "gulp ci:prerender"
2325
},
2426
"version": "2.0.0-beta.8",
2527
"license": "MIT",

scripts/ci/travis-testing.sh

+2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ elif is_aot; then
3939
$(npm bin)/gulp ci:aot
4040
elif is_unit; then
4141
$(npm bin)/gulp ci:test
42+
elif is_prerender; then
43+
$(npm bin)/gulp ci:prerender
4244
elif is_closure_compiler; then
4345
./scripts/closure-compiler/build-devapp-bundle.sh
4446
fi

scripts/closure-compiler/build-devapp-bundle.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ set -e -o pipefail
99
cd $(dirname $0)/../..
1010

1111

12-
# Build a release of material and of the CDK package.
12+
# Build a release of Flex-Layout library
1313
$(npm bin)/gulp flex-layout:build-release:clean
1414

1515
# Build demo-app with ES2015 modules. Closure compiler is then able to parse imports.

src/demo-app/app/github-issues/splitter/split-handle.directive.ts

+8-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import {Directive, ElementRef, Output} from '@angular/core';
1+
import {Directive, ElementRef, Inject, Output} from '@angular/core';
2+
import {DOCUMENT} from '@angular/platform-browser';
3+
24
import {Observable} from 'rxjs/Observable';
35

46
import 'rxjs/add/observable/fromEvent';
@@ -17,14 +19,13 @@ export class SplitHandleDirective {
1719

1820
@Output() drag: Observable<{ x: number, y: number }>;
1921

20-
constructor(ref: ElementRef) {
22+
constructor(ref: ElementRef, @Inject(DOCUMENT) _document: any) {
23+
const fromEvent = Observable.fromEvent;
2124
const getMouseEventPosition = (event: MouseEvent) => ({x: event.movementX, y: event.movementY});
2225

23-
const mouseup$ = Observable.fromEvent(document, 'mouseup');
24-
const mousemove$ = Observable.fromEvent(document, 'mousemove')
25-
.map(getMouseEventPosition);
26-
const mousedown$ = Observable.fromEvent(ref.nativeElement, 'mousedown')
27-
.map(getMouseEventPosition);
26+
const mousedown$ = fromEvent(ref.nativeElement, 'mousedown').map(getMouseEventPosition);
27+
const mousemove$ = fromEvent(_document, 'mousemove').map(getMouseEventPosition);
28+
const mouseup$ = fromEvent(_document, 'mouseup').map(getMouseEventPosition);
2829

2930
this.drag = mousedown$.switchMap(_ => mousemove$.takeUntil(mouseup$));
3031
}

src/lib/flexbox/api/base.ts

+29-15
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
ElementRef, OnDestroy, SimpleChanges, OnChanges,
1010
SimpleChange, Renderer
1111
} from '@angular/core';
12+
import {ɵgetDOM as getDom} from '@angular/platform-browser';
1213

1314
import {applyCssPrefixes} from '../../utils/auto-prefixer';
1415
import {buildLayoutCSS} from '../../utils/layout-validator';
@@ -117,27 +118,40 @@ export abstract class BaseFxDirective implements OnDestroy, OnChanges {
117118
*/
118119
protected _getDisplayStyle(source?: HTMLElement): string {
119120
let element: HTMLElement = source || this._elementRef.nativeElement;
120-
let value = (element.style as any)['display'] || getComputedStyle(element)['display'];
121-
return value ? value.trim() : 'block';
121+
let value = this._lookupStyle(element, 'display');
122+
123+
return value ? value.trim() : ((element.nodeType === 1) ? 'block' : 'inline-block');
122124
}
123125

124126
protected _getFlowDirection(target: any, addIfMissing = false): string {
125-
let value = '';
127+
let value = 'row';
128+
126129
if (target) {
127-
let directionKeys = Object.keys(applyCssPrefixes({'flex-direction': ''}));
128-
let findDirection = (styles) => directionKeys.reduce((direction, key) => {
129-
return direction || styles[key];
130-
}, null);
131-
132-
let immediateValue = findDirection(target.style);
133-
value = immediateValue || findDirection(getComputedStyle(target as Element));
134-
if (!immediateValue && addIfMissing) {
135-
value = value || 'row';
130+
value = this._lookupStyle(target, 'flex-direction') || 'row';
131+
132+
let hasInlineValue = getDom().getStyle(target, 'flex-direction');
133+
if (!hasInlineValue && addIfMissing) {
136134
this._applyStyleToElements(buildLayoutCSS(value), [target]);
137135
}
138136
}
139137

140-
return value ? value.trim() : 'row';
138+
return value.trim();
139+
}
140+
141+
/**
142+
* Determine the inline or inherited CSS style
143+
*/
144+
protected _lookupStyle(element: HTMLElement, styleName: string): any {
145+
let value = '';
146+
try {
147+
if (element) {
148+
let immediateValue = getDom().getStyle(element, styleName);
149+
value = immediateValue || getDom().getComputedStyle(element).display;
150+
}
151+
} catch (e) {
152+
// TODO: platform-server throws an exception for getComputedStyle
153+
}
154+
return value;
141155
}
142156

143157
/**
@@ -220,11 +234,11 @@ export abstract class BaseFxDirective implements OnDestroy, OnChanges {
220234
* Special accessor to query for all child 'element' nodes regardless of type, class, etc.
221235
*/
222236
protected get childrenNodes() {
223-
const obj = this._elementRef.nativeElement.childNodes;
237+
const obj = this._elementRef.nativeElement.children;
224238
const buffer = [];
225239

226240
// iterate backwards ensuring that length is an UInt32
227-
for ( let i = obj.length; i--; ) {
241+
for (let i = obj.length; i--; ) {
228242
buffer[i] = obj[i];
229243
}
230244
return buffer;

src/lib/flexbox/api/layout-gap.ts

+23-19
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@ import {LayoutDirective} from './layout';
2424
import {MediaChange} from '../../media-query/media-change';
2525
import {MediaMonitor} from '../../media-query/media-monitor';
2626
import {LAYOUT_VALUES} from '../../utils/layout-validator';
27+
2728
/**
2829
* 'layout-padding' styling directive
2930
* Defines padding of child elements in a layout container
3031
*/
31-
@Directive({selector: `
32+
@Directive({
33+
selector: `
3234
[fxLayoutGap],
3335
[fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl],
3436
[fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl],
@@ -42,22 +44,22 @@ export class LayoutGapDirective extends BaseFxDirective implements AfterContentI
4244
protected _observer: MutationObserver;
4345

4446
/* tslint:disable */
45-
@Input('fxLayoutGap') set gap(val) { this._cacheInput('gap', val); }
46-
@Input('fxLayoutGap.xs') set gapXs(val) { this._cacheInput('gapXs', val); }
47-
@Input('fxLayoutGap.sm') set gapSm(val) { this._cacheInput('gapSm', val); };
48-
@Input('fxLayoutGap.md') set gapMd(val) { this._cacheInput('gapMd', val); };
49-
@Input('fxLayoutGap.lg') set gapLg(val) { this._cacheInput('gapLg', val); };
50-
@Input('fxLayoutGap.xl') set gapXl(val) { this._cacheInput('gapXl', val); };
51-
52-
@Input('fxLayoutGap.gt-xs') set gapGtXs(val) { this._cacheInput('gapGtXs', val); };
53-
@Input('fxLayoutGap.gt-sm') set gapGtSm(val) { this._cacheInput('gapGtSm', val); };
54-
@Input('fxLayoutGap.gt-md') set gapGtMd(val) { this._cacheInput('gapGtMd', val); };
55-
@Input('fxLayoutGap.gt-lg') set gapGtLg(val) { this._cacheInput('gapGtLg', val); };
56-
57-
@Input('fxLayoutGap.lt-sm') set gapLtSm(val) { this._cacheInput('gapLtSm', val); };
58-
@Input('fxLayoutGap.lt-md') set gapLtMd(val) { this._cacheInput('gapLtMd', val); };
59-
@Input('fxLayoutGap.lt-lg') set gapLtLg(val) { this._cacheInput('gapLtLg', val); };
60-
@Input('fxLayoutGap.lt-xl') set gapLtXl(val) { this._cacheInput('gapLtXl', val); };
47+
@Input('fxLayoutGap') set gap(val) { this._cacheInput('gap', val); }
48+
@Input('fxLayoutGap.xs') set gapXs(val) { this._cacheInput('gapXs', val); }
49+
@Input('fxLayoutGap.sm') set gapSm(val) { this._cacheInput('gapSm', val); };
50+
@Input('fxLayoutGap.md') set gapMd(val) { this._cacheInput('gapMd', val); };
51+
@Input('fxLayoutGap.lg') set gapLg(val) { this._cacheInput('gapLg', val); };
52+
@Input('fxLayoutGap.xl') set gapXl(val) { this._cacheInput('gapXl', val); };
53+
54+
@Input('fxLayoutGap.gt-xs') set gapGtXs(val) { this._cacheInput('gapGtXs', val); };
55+
@Input('fxLayoutGap.gt-sm') set gapGtSm(val) { this._cacheInput('gapGtSm', val); };
56+
@Input('fxLayoutGap.gt-md') set gapGtMd(val) { this._cacheInput('gapGtMd', val); };
57+
@Input('fxLayoutGap.gt-lg') set gapGtLg(val) { this._cacheInput('gapGtLg', val); };
58+
59+
@Input('fxLayoutGap.lt-sm') set gapLtSm(val) { this._cacheInput('gapLtSm', val); };
60+
@Input('fxLayoutGap.lt-md') set gapLtMd(val) { this._cacheInput('gapLtMd', val); };
61+
@Input('fxLayoutGap.lt-lg') set gapLtLg(val) { this._cacheInput('gapLtLg', val); };
62+
@Input('fxLayoutGap.lt-xl') set gapLtXl(val) { this._cacheInput('gapLtXl', val); };
6163

6264
/* tslint:enable */
6365
constructor(monitor: MediaMonitor,
@@ -124,8 +126,10 @@ export class LayoutGapDirective extends BaseFxDirective implements AfterContentI
124126
}
125127
};
126128

127-
this._observer = new MutationObserver(onMutationCallback);
128-
this._observer.observe(this._elementRef.nativeElement, {childList: true});
129+
if (typeof MutationObserver !== 'undefined') {
130+
this._observer = new MutationObserver(onMutationCallback);
131+
this._observer.observe(this._elementRef.nativeElement, {childList: true});
132+
}
129133
}
130134

131135
/**

src/lib/media-query/match-media.ts

+24-13
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 {Injectable, NgZone} from '@angular/core';
9-
8+
import {Inject, Injectable, NgZone} from '@angular/core';
9+
import {ɵgetDOM as getDom, DOCUMENT} from '@angular/platform-browser';
1010
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
1111
import {Observable} from 'rxjs/Observable';
1212
import {filter} from 'rxjs/operator/filter';
@@ -27,7 +27,9 @@ export interface MediaQueryListListener {
2727
export interface MediaQueryList {
2828
readonly matches: boolean;
2929
readonly media: string;
30+
3031
addListener(listener: MediaQueryListListener): void;
32+
3133
removeListener(listener: MediaQueryListListener): void;
3234
}
3335

@@ -45,7 +47,7 @@ export class MatchMedia {
4547
protected _source: BehaviorSubject<MediaChange>;
4648
protected _observable$: Observable<MediaChange>;
4749

48-
constructor(protected _zone: NgZone) {
50+
constructor(protected _zone: NgZone, @Inject(DOCUMENT) protected _document: any) {
4951
this._registry = new Map<string, MediaQueryList>();
5052
this._source = new BehaviorSubject<MediaChange>(new MediaChange(true));
5153
this._observable$ = this._source.asObservable();
@@ -86,7 +88,7 @@ export class MatchMedia {
8688
let list = normalizeQuery(mediaQuery);
8789

8890
if (list.length > 0) {
89-
prepareQueryCSS(list);
91+
prepareQueryCSS(list, this._document);
9092

9193
list.forEach(query => {
9294
let mql = this._registry.get(query);
@@ -114,8 +116,9 @@ export class MatchMedia {
114116
* Call window.matchMedia() to build a MediaQueryList; which
115117
* supports 0..n listeners for activation/deactivation
116118
*/
117-
protected _buildMQL(query: string): MediaQueryList {
118-
let canListen = !!(<any>window).matchMedia('all').addListener;
119+
protected _buildMQL(query: string): MediaQueryList {
120+
let canListen = isBrowser() && !!(<any>window).matchMedia('all').addListener;
121+
119122
return canListen ? (<any>window).matchMedia(query) : <MediaQueryList>{
120123
matches: query === 'all' || query === '',
121124
media: query,
@@ -127,6 +130,13 @@ export class MatchMedia {
127130
}
128131
}
129132

133+
/**
134+
* Determine if SSR or Browser rendering.
135+
*/
136+
export function isBrowser() {
137+
return getDom().supportsDOMEvents();
138+
}
139+
130140
/**
131141
* Private global registry for all dynamically-created, injected style tags
132142
* @see prepare(query)
@@ -140,27 +150,28 @@ const ALL_STYLES = {};
140150
* @param query string The mediaQuery used to create a faux CSS selector
141151
*
142152
*/
143-
function prepareQueryCSS(mediaQueries: string[]) {
153+
function prepareQueryCSS(mediaQueries: string[], _document: any) {
144154
let list = mediaQueries.filter(it => !ALL_STYLES[it]);
145155
if (list.length > 0) {
146156
let query = list.join(', ');
157+
147158
try {
148-
let style = document.createElement('style');
159+
let styleEl = getDom().createElement('style');
149160

150-
style.setAttribute('type', 'text/css');
151-
if (!style['styleSheet']) {
161+
getDom().setAttribute(styleEl, 'type', 'text/css');
162+
if (!styleEl['styleSheet']) {
152163
let cssText = `/*
153164
@angular/flex-layout - workaround for possible browser quirk with mediaQuery listeners
154165
see http://bit.ly/2sd4HMP
155166
*/
156167
@media ${query} {.fx-query-test{ }}`;
157-
style.appendChild(document.createTextNode(cssText));
168+
getDom().appendChild(styleEl, getDom().createTextNode(cssText));
158169
}
159170

160-
document.getElementsByTagName('head')[0].appendChild(style);
171+
getDom().appendChild(_document.head, styleEl);
161172

162173
// Store in private global registry
163-
list.forEach(mq => ALL_STYLES[mq] = style);
174+
list.forEach(mq => ALL_STYLES[mq] = styleEl);
164175

165176
} catch (e) {
166177
console.error(e);

src/lib/media-query/mock/mock-match-media.ts

+32-12
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
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 {Injectable, NgZone} from '@angular/core';
8+
import {Inject, Injectable, NgZone} from '@angular/core';
9+
import {DOCUMENT} from '@angular/platform-browser';
10+
911
import {MatchMedia} from '../match-media';
1012
import {BreakPointRegistry} from '../breakpoints/break-point-registry';
1113

@@ -28,8 +30,10 @@ export class MockMatchMedia extends MatchMedia {
2830
*/
2931
public useOverlaps = false;
3032

31-
constructor(_zone: NgZone, private _breakpoints: BreakPointRegistry) {
32-
super(_zone);
33+
constructor(_zone: NgZone,
34+
@Inject(DOCUMENT) _document: any,
35+
private _breakpoints: BreakPointRegistry) {
36+
super(_zone, _document);
3337
this._actives = [];
3438
}
3539

@@ -83,18 +87,34 @@ export class MockMatchMedia extends MatchMedia {
8387

8488
// Simulate activation of overlapping lt-<XXX> ranges
8589
switch (alias) {
86-
case 'lg' : this._activateByAlias('lt-xl'); break;
87-
case 'md' : this._activateByAlias('lt-xl, lt-lg'); break;
88-
case 'sm' : this._activateByAlias('lt-xl, lt-lg, lt-md'); break;
89-
case 'xs' : this._activateByAlias('lt-xl, lt-lg, lt-md, lt-sm'); break;
90+
case 'lg' :
91+
this._activateByAlias('lt-xl');
92+
break;
93+
case 'md' :
94+
this._activateByAlias('lt-xl, lt-lg');
95+
break;
96+
case 'sm' :
97+
this._activateByAlias('lt-xl, lt-lg, lt-md');
98+
break;
99+
case 'xs' :
100+
this._activateByAlias('lt-xl, lt-lg, lt-md, lt-sm');
101+
break;
90102
}
91103

92104
// Simulate activate of overlapping gt-<xxxx> mediaQuery ranges
93105
switch (alias) {
94-
case 'xl' : this._activateByAlias('gt-lg, gt-md, gt-sm, gt-xs'); break;
95-
case 'lg' : this._activateByAlias('gt-md, gt-sm, gt-xs'); break;
96-
case 'md' : this._activateByAlias('gt-sm, gt-xs'); break;
97-
case 'sm' : this._activateByAlias('gt-xs'); break;
106+
case 'xl' :
107+
this._activateByAlias('gt-lg, gt-md, gt-sm, gt-xs');
108+
break;
109+
case 'lg' :
110+
this._activateByAlias('gt-md, gt-sm, gt-xs');
111+
break;
112+
case 'md' :
113+
this._activateByAlias('gt-sm, gt-xs');
114+
break;
115+
case 'sm' :
116+
this._activateByAlias('gt-xs');
117+
break;
98118
}
99119
}
100120
// Activate last since the responsiveActivation is watching *this* mediaQuery
@@ -195,7 +215,7 @@ export class MockMediaQueryList implements MediaQueryList {
195215
* Notify all listeners that 'matches === TRUE'
196216
*/
197217
activate(): MockMediaQueryList {
198-
if ( !this._isActive ) {
218+
if (!this._isActive) {
199219
this._isActive = true;
200220
this._listeners.forEach((callback) => {
201221
callback(this);

0 commit comments

Comments
 (0)