Skip to content

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

Merged
merged 1 commit into from
Sep 29, 2017

Conversation

josephperrott
Copy link
Member

#45

@josephperrott josephperrott requested a review from jelbourn August 31, 2017 21:01
@googlebot googlebot added the cla: yes PR author has agreed to Google's Contributor License Agreement label Aug 31, 2017
@josephperrott josephperrott force-pushed the mediaquery branch 8 times, most recently from 9029b8f to 21995fe Compare September 1, 2017 20:21
@fxck
Copy link
Contributor

fxck commented Sep 2, 2017

Doesn't flex-layout have something like this?

@jelbourn
Copy link
Member

jelbourn commented Sep 5, 2017

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
Copy link
Member

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) => {
Copy link
Member

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', () => {
Copy link
Member

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 {
Copy link
Member

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.
Copy link
Member

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

/**
* Global registry for all dynamically-created, injected style tags.
*/
let styleElementForWebkitCompatibility: {[key: string]: HTMLStyleElement} = {};
Copy link
Member

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


constructor(private platform: Platform) {
this._matchMedia = this.platform.isBrowser ?
(<any>window).matchMedia.bind((<any>window)) :
Copy link
Member

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?

Copy link
Member Author

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

Copy link
Member

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?


constructor(private platform: Platform) {
this._matchMedia = this.platform.isBrowser ?
(<any>window).matchMedia.bind((<any>window)) :
Copy link
Member

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

constructor(private platform: Platform) {
this._matchMedia = this.platform.isBrowser ?
(<any>window).matchMedia.bind((<any>window)) :
(query: string) => <MediaQueryList>{
Copy link
Member

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... */

*/
function createEmptyStyleRule(query: string) {
if (!styleElementForWebkitCompatibility[query]) {
try {
Copy link
Member

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

Copy link
Member Author

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.

Copy link
Member

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?

@josephperrott josephperrott force-pushed the mediaquery branch 2 times, most recently from eda3c6c to 29ceb71 Compare September 6, 2017 21:01
/** Whether the query currently is matched. */
isMatched(value: string | string[]): boolean {
let queries = coerceArray(value);
return queries.some((mediaQuery: string) => {
Copy link
Member

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>{
Copy link
Member

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.
*/
Copy link
Member

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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe _teardownSubject or _destroySubject?

/**
* matchMedia is bound to the window scope intentionally as it is an illegal invocation to
* call it from a different scope.
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use //-style comments

@@ -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'},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing / -> /screen-type.

function createEmptyStyleRule(query: string) {
if (!styleElementForWebkitCompatibility.has(query)) {
try {
let style: HTMLStyleElement = document.createElement('style');
Copy link
Contributor

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(...).

@josephperrott
Copy link
Member Author

@jelbourn

PR is ready for another look.

Copy link
Member

@jelbourn jelbourn left a 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) {
Copy link
Member

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}; })
Copy link
Member

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.

Copy link
Member Author

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.

*/
const styleElementForWebkitCompatibility: Map<string, HTMLStyleElement> = new Map();

@Injectable()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add JsDoc description

@jelbourn jelbourn added pr: lgtm action: merge The PR is ready for merge by the caretaker and removed action: merge The PR is ready for merge by the caretaker labels Sep 14, 2017
@josephperrott josephperrott force-pushed the mediaquery branch 3 times, most recently from 58c34c6 to d375fc6 Compare September 14, 2017 21:08
@josephperrott josephperrott added the action: merge The PR is ready for merge by the caretaker label Sep 14, 2017
@jelbourn
Copy link
Member

@josephperrott can you rebase? I want to get this in the next sync

@jelbourn jelbourn added the P1 Impacts a large percentage of users; if a workaround exists it is partial or overly painful label Sep 26, 2017
@josephperrott
Copy link
Member Author

@jelbourn rebased.

@jelbourn
Copy link
Member

@josephperrott can you rebase one more time?

@josephperrott
Copy link
Member Author

@jelbourn rebased

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 7, 2019
@josephperrott josephperrott deleted the mediaquery branch March 20, 2020 22:15
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker cla: yes PR author has agreed to Google's Contributor License Agreement P1 Impacts a large percentage of users; if a workaround exists it is partial or overly painful
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants