Skip to content

Commit

Permalink
fix(matchMediaObservable): expose observable for rxjs operators (#133)
Browse files Browse the repository at this point in the history
* fix(matchMediaObservable): expose observable for rxjs operators

* rename token to 'ObservableMediaService'
* rename MatchMediaObservableProvider to ObservableMediaServiceProvider
* add method `.asObservable()` to MediaService

BREAKING CHANGE:

* use opaque token `ObservableMediateService` to inject instance of `MediaService`
* use `MediaService::asObservable()` to get instance of observable

```js
// RxJS
import 'rxjs/add/operator/map';

@component({ ... })
export class MyComponent {
  constructor( @Inject(ObservableMediaService) media) {
    media.asObservable()
      .map( (change:MediaChange) => change.mqAlias == 'md' )
      .subscribe((change:MediaChange) => {
        let state = change ? `'${change.mqAlias}' = (${change.mediaQuery})` : ""
        console.log( state );
      });
  }
}
```

fixes #125.

* update(demo): fix imports for observable-media-service
  • Loading branch information
ThomasBurleson authored and andrewseguin committed Jan 26, 2017
1 parent 3023f60 commit 6e46561
Show file tree
Hide file tree
Showing 14 changed files with 244 additions and 145 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
<a name="2.0.0-beta.3"></a>
# [2.0.0-beta.3](https://github.com/angular/flex-layout/compare/v2.0.0-beta.2...v2.0.0-beta.3) (2017-01-17)


<a name="2.0.0-beta.2"></a>
# [2.0.0-beta.2](https://github.com/angular/flex-layout/compare/v2.0.0-beta.1...v2.0.0-beta.2) (2017-01-13)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {Subscription} from "rxjs/Subscription";
import 'rxjs/add/operator/filter';

import {MediaChange} from "../../../lib/media-query/media-change";
import {MatchMediaObservable} from "../../../lib/media-query/match-media";
import {ObservableMediaService} from "../../../lib/media-query/observable-media-service";

@Component({
selector: 'demo-responsive-flex-directive',
Expand Down Expand Up @@ -32,7 +32,7 @@ export class DemoResponsiveFlexDirectives implements OnInit, OnDestroy {
private _watcher : Subscription;
public activeMediaQuery = "";

constructor(@Inject(MatchMediaObservable) private _media$) { }
constructor(@Inject(ObservableMediaService) private _media$) { }

ngOnInit() {
this._watcher = this.watchMQChanges();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {Subscription} from "rxjs/Subscription";
import 'rxjs/add/operator/filter';

import {MediaChange} from "../../../lib/media-query/media-change";
import {MatchMediaObservable} from "../../../lib/media-query/match-media";
import {ObservableMediaService} from "../../../lib/media-query/observable-media-service";

@Component({
selector: 'demo-responsive-flex-order',
Expand Down Expand Up @@ -44,7 +44,7 @@ export class DemoResponsiveFlexOrder implements OnInit, OnDestroy {
public activeMediaQuery = "";
private _watcher : Subscription;

constructor(@Inject(MatchMediaObservable) private _media$) { }
constructor(@Inject(ObservableMediaService) private _media$) { }

ngOnInit() {
this._watcher = this.watchMQChanges();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {Subscription} from "rxjs/Subscription";
import 'rxjs/add/operator/filter';

import {MediaChange} from "../../../lib/media-query/media-change";
import {MatchMediaObservable} from "../../../lib/media-query/match-media";
import {ObservableMediaService} from "../../../lib/media-query/observable-media-service";

@Component({
selector: 'demo-responsive-row-column',
Expand Down Expand Up @@ -64,7 +64,7 @@ export class DemoResponsiveRows implements OnDestroy {

isVisible = true;

constructor(@Inject(MatchMediaObservable) private _media$) {
constructor(@Inject(ObservableMediaService) private _media$) {
this._watcher = this._media$
.subscribe((e:MediaChange) => {
this._activeMQC = e;
Expand Down
5 changes: 3 additions & 2 deletions src/lib/flexbox/api/hide.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import {CommonModule} from '@angular/common';
import {ComponentFixture, TestBed} from '@angular/core/testing';

import {MockMatchMedia} from '../../media-query/mock/mock-match-media';
import {MatchMedia, MatchMediaObservable} from '../../media-query/match-media';
import {MatchMedia} from '../../media-query/match-media';
import {ObservableMediaService} from '../../media-query/observable-media-service';
import {BreakPointsProvider} from '../../media-query/providers/break-points-provider';
import {BreakPointRegistry} from '../../media-query/breakpoints/break-point-registry';

Expand Down Expand Up @@ -258,7 +259,7 @@ export class TestHideComponent implements OnInit {
isHidden = true;
menuHidden = true;

constructor(@Inject(MatchMediaObservable) private media) {
constructor(@Inject(ObservableMediaService) private media) {
}

toggleMenu() {
Expand Down
5 changes: 3 additions & 2 deletions src/lib/flexbox/api/show.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import {Component, OnInit, Inject} from '@angular/core';
import {CommonModule} from '@angular/common';
import {ComponentFixture, TestBed} from '@angular/core/testing';

import {MatchMedia} from '../../media-query/match-media';
import {MockMatchMedia} from '../../media-query/mock/mock-match-media';
import {MatchMedia, MatchMediaObservable} from '../../media-query/match-media';
import {ObservableMediaService} from '../../media-query/observable-media-service';
import {BreakPointsProvider} from '../../media-query/providers/break-points-provider';
import {BreakPointRegistry} from '../../media-query/breakpoints/break-point-registry';
import {FlexLayoutModule} from '../_module';
Expand Down Expand Up @@ -214,7 +215,7 @@ export class TestShowComponent implements OnInit {
isHidden = false;
menuOpen: boolean = true;

constructor(@Inject(MatchMediaObservable) private media) {
constructor(@Inject(ObservableMediaService) private media) {
}

toggleMenu() {
Expand Down
4 changes: 2 additions & 2 deletions src/lib/media-query/_module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {BreakPointsProvider} from "./providers/break-points-provider";

import {MatchMedia} from './match-media';
import {MediaMonitor} from './media-monitor';
import {MatchMediaObservableProvider} from './providers/match-media-observable-provider';
import {ObservableMediaServiceProvider} from './providers/observable-media-service-provider';

/**
* *****************************************************************
Expand All @@ -27,7 +27,7 @@ import {MatchMediaObservableProvider} from './providers/match-media-observable-p
MediaMonitor, // MediaQuery monitor service observes all known breakpoints
BreakPointRegistry, // Registry of known/used BreakPoint(s)
BreakPointsProvider, // Supports developer overrides of list of known breakpoints
MatchMediaObservableProvider // easy subscription injectable `media$` matchMedia observable
ObservableMediaServiceProvider // easy subscription injectable `media$` matchMedia observable
]
})
export class MediaQueriesModule {
Expand Down
2 changes: 1 addition & 1 deletion src/lib/media-query/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/
export * from './breakpoints/break-point-registry';
export * from './providers/break-points-provider';
export * from './providers/match-media-observable-provider';
export * from './providers/observable-media-service-provider';
export * from './match-media';
export * from './media-change';
export * from './media-monitor';
Expand Down
11 changes: 6 additions & 5 deletions src/lib/media-query/match-media.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ import {BreakPoint} from './breakpoints/break-point';
import {MockMatchMedia} from './mock/mock-match-media';
import {BreakPointRegistry} from './breakpoints/break-point-registry';
import {BreakPointsProvider} from './providers/break-points-provider';
import {MatchMedia, MatchMediaObservable} from './match-media';
import {MatchMediaObservableProvider} from './providers/match-media-observable-provider';
import {MatchMedia} from './match-media';
import {ObservableMediaService} from './observable-media-service';
import {ObservableMediaServiceProvider} from './providers/observable-media-service-provider';

describe('match-media', () => {
let matchMedia: MockMatchMedia;
Expand Down Expand Up @@ -130,14 +131,14 @@ describe('match-media-observable', () => {
BreakPointsProvider, // Supports developer overrides of list of known breakpoints
BreakPointRegistry, // Registry of known/used BreakPoint(s)
{provide: MatchMedia, useClass: MockMatchMedia},
MatchMediaObservableProvider // injectable `media$` matchMedia observable
ObservableMediaServiceProvider // injectable `media$` matchMedia observable
]
});
});

// Single async inject to save references; which are used in all tests below
beforeEach(async(inject(
[MatchMediaObservable, MatchMedia, BreakPointRegistry],
[ObservableMediaService, MatchMedia, BreakPointRegistry],
(_media$_, _matchMedia_, _breakPoints_) => {
matchMedia = _matchMedia_; // inject only to manually activate mediaQuery ranges
breakPoints = _breakPoints_;
Expand Down Expand Up @@ -204,7 +205,7 @@ describe('match-media-observable', () => {
});

/**
* Only the MatchMediaObservable ignores de-activations;
* Only the ObservableMediaService ignores de-activations;
* MediaMonitor and MatchMedia report both activations and de-activations!
*/
it('ignores mediaQuery de-activations', () => {
Expand Down
11 changes: 0 additions & 11 deletions src/lib/media-query/match-media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {OpaqueToken} from '@angular/core';
import {Injectable, NgZone} from '@angular/core';

import {BehaviorSubject} from 'rxjs/BehaviorSubject';
Expand Down Expand Up @@ -35,16 +34,6 @@ export interface MediaQueryList {
removeListener(listener: MediaQueryListListener): void;
}

/**
* Opaque Token unique to the flex-layout library.
* Note: Developers must use this token when building their own custom
* `MatchMediaObservableProvider` provider.
*
* @see ./providers/match-media-observable-provider.ts
*/
// tslint:disable-next-line:variable-name
export const MatchMediaObservable: OpaqueToken = new OpaqueToken('fxObservableMatchMedia');


/**
* MediaMonitor configures listeners to mediaQuery changes and publishes an Observable facade to
Expand Down
157 changes: 157 additions & 0 deletions src/lib/media-query/observable-media-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {OpaqueToken} from '@angular/core'; // tslint:disable-line:no-unused-variable

import {Subscription} from 'rxjs/Subscription';
import {Observable, Subscribable} from "rxjs/Observable";
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/filter';

import {BreakPointRegistry} from './breakpoints/break-point-registry';

import {MediaChange} from './media-change';
import {MatchMedia} from './match-media';
import {mergeAlias} from './../utils/add-alias';
import {BreakPoint} from './breakpoints/break-point';


/**
* Opaque Token unique to the flex-layout library.
* Note: Developers must use this token when building their own custom
* `ObservableMediaServiceProvider` provider.
*
* @see ./providers/match-media-observable-provider.ts
*/
// tslint:disable-next-line:variable-name
export const ObservableMediaService: OpaqueToken = new OpaqueToken('flex-layout-media-service');


/**
* Class internalizes a MatchMedia service and exposes an Subscribable and Observable interface.
* This an Observable with that exposes a feature to subscribe to mediaQuery
* changes and a validator method (`isActive(<alias>)`) to test if a mediaQuery (or alias) is
* currently active.
*
* !! Only mediaChange activations (not de-activations) are announced by the ObservableMediaService
*
* This class uses the BreakPoint Registry to inject alias information into the raw MediaChange
* notification. For custom mediaQuery notifications, alias information will not be injected and
* those fields will be ''.
*
* !! This is not an actual Observable. It is a wrapper of an Observable used to publish additional
* methods like `isActive(<alias>). To access the Observable and use RxJS operators, use
* `.asObservable()` with syntax like media.asObservable().map(....).
*
* @usage
*
* // RxJS
* import 'rxjs/add/operator/map';
*
* @Component({ ... })
* export class AppComponent {
* constructor( @Inject(ObservableMediaService) media) {
* media.asObservable()
* .map( (change:MediaChange) => change.mqAlias == 'md' )
* .subscribe((change:MediaChange) => {
* console.log( change ? `'${change.mqAlias}' = (${change.mediaQuery})` : "" );
* });
* }
* }
*/
export class MediaService implements Subscribable<MediaChange> {
private observable$: Observable<MediaChange>;

constructor(private mediaWatcher: MatchMedia,
private breakpoints: BreakPointRegistry) {
this._registerBreakPoints();
this.observable$ = this._buildObservable();
}

/**
* Test if specified query/alias is active.
*/
isActive(alias): boolean {
let query = this._toMediaQuery(alias);
return this.mediaWatcher.isActive(query);
};

/**
* Proxy to the Observable subscribe method
*/
subscribe(next?: (value: MediaChange) => void,
error?: (error: any) => void,
complete?: () => void): Subscription {
return this.observable$.subscribe(next, error, complete);
};

/**
* Access to observable for use with operators like
* .filter(), .map(), etc.
*/
asObservable(): Observable<MediaChange> {
return this.observable$;
}

// ************************************************
// Internal Methods
// ************************************************

/**
* Register all the mediaQueries registered in the BreakPointRegistry
* This is needed so subscribers can be auto-notified of all standard, registered
* mediaQuery activations
*/
private _registerBreakPoints() {
this.breakpoints.items.forEach((bp: BreakPoint) => {
this.mediaWatcher.registerQuery(bp.mediaQuery);
return bp;
});
}

/**
* Prepare internal observable
* NOTE: the raw MediaChange events [from MatchMedia] do not contain important alias information
* these must be injected into the MediaChange
*/
private _buildObservable() {
return this.mediaWatcher.observe()
.filter((change: MediaChange) => {
// Only pass/announce activations (not de-activations)
return change.matches === true;
})
.map((change: MediaChange) => {
// Inject associated (if any) alias information into the MediaChange event
return mergeAlias(change, this._findByQuery(change.mediaQuery));
});
}

/**
* Breakpoint locator by alias
*/
private _findByAlias(alias) {
return this.breakpoints.findByAlias(alias);
};

/**
* Breakpoint locator by mediaQuery
*/
private _findByQuery(query) {
return this.breakpoints.findByQuery(query);
};

/**
* Find associated breakpoint (if any)
*/
private _toMediaQuery(query) {
let bp: BreakPoint = this._findByAlias(query) || this._findByQuery(query);
return bp ? bp.mediaQuery : query;
};

}
Loading

0 comments on commit 6e46561

Please sign in to comment.