Skip to content

Commit 9f2864d

Browse files
committed
feat(viewport-ruler): add common window resize handler
Adds the `change` method to the `ViewportRuler`, allowing for components to hook up to a common window resize handler.
1 parent ec4ea06 commit 9f2864d

File tree

9 files changed

+86
-40
lines changed

9 files changed

+86
-40
lines changed

src/cdk/scrolling/scroll-dispatcher.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ describe('Scroll Dispatcher', () => {
7272

7373
scroll.scrolled(0, () => {});
7474
dispatchFakeEvent(document, 'scroll');
75-
dispatchFakeEvent(window, 'resize');
7675

7776
expect(spy).not.toHaveBeenCalled();
7877
subscription.unsubscribe();

src/cdk/scrolling/scroll-dispatcher.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import {Platform} from '@angular/cdk/platform';
1111
import {Subject} from 'rxjs/Subject';
1212
import {Subscription} from 'rxjs/Subscription';
1313
import {fromEvent} from 'rxjs/observable/fromEvent';
14-
import {merge} from 'rxjs/observable/merge';
1514
import {auditTime} from 'rxjs/operator/auditTime';
1615
import {Scrollable} from './scrollable';
1716

@@ -87,10 +86,7 @@ export class ScrollDispatcher {
8786

8887
if (!this._globalSubscription) {
8988
this._globalSubscription = this._ngZone.runOutsideAngular(() => {
90-
return merge(
91-
fromEvent(window.document, 'scroll'),
92-
fromEvent(window, 'resize')
93-
).subscribe(() => this._notify());
89+
return fromEvent(window.document, 'scroll').subscribe(() => this._notify());
9490
});
9591
}
9692

src/cdk/scrolling/viewport-ruler.spec.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import {TestBed, inject} from '@angular/core/testing';
1+
import {TestBed, inject, fakeAsync, tick} from '@angular/core/testing';
22
import {ScrollDispatchModule} from './public_api';
33
import {ViewportRuler, VIEWPORT_RULER_PROVIDER} from './viewport-ruler';
4+
import {dispatchFakeEvent} from '@angular/cdk/testing';
45

56

67
// For all tests, we assume the browser window is 1024x786 (outerWidth x outerHeight).
@@ -101,4 +102,37 @@ describe('ViewportRuler', () => {
101102

102103
document.body.removeChild(veryLargeElement);
103104
});
105+
106+
describe('changed event', () => {
107+
it('should dispatch an event when the window is resized', () => {
108+
const spy = jasmine.createSpy('viewport changed spy');
109+
const subscription = ruler.change(0).subscribe(spy);
110+
111+
dispatchFakeEvent(window, 'resize');
112+
expect(spy).toHaveBeenCalledWith('resize');
113+
subscription.unsubscribe();
114+
});
115+
116+
it('should dispatch an event when the orientation is changed', () => {
117+
const spy = jasmine.createSpy('viewport changed spy');
118+
const subscription = ruler.change(0).subscribe(spy);
119+
120+
dispatchFakeEvent(window, 'orientationchange');
121+
expect(spy).toHaveBeenCalledWith('orientationchange');
122+
subscription.unsubscribe();
123+
});
124+
125+
it('should be able to throttle the callback', fakeAsync(() => {
126+
const spy = jasmine.createSpy('viewport changed spy');
127+
const subscription = ruler.change(1337).subscribe(spy);
128+
129+
dispatchFakeEvent(window, 'resize');
130+
expect(spy).not.toHaveBeenCalled();
131+
132+
tick(1337);
133+
134+
expect(spy).toHaveBeenCalledTimes(1);
135+
subscription.unsubscribe();
136+
}));
137+
});
104138
});

src/cdk/scrolling/viewport-ruler.ts

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,17 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {Injectable, Optional, SkipSelf} from '@angular/core';
9+
import {Injectable, Optional, SkipSelf, NgZone} from '@angular/core';
10+
import {Platform} from '@angular/cdk/platform';
1011
import {ScrollDispatcher} from './scroll-dispatcher';
12+
import {Observable} from 'rxjs/Observable';
13+
import {Subject} from 'rxjs/Subject';
14+
import {fromEvent} from 'rxjs/observable/fromEvent';
15+
import {merge} from 'rxjs/observable/merge';
16+
import {auditTime} from 'rxjs/operator/auditTime';
1117

18+
/** Time in ms to throttle the resize events by default. */
19+
export const DEFAULT_RESIZE_TIME = 20;
1220

1321
/**
1422
* Simple utility for getting the bounds of the browser viewport.
@@ -20,9 +28,22 @@ export class ViewportRuler {
2028
/** Cached document client rectangle. */
2129
private _documentRect?: ClientRect;
2230

23-
constructor(scrollDispatcher: ScrollDispatcher) {
31+
/** Stream of viewport change events. */
32+
private _changed = new Subject<string>();
33+
34+
constructor(platform: Platform, ngZone: NgZone, scrollDispatcher: ScrollDispatcher) {
35+
if (platform.isBrowser) {
36+
ngZone.runOutsideAngular(() => {
37+
merge<Event>(
38+
fromEvent(window, 'resize'),
39+
fromEvent(window, 'orientationchange')
40+
).subscribe(event => this._changed.next(event.type));
41+
});
42+
}
43+
2444
// Subscribe to scroll and resize events and update the document rectangle on changes.
2545
scrollDispatcher.scrolled(0, () => this._cacheViewportGeometry());
46+
this.change().subscribe(() => this._cacheViewportGeometry());
2647
}
2748

2849
/** Gets a ClientRect for the viewport's bounds. */
@@ -56,7 +77,6 @@ export class ViewportRuler {
5677
};
5778
}
5879

59-
6080
/**
6181
* Gets the (top, left) scroll position of the viewport.
6282
* @param documentRect
@@ -75,31 +95,40 @@ export class ViewportRuler {
7595
// `document.documentElement` works consistently, where the `top` and `left` values will
7696
// equal negative the scroll position.
7797
const top = -documentRect!.top || document.body.scrollTop || window.scrollY ||
78-
document.documentElement.scrollTop || 0;
98+
document.documentElement.scrollTop || 0;
7999

80100
const left = -documentRect!.left || document.body.scrollLeft || window.scrollX ||
81101
document.documentElement.scrollLeft || 0;
82102

83103
return {top, left};
84104
}
85105

106+
/**
107+
* Returns a stream that emits whenever the size of the viewport changes.
108+
* @param throttle Time in milliseconds to throttle the stream.
109+
*/
110+
change(throttleTime: number = DEFAULT_RESIZE_TIME): Observable<string> {
111+
return throttleTime > 0 ? auditTime.call(this._changed, throttleTime) : this._changed;
112+
}
113+
86114
/** Caches the latest client rectangle of the document element. */
87115
_cacheViewportGeometry() {
88116
this._documentRect = document.documentElement.getBoundingClientRect();
89117
}
90-
91118
}
92119

93120
/** @docs-private */
94121
export function VIEWPORT_RULER_PROVIDER_FACTORY(parentRuler: ViewportRuler,
122+
platform: Platform,
123+
ngZone: NgZone,
95124
scrollDispatcher: ScrollDispatcher) {
96-
return parentRuler || new ViewportRuler(scrollDispatcher);
125+
return parentRuler || new ViewportRuler(platform, ngZone, scrollDispatcher);
97126
}
98127

99128
/** @docs-private */
100129
export const VIEWPORT_RULER_PROVIDER = {
101130
// If there is already a ViewportRuler available, use that. Otherwise, provide a new one.
102131
provide: ViewportRuler,
103-
deps: [[new Optional(), new SkipSelf(), ViewportRuler], ScrollDispatcher],
132+
deps: [[new Optional(), new SkipSelf(), ViewportRuler], Platform, NgZone, ScrollDispatcher],
104133
useFactory: VIEWPORT_RULER_PROVIDER_FACTORY
105134
};

src/lib/tabs/tab-group.spec.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import {async, ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/t
22
import {Component, QueryList, ViewChild, ViewChildren} from '@angular/core';
33
import {BrowserAnimationsModule, NoopAnimationsModule} from '@angular/platform-browser/animations';
44
import {By} from '@angular/platform-browser';
5-
import {ViewportRuler} from '@angular/cdk/scrolling';
6-
import {dispatchFakeEvent, FakeViewportRuler} from '@angular/cdk/testing';
5+
import {dispatchFakeEvent} from '@angular/cdk/testing';
76
import {Observable} from 'rxjs/Observable';
87
import {MdTab, MdTabGroup, MdTabHeaderPosition, MdTabsModule} from './index';
98

@@ -19,9 +18,6 @@ describe('MdTabGroup', () => {
1918
AsyncTabsTestApp,
2019
DisabledTabsTestApp,
2120
TabGroupWithSimpleApi,
22-
],
23-
providers: [
24-
{provide: ViewportRuler, useClass: FakeViewportRuler},
2521
]
2622
});
2723

src/lib/tabs/tab-header.spec.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@ import {CommonModule} from '@angular/common';
66
import {By} from '@angular/platform-browser';
77
import {ENTER, LEFT_ARROW, RIGHT_ARROW, SPACE} from '@angular/cdk/keycodes';
88
import {PortalModule} from '@angular/cdk/portal';
9-
import {ViewportRuler} from '@angular/cdk/scrolling';
109
import {Direction, Directionality} from '@angular/cdk/bidi';
11-
import {dispatchFakeEvent, dispatchKeyboardEvent, FakeViewportRuler} from '@angular/cdk/testing';
10+
import {dispatchFakeEvent, dispatchKeyboardEvent} from '@angular/cdk/testing';
1211
import {MdTabHeader} from './tab-header';
1312
import {MdRippleModule} from '../core/ripple/index';
1413
import {MdInkBar} from './ink-bar';
@@ -35,7 +34,6 @@ describe('MdTabHeader', () => {
3534
],
3635
providers: [
3736
{provide: Directionality, useFactory: () => ({value: dir, change: change.asObservable()})},
38-
{provide: ViewportRuler, useClass: FakeViewportRuler},
3937
]
4038
});
4139

src/lib/tabs/tab-header.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@ import {
2626
} from '@angular/core';
2727
import {Directionality, Direction} from '@angular/cdk/bidi';
2828
import {RIGHT_ARROW, LEFT_ARROW, ENTER, SPACE} from '@angular/cdk/keycodes';
29-
import {auditTime, startWith} from '@angular/cdk/rxjs';
29+
import {startWith} from '@angular/cdk/rxjs';
3030
import {Subscription} from 'rxjs/Subscription';
3131
import {of as observableOf} from 'rxjs/observable/of';
3232
import {merge} from 'rxjs/observable/merge';
33-
import {fromEvent} from 'rxjs/observable/fromEvent';
3433
import {MdTabLabelWrapper} from './tab-label-wrapper';
3534
import {MdInkBar} from './ink-bar';
3635
import {CanDisableRipple, mixinDisableRipple} from '../core/common-behaviors/disable-ripple';
36+
import {ViewportRuler} from '@angular/cdk/scrolling';
3737

3838
/**
3939
* The directions that scrolling can go in when the header's tabs exceed the header width. 'After'
@@ -132,6 +132,7 @@ export class MdTabHeader extends _MdTabHeaderMixinBase
132132
constructor(private _elementRef: ElementRef,
133133
private _renderer: Renderer2,
134134
private _changeDetectorRef: ChangeDetectorRef,
135+
private _viewportRuler: ViewportRuler,
135136
@Optional() private _dir: Directionality) {
136137
super();
137138
}
@@ -184,9 +185,7 @@ export class MdTabHeader extends _MdTabHeaderMixinBase
184185
*/
185186
ngAfterContentInit() {
186187
const dirChange = this._dir ? this._dir.change : observableOf(null);
187-
const resize = typeof window !== 'undefined' ?
188-
auditTime.call(fromEvent(window, 'resize'), 150) :
189-
observableOf(null);
188+
const resize = this._viewportRuler.change(150);
190189

191190
this._realignInkBar = startWith.call(merge(dirChange, resize), null).subscribe(() => {
192191
this._updatePagination();

src/lib/tabs/tab-nav-bar/tab-nav-bar.spec.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import {async, ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing';
22
import {Component, ViewChild} from '@angular/core';
33
import {By} from '@angular/platform-browser';
4-
import {ViewportRuler} from '@angular/cdk/scrolling';
5-
import {dispatchFakeEvent, dispatchMouseEvent, FakeViewportRuler} from '@angular/cdk/testing';
4+
import {dispatchFakeEvent, dispatchMouseEvent} from '@angular/cdk/testing';
65
import {Direction, Directionality} from '@angular/cdk/bidi';
76
import {Subject} from 'rxjs/Subject';
87
import {MdTabNav, MdTabsModule, MdTabLink} from '../index';
@@ -23,8 +22,7 @@ describe('MdTabNavBar', () => {
2322
{provide: Directionality, useFactory: () => ({
2423
value: dir,
2524
change: dirChange.asObservable()
26-
})},
27-
{provide: ViewportRuler, useClass: FakeViewportRuler},
25+
})}
2826
]
2927
});
3028

@@ -173,7 +171,7 @@ describe('MdTabNavBar', () => {
173171
spyOn(inkBar, 'alignToElement');
174172

175173
dispatchFakeEvent(window, 'resize');
176-
tick(10);
174+
tick(150);
177175
fixture.detectChanges();
178176

179177
expect(inkBar.alignToElement).toHaveBeenCalled();

src/lib/tabs/tab-nav-bar/tab-nav-bar.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,11 @@ import {
2828
import {ViewportRuler} from '@angular/cdk/scrolling';
2929
import {Directionality} from '@angular/cdk/bidi';
3030
import {Platform} from '@angular/cdk/platform';
31-
import {auditTime, takeUntil} from '@angular/cdk/rxjs';
31+
import {takeUntil} from '@angular/cdk/rxjs';
3232
import {Subject} from 'rxjs/Subject';
3333
import {Subscription} from 'rxjs/Subscription';
3434
import {of as observableOf} from 'rxjs/observable/of';
3535
import {merge} from 'rxjs/observable/merge';
36-
import {fromEvent} from 'rxjs/observable/fromEvent';
3736
import {CanDisableRipple, mixinDisableRipple} from '../../core/common-behaviors/disable-ripple';
3837
import {coerceBooleanProperty} from '@angular/cdk/coercion';
3938
import {CanDisable, mixinDisabled} from '../../core/common-behaviors/disabled';
@@ -109,7 +108,8 @@ export class MdTabNav extends _MdTabNavMixinBase implements AfterContentInit, Ca
109108
elementRef: ElementRef,
110109
@Optional() private _dir: Directionality,
111110
private _ngZone: NgZone,
112-
private _changeDetectorRef: ChangeDetectorRef) {
111+
private _changeDetectorRef: ChangeDetectorRef,
112+
private _viewportRuler: ViewportRuler) {
113113
super(renderer, elementRef);
114114
}
115115

@@ -125,12 +125,9 @@ export class MdTabNav extends _MdTabNavMixinBase implements AfterContentInit, Ca
125125

126126
ngAfterContentInit(): void {
127127
this._resizeSubscription = this._ngZone.runOutsideAngular(() => {
128-
let dirChange = this._dir ? this._dir.change : observableOf(null);
129-
let resize = typeof window !== 'undefined' ?
130-
auditTime.call(fromEvent(window, 'resize'), 10) :
131-
observableOf(null);
128+
const dirChange = this._dir ? this._dir.change : observableOf(null);
132129

133-
return takeUntil.call(merge(dirChange, resize), this._onDestroy)
130+
return takeUntil.call(merge(dirChange, this._viewportRuler.change(10)), this._onDestroy)
134131
.subscribe(() => this._alignInkBar());
135132
});
136133
this._setLinkDisableRipple();

0 commit comments

Comments
 (0)