-
Notifications
You must be signed in to change notification settings - Fork 6.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(scroll): provide directive and service to listen to scrolling (#…
…2188) * feat(scroll): provide directive and service to listen to scrolling * review response * fix lint * move scroll to overlay * rename scroll in tooltip * remove reference tot he live announcer
- Loading branch information
1 parent
a1f9028
commit 9b68e68
Showing
10 changed files
with
229 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import {inject, TestBed, async, ComponentFixture} from '@angular/core/testing'; | ||
import {NgModule, Component, ViewChild, ElementRef} from '@angular/core'; | ||
import {ScrollDispatcher} from './scroll-dispatcher'; | ||
import {OverlayModule} from '../overlay-directives'; | ||
import {Scrollable} from './scrollable'; | ||
|
||
describe('Scroll Dispatcher', () => { | ||
let scroll: ScrollDispatcher; | ||
let fixture: ComponentFixture<ScrollingComponent>; | ||
|
||
beforeEach(async(() => { | ||
TestBed.configureTestingModule({ | ||
imports: [OverlayModule.forRoot(), ScrollTestModule], | ||
}); | ||
|
||
TestBed.compileComponents(); | ||
})); | ||
|
||
beforeEach(inject([ScrollDispatcher], (s: ScrollDispatcher) => { | ||
scroll = s; | ||
|
||
fixture = TestBed.createComponent(ScrollingComponent); | ||
fixture.detectChanges(); | ||
})); | ||
|
||
it('should be registered with the scrollable directive with the scroll service', () => { | ||
const componentScrollable = fixture.componentInstance.scrollable; | ||
expect(scroll.scrollableReferences.has(componentScrollable)).toBe(true); | ||
}); | ||
|
||
it('should have the scrollable directive deregistered when the component is destroyed', () => { | ||
const componentScrollable = fixture.componentInstance.scrollable; | ||
expect(scroll.scrollableReferences.has(componentScrollable)).toBe(true); | ||
|
||
fixture.destroy(); | ||
expect(scroll.scrollableReferences.has(componentScrollable)).toBe(false); | ||
}); | ||
|
||
it('should notify through the directive and service that a scroll event occurred', () => { | ||
let hasDirectiveScrollNotified = false; | ||
// Listen for notifications from scroll directive | ||
let scrollable = fixture.componentInstance.scrollable; | ||
scrollable.elementScrolled().subscribe(() => { hasDirectiveScrollNotified = true; }); | ||
|
||
// Listen for notifications from scroll service | ||
let hasServiceScrollNotified = false; | ||
scroll.scrolled().subscribe(() => { hasServiceScrollNotified = true; }); | ||
|
||
// Emit a scroll event from the scrolling element in our component. | ||
// This event should be picked up by the scrollable directive and notify. | ||
// The notification should be picked up by the service. | ||
const scrollEvent = document.createEvent('UIEvents'); | ||
scrollEvent.initUIEvent('scroll', true, true, window, 0); | ||
fixture.componentInstance.scrollingElement.nativeElement.dispatchEvent(scrollEvent); | ||
|
||
expect(hasDirectiveScrollNotified).toBe(true); | ||
expect(hasServiceScrollNotified).toBe(true); | ||
}); | ||
}); | ||
|
||
|
||
/** Simple component that contains a large div and can be scrolled. */ | ||
@Component({ | ||
template: `<div #scrollingElement cdk-scrollable style="height: 9999px"></div>` | ||
}) | ||
class ScrollingComponent { | ||
@ViewChild(Scrollable) scrollable: Scrollable; | ||
@ViewChild('scrollingElement') scrollingElement: ElementRef; | ||
} | ||
|
||
const TEST_COMPONENTS = [ScrollingComponent]; | ||
@NgModule({ | ||
imports: [OverlayModule], | ||
providers: [ScrollDispatcher], | ||
exports: TEST_COMPONENTS, | ||
declarations: TEST_COMPONENTS, | ||
entryComponents: TEST_COMPONENTS, | ||
}) | ||
class ScrollTestModule { } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import {Injectable} from '@angular/core'; | ||
import {Scrollable} from './scrollable'; | ||
import {Subject} from 'rxjs/Subject'; | ||
import {Observable} from 'rxjs/Observable'; | ||
import {Subscription} from 'rxjs/Subscription'; | ||
import 'rxjs/add/observable/fromEvent'; | ||
|
||
|
||
/** | ||
* Service contained all registered Scrollable references and emits an event when any one of the | ||
* Scrollable references emit a scrolled event. | ||
*/ | ||
@Injectable() | ||
export class ScrollDispatcher { | ||
/** Subject for notifying that a registered scrollable reference element has been scrolled. */ | ||
_scrolled: Subject<void> = new Subject<void>(); | ||
|
||
/** | ||
* Map of all the scrollable references that are registered with the service and their | ||
* scroll event subscriptions. | ||
*/ | ||
scrollableReferences: WeakMap<Scrollable, Subscription> = new WeakMap(); | ||
|
||
constructor() { | ||
// By default, notify a scroll event when the document is scrolled or the window is resized. | ||
Observable.fromEvent(window.document, 'scroll').subscribe(() => this._notify()); | ||
Observable.fromEvent(window, 'resize').subscribe(() => this._notify()); | ||
} | ||
|
||
/** | ||
* Registers a Scrollable with the service and listens for its scrolled events. When the | ||
* scrollable is scrolled, the service emits the event in its scrolled observable. | ||
*/ | ||
register(scrollable: Scrollable): void { | ||
const scrollSubscription = scrollable.elementScrolled().subscribe(() => this._notify()); | ||
this.scrollableReferences.set(scrollable, scrollSubscription); | ||
} | ||
|
||
/** | ||
* Deregisters a Scrollable reference and unsubscribes from its scroll event observable. | ||
*/ | ||
deregister(scrollable: Scrollable): void { | ||
this.scrollableReferences.get(scrollable).unsubscribe(); | ||
this.scrollableReferences.delete(scrollable); | ||
} | ||
|
||
/** | ||
* Returns an observable that emits an event whenever any of the registered Scrollable | ||
* references (or window, document, or body) fire a scrolled event. | ||
* TODO: Add an event limiter that includes throttle with the leading and trailing events. | ||
*/ | ||
scrolled(): Observable<void> { | ||
return this._scrolled.asObservable(); | ||
} | ||
|
||
/** Sends a notification that a scroll event has been fired. */ | ||
_notify() { | ||
this._scrolled.next(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { | ||
Directive, ElementRef, OnInit, OnDestroy | ||
} from '@angular/core'; | ||
import {Observable} from 'rxjs/Observable'; | ||
import {ScrollDispatcher} from './scroll-dispatcher'; | ||
import 'rxjs/add/observable/fromEvent'; | ||
|
||
|
||
/** | ||
* Sends an event when the directive's element is scrolled. Registers itself with the | ||
* ScrollDispatcher service to include itself as part of its collection of scrolling events that it | ||
* can be listened to through the service. | ||
*/ | ||
@Directive({ | ||
selector: '[cdk-scrollable]' | ||
}) | ||
export class Scrollable implements OnInit, OnDestroy { | ||
constructor(private _elementRef: ElementRef, private _scroll: ScrollDispatcher) {} | ||
|
||
ngOnInit() { | ||
this._scroll.register(this); | ||
} | ||
|
||
ngOnDestroy() { | ||
this._scroll.deregister(this); | ||
} | ||
|
||
/** Returns observable that emits when the scroll event is fired on the host element. */ | ||
elementScrolled(): Observable<any> { | ||
return Observable.fromEvent(this._elementRef.nativeElement, 'scroll'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters