Skip to content

Commit f27df86

Browse files
jelbournkara
authored andcommitted
fix(universal): gate several browser-specific bits on being on the browser (#4251)
Related to #308
1 parent fc73a4b commit f27df86

22 files changed

+194
-125
lines changed

src/lib/button-toggle/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,20 @@ import {MdButtonToggleGroup, MdButtonToggleGroupMultiple, MdButtonToggle} from '
44
import {
55
UNIQUE_SELECTION_DISPATCHER_PROVIDER,
66
MdCommonModule,
7-
FocusOriginMonitor,
7+
StyleModule,
88
} from '../core';
99

1010

1111
@NgModule({
12-
imports: [FormsModule, MdCommonModule],
12+
imports: [FormsModule, MdCommonModule, StyleModule],
1313
exports: [
1414
MdButtonToggleGroup,
1515
MdButtonToggleGroupMultiple,
1616
MdButtonToggle,
1717
MdCommonModule,
1818
],
1919
declarations: [MdButtonToggleGroup, MdButtonToggleGroupMultiple, MdButtonToggle],
20-
providers: [UNIQUE_SELECTION_DISPATCHER_PROVIDER, FocusOriginMonitor]
20+
providers: [UNIQUE_SELECTION_DISPATCHER_PROVIDER]
2121
})
2222
export class MdButtonToggleModule {}
2323

src/lib/button/button.ts

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
Renderer2,
1010
ViewEncapsulation
1111
} from '@angular/core';
12-
import {coerceBooleanProperty, FocusOriginMonitor} from '../core';
12+
import {coerceBooleanProperty, FocusOriginMonitor, Platform} from '../core';
1313
import {mixinDisabled, CanDisable} from '../core/common-behaviors/disabled';
1414

1515

@@ -22,9 +22,7 @@ import {mixinDisabled, CanDisable} from '../core/common-behaviors/disabled';
2222
*/
2323
@Directive({
2424
selector: 'button[md-button], button[mat-button], a[md-button], a[mat-button]',
25-
host: {
26-
'[class.mat-button]': 'true'
27-
}
25+
host: {'class': 'mat-button'}
2826
})
2927
export class MdButtonCssMatStyler {}
3028

@@ -36,9 +34,7 @@ export class MdButtonCssMatStyler {}
3634
selector:
3735
'button[md-raised-button], button[mat-raised-button], ' +
3836
'a[md-raised-button], a[mat-raised-button]',
39-
host: {
40-
'[class.mat-raised-button]': 'true'
41-
}
37+
host: {'class': 'mat-raised-button'}
4238
})
4339
export class MdRaisedButtonCssMatStyler {}
4440

@@ -49,9 +45,7 @@ export class MdRaisedButtonCssMatStyler {}
4945
@Directive({
5046
selector:
5147
'button[md-icon-button], button[mat-icon-button], a[md-icon-button], a[mat-icon-button]',
52-
host: {
53-
'[class.mat-icon-button]': 'true',
54-
}
48+
host: {'class': 'mat-icon-button'}
5549
})
5650
export class MdIconButtonCssMatStyler {}
5751

@@ -61,9 +55,7 @@ export class MdIconButtonCssMatStyler {}
6155
*/
6256
@Directive({
6357
selector: 'button[md-fab], button[mat-fab], a[md-fab], a[mat-fab]',
64-
host: {
65-
'[class.mat-fab]': 'true'
66-
}
58+
host: {'class': 'mat-fab'}
6759
})
6860
export class MdFabCssMatStyler {}
6961

@@ -73,9 +65,7 @@ export class MdFabCssMatStyler {}
7365
*/
7466
@Directive({
7567
selector: 'button[md-mini-fab], button[mat-mini-fab], a[md-mini-fab], a[mat-mini-fab]',
76-
host: {
77-
'[class.mat-mini-fab]': 'true'
78-
}
68+
host: {'class': 'mat-mini-fab'}
7969
})
8070
export class MdMiniFabCssMatStyler {}
8171

@@ -120,8 +110,11 @@ export class MdButton extends _MdButtonMixinBase implements OnDestroy, CanDisabl
120110
get disableRipple() { return this._disableRipple; }
121111
set disableRipple(v) { this._disableRipple = coerceBooleanProperty(v); }
122112

123-
constructor(private _elementRef: ElementRef, private _renderer: Renderer2,
124-
private _focusOriginMonitor: FocusOriginMonitor) {
113+
constructor(
114+
private _elementRef: ElementRef,
115+
private _renderer: Renderer2,
116+
private _platform: Platform,
117+
private _focusOriginMonitor: FocusOriginMonitor) {
125118
super();
126119
this._focusOriginMonitor.monitor(this._elementRef.nativeElement, this._renderer, true);
127120
}
@@ -169,6 +162,13 @@ export class MdButton extends _MdButtonMixinBase implements OnDestroy, CanDisabl
169162
* with either an 'md-' or 'mat-' prefix.
170163
*/
171164
_hasAttributeWithPrefix(...unprefixedAttributeNames: string[]) {
165+
// If not on the browser, say that there are none of the attributes present.
166+
// Since these only affect how the ripple displays (and ripples only happen on the client),
167+
// detecting these attributes isn't necessary when not on the browser.
168+
if (!this._platform.isBrowser) {
169+
return false;
170+
}
171+
172172
return unprefixedAttributeNames.some(suffix => {
173173
const el = this._getHostElement();
174174

@@ -195,8 +195,12 @@ export class MdButton extends _MdButtonMixinBase implements OnDestroy, CanDisabl
195195
encapsulation: ViewEncapsulation.None
196196
})
197197
export class MdAnchor extends MdButton {
198-
constructor(elementRef: ElementRef, renderer: Renderer2, focusOriginMonitor: FocusOriginMonitor) {
199-
super(elementRef, renderer, focusOriginMonitor);
198+
constructor(
199+
elementRef: ElementRef,
200+
renderer: Renderer2,
201+
platform: Platform,
202+
focusOriginMonitor: FocusOriginMonitor) {
203+
super(elementRef, renderer, platform, focusOriginMonitor);
200204
}
201205

202206
/** @docs-private */

src/lib/core/a11y/live-announcer.spec.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {inject, fakeAsync, tick, ComponentFixture, TestBed} from '@angular/core/
22
import {Component} from '@angular/core';
33
import {By} from '@angular/platform-browser';
44
import {LiveAnnouncer, LIVE_ANNOUNCER_ELEMENT_TOKEN} from './live-announcer';
5+
import {A11yModule} from '../index';
56

67

78
describe('LiveAnnouncer', () => {
@@ -11,8 +12,8 @@ describe('LiveAnnouncer', () => {
1112

1213
describe('with default element', () => {
1314
beforeEach(() => TestBed.configureTestingModule({
15+
imports: [A11yModule],
1416
declarations: [TestApp],
15-
providers: [LiveAnnouncer]
1617
}));
1718

1819
beforeEach(fakeAsync(inject([LiveAnnouncer], (la: LiveAnnouncer) => {
@@ -77,11 +78,9 @@ describe('LiveAnnouncer', () => {
7778
customLiveElement = document.createElement('div');
7879

7980
return TestBed.configureTestingModule({
81+
imports: [A11yModule],
8082
declarations: [TestApp],
81-
providers: [
82-
{provide: LIVE_ANNOUNCER_ELEMENT_TOKEN, useValue: customLiveElement},
83-
LiveAnnouncer,
84-
],
83+
providers: [{provide: LIVE_ANNOUNCER_ELEMENT_TOKEN, useValue: customLiveElement}],
8584
});
8685
});
8786

src/lib/core/a11y/live-announcer.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {
55
Inject,
66
SkipSelf,
77
} from '@angular/core';
8+
import {Platform} from '../platform/platform';
9+
810

911
export const LIVE_ANNOUNCER_ELEMENT_TOKEN = new InjectionToken<HTMLElement>('liveAnnouncerElement');
1012

@@ -16,12 +18,16 @@ export class LiveAnnouncer {
1618

1719
private _liveElement: Element;
1820

19-
constructor(@Optional() @Inject(LIVE_ANNOUNCER_ELEMENT_TOKEN) elementToken: any) {
20-
21-
// We inject the live element as `any` because the constructor signature cannot reference
22-
// browser globals (HTMLElement) on non-browser environments, since having a class decorator
23-
// causes TypeScript to preserve the constructor signature types.
24-
this._liveElement = elementToken || this._createLiveElement();
21+
constructor(
22+
@Optional() @Inject(LIVE_ANNOUNCER_ELEMENT_TOKEN) elementToken: any,
23+
platform: Platform) {
24+
// Only do anything if we're on the browser platform.
25+
if (platform.isBrowser) {
26+
// We inject the live element as `any` because the constructor signature cannot reference
27+
// browser globals (HTMLElement) on non-browser environments, since having a class decorator
28+
// causes TypeScript to preserve the constructor signature types.
29+
this._liveElement = elementToken || this._createLiveElement();
30+
}
2531
}
2632

2733
/**
@@ -64,16 +70,18 @@ export class LiveAnnouncer {
6470

6571
}
6672

67-
export function LIVE_ANNOUNCER_PROVIDER_FACTORY(parentDispatcher: LiveAnnouncer, liveElement: any) {
68-
return parentDispatcher || new LiveAnnouncer(liveElement);
73+
export function LIVE_ANNOUNCER_PROVIDER_FACTORY(
74+
parentDispatcher: LiveAnnouncer, liveElement: any, platform: Platform) {
75+
return parentDispatcher || new LiveAnnouncer(liveElement, platform);
6976
}
7077

7178
export const LIVE_ANNOUNCER_PROVIDER = {
7279
// If there is already a LiveAnnouncer available, use that. Otherwise, provide a new one.
7380
provide: LiveAnnouncer,
7481
deps: [
7582
[new Optional(), new SkipSelf(), LiveAnnouncer],
76-
[new Optional(), new Inject(LIVE_ANNOUNCER_ELEMENT_TOKEN)]
83+
[new Optional(), new Inject(LIVE_ANNOUNCER_ELEMENT_TOKEN)],
84+
Platform,
7785
],
7886
useFactory: LIVE_ANNOUNCER_PROVIDER_FACTORY
7987
};

src/lib/core/overlay/overlay-directives.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {coerceBooleanProperty} from '../coercion/boolean-property';
2929
import {ESCAPE} from '../keyboard/keycodes';
3030
import {ScrollDispatcher} from './scroll/scroll-dispatcher';
3131
import {Subscription} from 'rxjs/Subscription';
32+
import {ScrollDispatchModule} from './scroll/index';
3233

3334

3435
/** Default set of positions for the overlay. Follows the behavior of a dropdown. */
@@ -323,9 +324,9 @@ export class ConnectedOverlayDirective implements OnDestroy {
323324

324325

325326
@NgModule({
326-
imports: [PortalModule],
327-
exports: [ConnectedOverlayDirective, OverlayOrigin, Scrollable],
328-
declarations: [ConnectedOverlayDirective, OverlayOrigin, Scrollable],
327+
imports: [PortalModule, ScrollDispatchModule],
328+
exports: [ConnectedOverlayDirective, OverlayOrigin, ScrollDispatchModule],
329+
declarations: [ConnectedOverlayDirective, OverlayOrigin],
329330
providers: [OVERLAY_PROVIDERS],
330331
})
331332
export class OverlayModule {}

src/lib/core/overlay/overlay.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {OverlayRef} from './overlay-ref';
1212
import {OverlayPositionBuilder} from './position/overlay-position-builder';
1313
import {VIEWPORT_RULER_PROVIDER} from './position/viewport-ruler';
1414
import {OverlayContainer, OVERLAY_CONTAINER_PROVIDER} from './overlay-container';
15-
import {SCROLL_DISPATCHER_PROVIDER} from './scroll/scroll-dispatcher';
1615

1716

1817
/** Next overlay unique ID. */
@@ -94,6 +93,5 @@ export const OVERLAY_PROVIDERS: Provider[] = [
9493
Overlay,
9594
OverlayPositionBuilder,
9695
VIEWPORT_RULER_PROVIDER,
97-
SCROLL_DISPATCHER_PROVIDER,
9896
OVERLAY_CONTAINER_PROVIDER,
9997
];

src/lib/core/overlay/position/connected-position-strategy.spec.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {Scrollable} from '../scroll/scrollable';
77
import {Subscription} from 'rxjs/Subscription';
88
import {TestBed, inject} from '@angular/core/testing';
99
import Spy = jasmine.Spy;
10-
import {SCROLL_DISPATCHER_PROVIDER} from '../scroll/scroll-dispatcher';
10+
import {ScrollDispatchModule} from '../scroll/index';
1111

1212

1313
// Default width and height of the overlay and origin panels throughout these tests.
@@ -23,7 +23,8 @@ describe('ConnectedPositionStrategy', () => {
2323
let viewportRuler: ViewportRuler;
2424

2525
beforeEach(() => TestBed.configureTestingModule({
26-
providers: [VIEWPORT_RULER_PROVIDER, SCROLL_DISPATCHER_PROVIDER]
26+
imports: [ScrollDispatchModule],
27+
providers: [VIEWPORT_RULER_PROVIDER]
2728
}));
2829

2930
beforeEach(inject([ViewportRuler], (_ruler: ViewportRuler) => {

src/lib/core/overlay/position/viewport-ruler.spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {ViewportRuler, VIEWPORT_RULER_PROVIDER} from './viewport-ruler';
22
import {TestBed, inject} from '@angular/core/testing';
3-
import {SCROLL_DISPATCHER_PROVIDER} from '../scroll/scroll-dispatcher';
3+
import {ScrollDispatchModule} from '../scroll/index';
4+
45

56
// For all tests, we assume the browser window is 1024x786 (outerWidth x outerHeight).
67
// The karma config has been set to this for local tests, and it is the default size
@@ -22,7 +23,8 @@ describe('ViewportRuler', () => {
2223
veryLargeElement.style.height = '6000px';
2324

2425
beforeEach(() => TestBed.configureTestingModule({
25-
providers: [VIEWPORT_RULER_PROVIDER, SCROLL_DISPATCHER_PROVIDER]
26+
imports: [ScrollDispatchModule],
27+
providers: [VIEWPORT_RULER_PROVIDER]
2628
}));
2729

2830
beforeEach(inject([ViewportRuler], (viewportRuler: ViewportRuler) => {

src/lib/core/overlay/position/viewport-ruler.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,18 @@ export class ViewportRuler {
1313
private _documentRect?: ClientRect;
1414

1515
constructor(scrollDispatcher: ScrollDispatcher) {
16-
// Initially cache the document rectangle.
17-
this._cacheViewportGeometry();
18-
1916
// Subscribe to scroll and resize events and update the document rectangle on changes.
2017
scrollDispatcher.scrolled(null, () => this._cacheViewportGeometry());
2118
}
2219

2320
/** Gets a ClientRect for the viewport's bounds. */
2421
getViewportRect(documentRect = this._documentRect): ClientRect {
22+
// Cache the document bounding rect so that we don't recompute it for multiple calls.
23+
if (!documentRect) {
24+
this._cacheViewportGeometry();
25+
documentRect = this._documentRect;
26+
}
27+
2528
// Use the document element's bounding rect rather than the window scroll properties
2629
// (e.g. pageYOffset, scrollY) due to in issue in Chrome and IE where window scroll
2730
// properties and client coordinates (boundingClientRect, clientX/Y, etc.) are in different
@@ -51,6 +54,12 @@ export class ViewportRuler {
5154
* @param documentRect
5255
*/
5356
getViewportScrollPosition(documentRect = this._documentRect) {
57+
// Cache the document bounding rect so that we don't recompute it for multiple calls.
58+
if (!documentRect) {
59+
this._cacheViewportGeometry();
60+
documentRect = this._documentRect;
61+
}
62+
5463
// The top-left-corner of the viewport is determined by the scroll position of the document
5564
// body, normally just (scrollLeft, scrollTop). However, Chrome and Firefox disagree about
5665
// whether `document.body` or `document.documentElement` is the scrolled element, so reading

src/lib/core/overlay/scroll/index.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import {NgModule} from '@angular/core';
2+
import {SCROLL_DISPATCHER_PROVIDER} from './scroll-dispatcher';
3+
import {Scrollable} from './scrollable';
4+
import {PlatformModule} from '../../platform/index';
5+
6+
export {Scrollable} from './scrollable';
7+
export {ScrollDispatcher} from './scroll-dispatcher';
8+
9+
@NgModule({
10+
imports: [PlatformModule],
11+
exports: [Scrollable],
12+
declarations: [Scrollable],
13+
providers: [SCROLL_DISPATCHER_PROVIDER],
14+
})
15+
export class ScrollDispatchModule { }

src/lib/core/overlay/scroll/scroll-dispatcher.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import {Injectable, ElementRef, Optional, SkipSelf, NgZone} from '@angular/core';
1+
import {ElementRef, Injectable, NgZone, Optional, SkipSelf} from '@angular/core';
2+
import {Platform} from '../../platform/index';
23
import {Scrollable} from './scrollable';
34
import {Subject} from 'rxjs/Subject';
45
import {Observable} from 'rxjs/Observable';
@@ -17,7 +18,7 @@ export const DEFAULT_SCROLL_TIME = 20;
1718
*/
1819
@Injectable()
1920
export class ScrollDispatcher {
20-
constructor(private _ngZone: NgZone) { }
21+
constructor(private _ngZone: NgZone, private _platform: Platform) { }
2122

2223
/** Subject for notifying that a registered scrollable reference element has been scrolled. */
2324
_scrolled: Subject<void> = new Subject<void>();
@@ -62,6 +63,11 @@ export class ScrollDispatcher {
6263
* to override the default "throttle" time.
6364
*/
6465
scrolled(auditTimeInMs: number = DEFAULT_SCROLL_TIME, callback: () => any): Subscription {
66+
// Scroll events can only happen on the browser, so do nothing if we're not on the browser.
67+
if (!this._platform.isBrowser) {
68+
return Subscription.EMPTY;
69+
}
70+
6571
// In the case of a 0ms delay, use an observable without auditTime
6672
// since it does add a perceptible delay in processing overhead.
6773
let observable = auditTimeInMs > 0 ?
@@ -126,14 +132,14 @@ export class ScrollDispatcher {
126132
}
127133
}
128134

129-
export function SCROLL_DISPATCHER_PROVIDER_FACTORY(parentDispatcher: ScrollDispatcher,
130-
ngZone: NgZone) {
131-
return parentDispatcher || new ScrollDispatcher(ngZone);
135+
export function SCROLL_DISPATCHER_PROVIDER_FACTORY(
136+
parentDispatcher: ScrollDispatcher, ngZone: NgZone, platform: Platform) {
137+
return parentDispatcher || new ScrollDispatcher(ngZone, platform);
132138
}
133139

134140
export const SCROLL_DISPATCHER_PROVIDER = {
135141
// If there is already a ScrollDispatcher available, use that. Otherwise, provide a new one.
136142
provide: ScrollDispatcher,
137-
deps: [[new Optional(), new SkipSelf(), ScrollDispatcher], NgZone],
143+
deps: [[new Optional(), new SkipSelf(), ScrollDispatcher], NgZone, Platform],
138144
useFactory: SCROLL_DISPATCHER_PROVIDER_FACTORY
139145
};

src/lib/core/platform/platform.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ const hasV8BreakIterator = typeof(window) !== 'undefined' ?
1515
*/
1616
@Injectable()
1717
export class Platform {
18+
isBrowser: boolean = typeof document === 'object' && !!document;
19+
1820
/** Layout Engines */
1921
EDGE = /(edge)/i.test(navigator.userAgent);
2022
TRIDENT = /(msie|trident)/i.test(navigator.userAgent);

0 commit comments

Comments
 (0)