Skip to content

Commit

Permalink
feat(breakpoint-observer): Emit matching state of each query provided (
Browse files Browse the repository at this point in the history
  • Loading branch information
josephperrott authored Aug 8, 2018
1 parent e09ec34 commit 5a560b2
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 18 deletions.
37 changes: 27 additions & 10 deletions src/cdk/layout/breakpoints-observer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe('BreakpointObserver', () => {
});

it('retrieves the whether a query is currently matched', () => {
let query = 'everything starts as true in the FakeMediaMatcher';
const query = 'everything starts as true in the FakeMediaMatcher';
expect(breakpointManager.isMatched(query)).toBeTruthy();
});

Expand All @@ -61,15 +61,15 @@ describe('BreakpointObserver', () => {
});

it('accepts an array of queries', () => {
let queries = ['1 query', '2 query', 'red query', 'blue query'];
const queries = ['1 query', '2 query', 'red query', 'blue query'];
breakpointManager.observe(queries);
expect(mediaMatcher.queryCount).toBe(queries.length);
});

it('completes all events when the breakpoint manager is destroyed', () => {
let firstTest = jasmine.createSpy('test1');
const firstTest = jasmine.createSpy('test1');
breakpointManager.observe('test1').subscribe(undefined, undefined, firstTest);
let secondTest = jasmine.createSpy('test2');
const secondTest = jasmine.createSpy('test2');
breakpointManager.observe('test2').subscribe(undefined, undefined, secondTest);

expect(firstTest).not.toHaveBeenCalled();
Expand All @@ -82,8 +82,8 @@ describe('BreakpointObserver', () => {
});

it('emits an event on the observable when values change', () => {
let query = '(width: 999px)';
let queryMatchState: boolean = false;
const query = '(width: 999px)';
let queryMatchState = false;
breakpointManager.observe(query).subscribe((state: BreakpointState) => {
queryMatchState = state.matches;
});
Expand All @@ -93,14 +93,31 @@ describe('BreakpointObserver', () => {
expect(queryMatchState).toBeFalsy();
});

it('emits an event on the observable with the matching state of all queries provided', () => {
const queryOne = '(width: 999px)';
const queryTwo = '(width: 700px)';
let state: BreakpointState = {matches: false, breakpoints: {}};
breakpointManager.observe([queryOne, queryTwo]).subscribe((breakpoint: BreakpointState) => {
state = breakpoint;
});

mediaMatcher.setMatchesQuery(queryOne, false);
mediaMatcher.setMatchesQuery(queryTwo, false);
expect(state.breakpoints).toEqual({[queryOne]: false, [queryTwo]: false});

mediaMatcher.setMatchesQuery(queryOne, true);
mediaMatcher.setMatchesQuery(queryTwo, false);
expect(state.breakpoints).toEqual({[queryOne]: true, [queryTwo]: false});
});

it('emits a true matches state when the query is matched', () => {
let query = '(width: 999px)';
const query = '(width: 999px)';
mediaMatcher.setMatchesQuery(query, true);
expect(breakpointManager.isMatched(query)).toBeTruthy();
});

it('emits a false matches state when the query is not matched', () => {
let query = '(width: 999px)';
const query = '(width: 999px)';
mediaMatcher.setMatchesQuery(query, false);
expect(breakpointManager.isMatched(query)).toBeTruthy();
});
Expand Down Expand Up @@ -130,7 +147,7 @@ export class FakeMediaQueryList implements MediaQueryList {
@Injectable()
export class FakeMediaMatcher {
/** A map of match media queries. */
private queries: Map<string, FakeMediaQueryList> = new Map();
private queries = new Map<string, FakeMediaQueryList>();

/** The number of distinct queries created in the media matcher during a test. */
get queryCount(): number {
Expand All @@ -139,7 +156,7 @@ export class FakeMediaMatcher {

/** Fakes the match media response to be controlled in tests. */
matchMedia(query: string): FakeMediaQueryList {
let mql = new FakeMediaQueryList(true, query);
const mql = new FakeMediaQueryList(true, query);
this.queries.set(query, mql);
return mql;
}
Expand Down
37 changes: 29 additions & 8 deletions src/cdk/layout/breakpoints-observer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,35 @@ import {coerceArray} from '@angular/cdk/coercion';
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: string]: 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<BreakpointState>;
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: Map<string, Query> = new Map();
private _queries = new Map<string, Query>();
/** A subject for all other observables to takeUntil based on. */
private _destroySubject: Subject<{}> = new Subject();
private _destroySubject = new Subject<void>();

constructor(private mediaMatcher: MediaMatcher, private zone: NgZone) {}

Expand Down Expand Up @@ -59,10 +74,16 @@ export class BreakpointObserver implements OnDestroy {
const queries = splitQueries(coerceArray(value));
const observables = queries.map(query => this._registerQuery(query).observable);

return combineLatest(observables).pipe(map((breakpointStates: BreakpointState[]) => {
return {
matches: breakpointStates.some(state => state && state.matches)
return combineLatest(observables).pipe(map((breakpointStates: InternalBreakpointState[]) => {
const response: BreakpointState = {
matches: false,
breakpoints: {},
};
breakpointStates.forEach((state: InternalBreakpointState) => {
response.matches = response.matches || state.matches;
response.breakpoints[state.query] = state.matches;
});
return response;
}));
}

Expand Down Expand Up @@ -90,11 +111,11 @@ export class BreakpointObserver implements OnDestroy {
.pipe(
takeUntil(this._destroySubject),
startWith(mql),
map((nextMql: MediaQueryList) => ({matches: nextMql.matches}))
map((nextMql: MediaQueryList) => ({query, matches: nextMql.matches}))
);

// Add the MediaQueryList to the set of queries.
const output = {observable: queryObservable, mql: mql};
const output = {observable: queryObservable, mql};
this._queries.set(query, output);
return output;
}
Expand Down

0 comments on commit 5a560b2

Please sign in to comment.