-
Notifications
You must be signed in to change notification settings - Fork 6.8k
feat(breakpoints): Create breakpoint management system #6772
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
9029b8f
to
21995fe
Compare
Doesn't flex-layout have something like this? |
Flex-layout is going to change to consume what's in the cdk |
@@ -0,0 +1,26 @@ | |||
### BreakpointsModule | |||
|
|||
When including the CDK's `BreakpointsModule`, components can inject `BreakpointsManager` to request |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LayoutModule
, BreakpointObserver
mediaMatcher = mm; | ||
})); | ||
|
||
afterEach(inject([MediaMatcher], (_mediaMatcher: FakeMediaMatcher) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think you need to inject
here vs. using the mediaMatcher
you already have
import {async, TestBed, inject} from '@angular/core/testing'; | ||
import {Injectable} from '@angular/core'; | ||
|
||
describe('BreakpointObserver', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
None of these tests cover the case when mediaMatcher doesn't match; we could add a method to the fake like setNoMatch(...)
|
||
export type BreakpointQuery = string | string[]; | ||
|
||
export interface BreakpointState { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add JsDoc description for interfaces / classes
|
||
@Injectable() | ||
export class BreakpointObserver implements OnDestroy { | ||
// A map of all media queries currently being listened for. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use JsDoc-style (/** */
) comments for member descriptions
src/cdk/layout/media-matcher.ts
Outdated
/** | ||
* Global registry for all dynamically-created, injected style tags. | ||
*/ | ||
let styleElementForWebkitCompatibility: {[key: string]: HTMLStyleElement} = {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not make this a Map<string, HTMLStyleElement>
?
Also const
src/cdk/layout/media-matcher.ts
Outdated
|
||
constructor(private platform: Platform) { | ||
this._matchMedia = this.platform.isBrowser ? | ||
(<any>window).matchMedia.bind((<any>window)) : |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this need bind
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are unable to bind the window.matchMedia
method as a property of our custom object as it results in an illegal invocation
.
So we instead bind
it to window so that its execution scope is always as expected
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a comment mentioning this?
src/cdk/layout/media-matcher.ts
Outdated
|
||
constructor(private platform: Platform) { | ||
this._matchMedia = this.platform.isBrowser ? | ||
(<any>window).matchMedia.bind((<any>window)) : |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this need the <any>
coercion? The typings look like they have matchMedia
present
src/cdk/layout/media-matcher.ts
Outdated
constructor(private platform: Platform) { | ||
this._matchMedia = this.platform.isBrowser ? | ||
(<any>window).matchMedia.bind((<any>window)) : | ||
(query: string) => <MediaQueryList>{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extract this into a const
outside the class like noopMatchMedia
? That way it can have it's own description like
/** No-op matchMedia replacement for non-browser platforms that... */
src/cdk/layout/media-matcher.ts
Outdated
*/ | ||
function createEmptyStyleRule(query: string) { | ||
if (!styleElementForWebkitCompatibility[query]) { | ||
try { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the try-catch
? Especially when the catch
still logs the error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is actually a hold over from @ThomasBurleson 's original implementation. My understanding is the try-catch
is intended to allow the service to fail gracefully and not cause an entire halt.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My stance is that any try-catch
need a comment explaining which error it is catching and why it's something that has to be caught instead of prevented in the first place
@ThomasBurleson do you remember if there was a specific reason for using try
around creating a style element?
eda3c6c
to
29ceb71
Compare
/** Whether the query currently is matched. */ | ||
isMatched(value: string | string[]): boolean { | ||
let queries = coerceArray(value); | ||
return queries.some((mediaQuery: string) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The : string
shouldn't be necessary since it is inferred from the array. It's also acceptable to use a single-character variable for concise lambdas like this:
return queries.some(q => this._registerQuery(q).mql.matches);
let observables = queries.map(query => this._registerQuery(query).observable); | ||
|
||
return combineLatest(observables, (a: BreakpointState, b: BreakpointState) => { | ||
return <BreakpointState>{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still has <BreakpointState>
* 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. | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use //
-style comments for explanations / details (with /** */
for class/member/variable descriptions)
/** 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 _activeSubject: Subject<{}> = new Subject(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe _teardownSubject
or _destroySubject
?
src/cdk/layout/media-matcher.ts
Outdated
/** | ||
* matchMedia is bound to the window scope intentionally as it is an illegal invocation to | ||
* call it from a different scope. | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use //
-style comments
29ceb71
to
3d4076d
Compare
src/demo-app/demo-app/demo-app.ts
Outdated
@@ -67,6 +67,7 @@ export class DemoApp { | |||
{name: 'Progress Spinner', route: '/progress-spinner'}, | |||
{name: 'Radio', route: '/radio'}, | |||
{name: 'Ripple', route: '/ripple'}, | |||
{name: 'Screen Type', route: 'screen-type'}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing /
-> /screen-type
.
src/cdk/layout/media-matcher.ts
Outdated
function createEmptyStyleRule(query: string) { | ||
if (!styleElementForWebkitCompatibility.has(query)) { | ||
try { | ||
let style: HTMLStyleElement = document.createElement('style'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
HTMLStyleElement
is already inferred from .createElement(...)
.
3d4076d
to
c82518c
Compare
PR is ready for another look. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, a few last comments
Add the "merge-ready" label when ready
export class MyWidget { | ||
isHandset: Observable<BreakpointState>; | ||
|
||
constructor(bm: BreakpointManager) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still has BreakpointManager
here
})) | ||
.call(takeUntil, this._destroySubject) | ||
.call(startWith, mql) | ||
.call(map, (nextMql: MediaQueryList) => { return {query: query, matches: nextMql.matches}; }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't need the braces and the return
, just parens around the object literal
.call(map, (nextMql: MediaQueryList) => ({query: query, matches: nextMql.matches}))
Also, is the explicit MediaQueryList
type necessary here? I think it should be inferred.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to declare the MediaQueryList
type, because the fromEventPattern
observable emits an any
type unless you use the selector
method to transform what is returned, but we would just have to define the type in this method for typescript to know its type. So I just went with declaring the type in the map
parameter.
src/cdk/layout/media-matcher.ts
Outdated
*/ | ||
const styleElementForWebkitCompatibility: Map<string, HTMLStyleElement> = new Map(); | ||
|
||
@Injectable() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add JsDoc description
58c34c6
to
d375fc6
Compare
cd71113
to
7cc37a3
Compare
@josephperrott can you rebase? I want to get this in the next sync |
7cc37a3
to
490c116
Compare
@jelbourn rebased. |
@josephperrott can you rebase one more time? |
490c116
to
7c0b0df
Compare
@jelbourn rebased |
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
#45