license import { Injectable , NgZone , OnDestroy } from '@angular/core' ; import { MediaMatcher } from './media-matcher' ; import { asapScheduler , combineLatest , fromEventPattern , Observable , Subject } from 'rxjs' ; import { debounceTime , map , startWith , takeUntil } from 'rxjs/operators' ; import { coerceArray } from '@angular/cdk/coercion' ; /** The current state of a layout breakpoint. */ export interface BreakpointState { /** Whether the breakpoint is currently matching. */ matches : boolean ; /**
     * A key boolean pair for each query provided to the observe method,
     * with its current matched state.
     */ breakpoints : { [ key ] : boolean ; } ; } /** The current state of a layout breakpoint. */ interface InternalBreakpointState { /** Whether the breakpoint is currently matching. */ matches : boolean ; /** The media query being to be matched */ query : string ; } interface Query { observable : Observable < InternalBreakpointState > ; mql : MediaQueryList ; } /** Utility for checking the matching state of @media queries. */ @ Injectable ( { providedIn : 'root' } ) export class BreakpointObserver implements OnDestroy { /**  A map of all media queries currently being listened for. */ private _queries = new Map < string , Query > ( ) ; /** A subject for all other observables to takeUntil based on. */ private _destroySubject = new Subject < void > ( ) ; constructor ( private mediaMatcher , private zone ) { } /** Completes the active subject, signalling to all other observables to complete. */ ngOnDestroy ( ) { this . _destroySubject . next ( ) ; this . _destroySubject . complete ( ) ; } param value returns isMatched ( value ) { const queries = splitQueries ( coerceArray ( value ) ) ; return queries . some ( mediaQuery => this . _registerQuery ( mediaQuery ) . mql . matches ) ; } param value returns observe ( value ) { const queries = splitQueries ( coerceArray ( value ) ) ; const observables = queries . map ( query => this . _registerQuery ( query ) . observable ) ; return combineLatest ( observables ) . pipe ( debounceTime ( 0 , asapScheduler ) , map ( ( breakpointStates ) => { const response = { matches : false , breakpoints : { } , } ; breakpointStates . forEach ( ( state ) => { response . matches = response . matches || state . matches ; response . breakpoints [ state . query ] = state . matches ; } ) ; return response ; } ) ) ; } /** Registers a specific query to be listened for. */ private _registerQuery ( query ) { if ( this . _queries . has ( query ) ) { return this . _queries . get ( query ) ! ; } const mql = this . mediaMatcher . matchMedia ( query ) ; let queryListener ; const queryObservable = fromEventPattern < MediaQueryList > ( ( listener ) => { queryListener = ( e ) => this . zone . run ( ( ) => listener ( e ) ) ; mql . addListener ( queryListener ) ; } , ( ) => mql . removeListener ( queryListener ) ) . pipe ( startWith ( mql ) , map ( ( nextMql ) => ( { query , matches : nextMql . matches } ) ) , takeUntil ( this . _destroySubject ) ) ; const output = { observable : queryObservable , mql } ; this . _queries . set ( query , output ) ; return output ; } } /**
 * Split each query string into separate query strings if two queries are provided as comma
 * separated.
 */ function splitQueries ( queries ) { return queries . map ( ( query ) => query . split ( ',' ) ) . reduce ( ( a1 , a2 ) => a1 . concat ( a2 ) ) . map ( query => query . trim ( ) ) ; }