Skip to content

Commit 0df820c

Browse files
committed
feat(breakpoint-observer): Emit matching state of each query provided (#12506)
1 parent 539abf4 commit 0df820c

File tree

2 files changed

+56
-18
lines changed

2 files changed

+56
-18
lines changed

src/cdk/layout/breakpoints-observer.spec.ts

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ describe('BreakpointObserver', () => {
3434
});
3535

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

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

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

6969
it('completes all events when the breakpoint manager is destroyed', () => {
70-
let firstTest = jasmine.createSpy('test1');
70+
const firstTest = jasmine.createSpy('test1');
7171
breakpointManager.observe('test1').subscribe(undefined, undefined, firstTest);
72-
let secondTest = jasmine.createSpy('test2');
72+
const secondTest = jasmine.createSpy('test2');
7373
breakpointManager.observe('test2').subscribe(undefined, undefined, secondTest);
7474

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

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

96+
it('emits an event on the observable with the matching state of all queries provided', () => {
97+
const queryOne = '(width: 999px)';
98+
const queryTwo = '(width: 700px)';
99+
let state: BreakpointState = {matches: false, breakpoints: {}};
100+
breakpointManager.observe([queryOne, queryTwo]).subscribe((breakpoint: BreakpointState) => {
101+
state = breakpoint;
102+
});
103+
104+
mediaMatcher.setMatchesQuery(queryOne, false);
105+
mediaMatcher.setMatchesQuery(queryTwo, false);
106+
expect(state.breakpoints).toEqual({[queryOne]: false, [queryTwo]: false});
107+
108+
mediaMatcher.setMatchesQuery(queryOne, true);
109+
mediaMatcher.setMatchesQuery(queryTwo, false);
110+
expect(state.breakpoints).toEqual({[queryOne]: true, [queryTwo]: false});
111+
});
112+
96113
it('emits a true matches state when the query is matched', () => {
97-
let query = '(width: 999px)';
114+
const query = '(width: 999px)';
98115
mediaMatcher.setMatchesQuery(query, true);
99116
expect(breakpointManager.isMatched(query)).toBeTruthy();
100117
});
101118

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

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

140157
/** Fakes the match media response to be controlled in tests. */
141158
matchMedia(query: string): FakeMediaQueryList {
142-
let mql = new FakeMediaQueryList(true, query);
159+
const mql = new FakeMediaQueryList(true, query);
143160
this.queries.set(query, mql);
144161
return mql;
145162
}

src/cdk/layout/breakpoints-observer.ts

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,35 @@ import {coerceArray} from '@angular/cdk/coercion';
1616
export interface BreakpointState {
1717
/** Whether the breakpoint is currently matching. */
1818
matches: boolean;
19+
/**
20+
* A key boolean pair for each query provided to the observe method,
21+
* with its current matched state.
22+
*/
23+
breakpoints: {
24+
[key: string]: boolean;
25+
};
26+
}
27+
28+
/** The current state of a layout breakpoint. */
29+
interface InternalBreakpointState {
30+
/** Whether the breakpoint is currently matching. */
31+
matches: boolean;
32+
/** The media query being to be matched */
33+
query: string;
1934
}
2035

2136
interface Query {
22-
observable: Observable<BreakpointState>;
37+
observable: Observable<InternalBreakpointState>;
2338
mql: MediaQueryList;
2439
}
2540

2641
/** Utility for checking the matching state of @media queries. */
2742
@Injectable({providedIn: 'root'})
2843
export class BreakpointObserver implements OnDestroy {
2944
/** A map of all media queries currently being listened for. */
30-
private _queries: Map<string, Query> = new Map();
45+
private _queries = new Map<string, Query>();
3146
/** A subject for all other observables to takeUntil based on. */
32-
private _destroySubject: Subject<{}> = new Subject();
47+
private _destroySubject = new Subject<void>();
3348

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

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

62-
return combineLatest(observables).pipe(map((breakpointStates: BreakpointState[]) => {
63-
return {
64-
matches: breakpointStates.some(state => state && state.matches)
77+
return combineLatest(observables).pipe(map((breakpointStates: InternalBreakpointState[]) => {
78+
const response: BreakpointState = {
79+
matches: false,
80+
breakpoints: {},
6581
};
82+
breakpointStates.forEach((state: InternalBreakpointState) => {
83+
response.matches = response.matches || state.matches;
84+
response.breakpoints[state.query] = state.matches;
85+
});
86+
return response;
6687
}));
6788
}
6889

@@ -90,11 +111,11 @@ export class BreakpointObserver implements OnDestroy {
90111
.pipe(
91112
takeUntil(this._destroySubject),
92113
startWith(mql),
93-
map((nextMql: MediaQueryList) => ({matches: nextMql.matches}))
114+
map((nextMql: MediaQueryList) => ({query, matches: nextMql.matches}))
94115
);
95116

96117
// Add the MediaQueryList to the set of queries.
97-
const output = {observable: queryObservable, mql: mql};
118+
const output = {observable: queryObservable, mql};
98119
this._queries.set(query, output);
99120
return output;
100121
}

0 commit comments

Comments
 (0)