ipsos-components
Version:
Material Design components for Angular
105 lines (93 loc) • 4.12 kB
text/typescript
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Injectable, NgZone, OnDestroy} from '@angular/core';
import {MediaMatcher} from './media-matcher';
import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject';
import {map} from 'rxjs/operators/map';
import {startWith} from 'rxjs/operators/startWith';
import {takeUntil} from 'rxjs/operators/takeUntil';
import {coerceArray} from '@angular/cdk/coercion';
import {combineLatest} from 'rxjs/observable/combineLatest';
import {fromEventPattern} from 'rxjs/observable/fromEventPattern';
/** The current state of a layout breakpoint. */
export interface BreakpointState {
/** Whether the breakpoint is currently matching. */
matches: boolean;
}
interface Query {
observable: Observable<BreakpointState>;
mql: MediaQueryList;
}
/** Utility for checking the matching state of @media queries. */
()
export class BreakpointObserver implements OnDestroy {
/** A map of all media queries currently being listened for. */
private _queries: Map<string, Query> = new Map();
/** A subject for all other observables to takeUntil based on. */
private _destroySubject: Subject<{}> = new Subject();
constructor(private mediaMatcher: MediaMatcher, private zone: NgZone) {}
/** Completes the active subject, signalling to all other observables to complete. */
ngOnDestroy() {
this._destroySubject.next();
this._destroySubject.complete();
}
/**
* Whether one or more media queries match the current viewport size.
* @param value One or more media queries to check.
* @returns Whether any of the media queries match.
*/
isMatched(value: string | string[]): boolean {
let queries = coerceArray(value);
return queries.some(mediaQuery => this._registerQuery(mediaQuery).mql.matches);
}
/**
* Gets an observable of results for the given queries that will emit new results for any changes
* in matching of the given queries.
* @returns A stream of matches for the given queries.
*/
observe(value: string | string[]): Observable<BreakpointState> {
let queries = coerceArray(value);
let observables = queries.map(query => this._registerQuery(query).observable);
return combineLatest(observables, (a: BreakpointState, b: BreakpointState) => {
return {
matches: !!((a && a.matches) || (b && b.matches)),
};
});
}
/** Registers a specific query to be listened for. */
private _registerQuery(query: string): Query {
// Only set up a new MediaQueryList if it is not already being listened for.
if (this._queries.has(query)) {
return this._queries.get(query)!;
}
let mql: MediaQueryList = this.mediaMatcher.matchMedia(query);
// Create callback for match changes and add it is as a listener.
let queryObservable = fromEventPattern(
// Listener callback methods are wrapped to be placed back in ngZone. Callbacks must be placed
// back into the zone because matchMedia is only included in Zone.js by loading the
// webapis-media-query.js file alongside the zone.js file. Additionally, some browsers do not
// have MediaQueryList inherit from EventTarget, which causes inconsistencies in how Zone.js
// patches it.
(listener: MediaQueryListListener) => {
mql.addListener((e: MediaQueryList) => this.zone.run(() => listener(e)));
},
(listener: MediaQueryListListener) => {
mql.removeListener((e: MediaQueryList) => this.zone.run(() => listener(e)));
})
.pipe(
takeUntil(this._destroySubject),
startWith(mql),
map((nextMql: MediaQueryList) => ({matches: nextMql.matches}))
);
// Add the MediaQueryList to the set of queries.
let output = {observable: queryObservable, mql: mql};
this._queries.set(query, output);
return output;
}
}