-
Notifications
You must be signed in to change notification settings - Fork 46
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #103 from ngParty/async-pipe
Async pipe Closes #98
- Loading branch information
Showing
13 changed files
with
348 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
//import {} from './src/common/services'; | ||
|
||
export * from './src/common/directives'; | ||
export * from './src/common/pipes'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
84 changes: 84 additions & 0 deletions
84
playground/app/components/async-example/async-example.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import { Observable } from 'rxjs/Observable' | ||
import { Subscriber } from 'rxjs/Subscriber' | ||
import 'rxjs/add/observable/interval'; | ||
import 'rxjs/add/operator/take'; | ||
import 'rxjs/add/operator/scan'; | ||
import { Component, Inject } from 'ng-metadata/core'; | ||
|
||
@Component({ | ||
selector: 'async-task', | ||
template: ( | ||
`Clock: {{ $ctrl.clock$ | async:this | date:'M/d/yy h:mm:ss a' }} <br> | ||
Timer: {{ $ctrl.timer$ | async:this }} <br> | ||
Async Stream via scan: | ||
<ul><li ng-repeat="u in $ctrl.stream$ | async:this">{{ u }}</li></ul>` | ||
) | ||
}) | ||
class AsyncTaskComponent { | ||
|
||
clock$ = Observable.create((observer: Subscriber<number>) => { | ||
this.$interval(() => observer.next(new Date().getTime()), 500); | ||
}); | ||
timer$ = Observable.interval(1000).take(50); | ||
stream$ = Observable.interval(1500).take(10).scan((acc, item) => [...acc, item], []); | ||
|
||
constructor(@Inject('$interval') private $interval: ng.IIntervalService){} | ||
|
||
} | ||
|
||
@Component({ | ||
selector: 'async-example', | ||
directives: [AsyncTaskComponent], | ||
template: ( | ||
`<button ng-click="$ctrl.renderTimers=!$ctrl.renderTimers">Show Timers</button> | ||
<async-task ng-if="$ctrl.renderTimers"></async-task> | ||
<br> | ||
<div> | ||
<p>Wait for it... {{ $ctrl.greeting | async }}</p> | ||
<button ng-click="$ctrl.clicked()">{{ $ctrl.arrived ? 'Reset' : 'Resolve' }}</button> | ||
</div> | ||
<div> | ||
<h4>Rx repos:</h4> | ||
<blockquote ng-hide="$ctrl.repos | async">Loading...</blockquote> | ||
<ul> | ||
<li ng-repeat="repo in $ctrl.repos | async"> | ||
<a href="{{ repo.html_url }}" target="_blank"> | ||
{{ repo.name }} | ||
</a> | ||
</li> | ||
</ul> | ||
<pre style="overflow:auto;max-height:250px;">{{ $ctrl.repos | async | json }}</pre> | ||
</div> | ||
` | ||
) | ||
}) | ||
export class AsyncExampleComponent { | ||
|
||
greeting: ng.IPromise<string> = null; | ||
arrived: boolean = false; | ||
|
||
repos = this.$http.get('https://api.github.com/orgs/Reactive-Extensions/repos'); | ||
|
||
private resolve: Function = null; | ||
|
||
constructor( | ||
@Inject('$q') private $q:ng.IQService, | ||
@Inject('$http') private $http:ng.IHttpService | ||
) { | ||
this.reset(); | ||
} | ||
|
||
reset() { | ||
this.arrived = false; | ||
this.greeting = this.$q((resolve, reject) => { this.resolve = resolve; }); | ||
} | ||
|
||
clicked() { | ||
if (this.arrived) { | ||
this.reset(); | ||
} else { | ||
this.resolve('hi there!'); | ||
this.arrived = true; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,12 @@ | ||
//main entry point | ||
import { bootstrap } from 'ng-metadata/platform'; | ||
import { Title } from 'ng-metadata/platform'; | ||
import {enableProdMode} from 'ng-metadata/core'; | ||
import { AsyncPipe } from 'ng-metadata/common'; | ||
import { enableProdMode } from 'ng-metadata/core'; | ||
|
||
import { AppComponent } from './app.component'; | ||
import { AppModule, configureProviders } from './index'; | ||
|
||
// enableProdMode(); | ||
|
||
bootstrap( AppComponent, [ Title, AppModule, configureProviders ] ); | ||
bootstrap( AppComponent, [ Title, AsyncPipe, AppModule, configureProviders ] ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
/** | ||
* @module | ||
* @description | ||
* This module provides a set of common Pipes. | ||
*/ | ||
|
||
export { AsyncPipe } from './pipes/async_pipe'; | ||
export { COMMON_PIPES } from './pipes/common_pipes'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import { Subscription } from 'rxjs/Subscription'; | ||
import { Observable } from 'rxjs/Observable'; | ||
|
||
import { Pipe } from '../../core/pipes/decorators'; | ||
import { PipeTransform } from '../../core/pipes/pipe_interfaces'; | ||
|
||
import { isObservable, isScope, isSubscription, isPromiseOrObservable } from '../../facade/lang'; | ||
|
||
type StoredSubscription = ng.IPromise<any>|ng.IHttpPromise<any>|Subscription; | ||
|
||
/** | ||
* Based on @cvuorinen angular1-async-filter implementation | ||
* @link https://github.com/cvuorinen/angular1-async-filter | ||
* | ||
* The `async` pipe subscribes to an `Observable` or `Promise` and returns the latest value it has emitted. | ||
* When a new value is emitted, the `async` pipe marks the component to be checked for changes. ( runs $scope.$digest() ) | ||
* When the component gets destroyed, the `async` pipe unsubscribes automatically to avoid potential memory leaks. | ||
* | ||
* ## Usage | ||
* | ||
* object | async // for non observable | ||
* object | async:this // for observable | ||
* | ||
* where: | ||
* - `object` is one of type `Observable`, `Promise`, 'ng.IPromise' or 'ng.IHttpPromise' | ||
* - `this` is pipe parameter ( in angular 1 reference to local $Scope ( we need for Observable disposal ) | ||
* | ||
* If you are using async with observables nad you don't provide scope we will throw Error to let you know that you forgot `this`, #perfmatters baby! | ||
* | ||
* ## Examples | ||
* | ||
* This example binds a `Promise` to the view. Clicking the `Resolve` button resolves the promise. | ||
* | ||
* {@example core/pipes/ts/async_pipe/async_pipe_example.ts region='AsyncPipePromise'} | ||
* | ||
* It's also possible to use `async` with Observables. The example below binds the `time` Observable | ||
* to the view. Every 500ms, the `time` Observable updates the view with the current time. | ||
* | ||
* {@example core/pipes/ts/async_pipe/async_pipe_example.ts region='AsyncPipeObservable'} | ||
*/ | ||
@Pipe( { name: 'async'/*, pure: false*/ } ) | ||
export class AsyncPipe implements PipeTransform { | ||
|
||
// Need a way to tell the input objects apart from each other (so we only subscribe to them once) | ||
private static nextObjectID: number = 0; | ||
private static values: {[key: string]: any} = {}; | ||
private static subscriptions: {[key: string]: StoredSubscription} = {}; | ||
private static TRACK_PROP_NAME = '__asyncFilterObjectID__'; | ||
|
||
private static _objectId( obj: any ): any { | ||
if ( !obj.hasOwnProperty( AsyncPipe.TRACK_PROP_NAME ) ) { | ||
obj[ AsyncPipe.TRACK_PROP_NAME ] = ++AsyncPipe.nextObjectID; | ||
} | ||
return obj[ AsyncPipe.TRACK_PROP_NAME ]; | ||
} | ||
|
||
private static _getSubscriptionStrategy( input: any ): ( value ) => StoredSubscription { | ||
return input.subscribe && input.subscribe.bind( input ) | ||
|| input.success && input.success.bind( input ) // To make it work with HttpPromise | ||
|| input.then.bind( input ); // To make it work with Promise | ||
} | ||
|
||
private static _markForCheck( scope: ng.IScope ) { | ||
if ( isScope( scope ) ) { | ||
// #perfmatters | ||
// wait till event loop is free and run just local digest so we don't get in conflict with other local $digest | ||
setTimeout( ()=>scope.$digest() ); | ||
// we can't run local scope.$digest, because if we have multiple async pipes on the same scope 'infdig' error would occur :( | ||
// scope.$applyAsync(); // Automatic safe apply, if scope provided | ||
} | ||
} | ||
|
||
private static _dispose( inputId: number ): void { | ||
if ( isSubscription( AsyncPipe.subscriptions[ inputId ] ) ) { | ||
(AsyncPipe.subscriptions[ inputId ] as Subscription).unsubscribe(); | ||
} | ||
delete AsyncPipe.subscriptions[ inputId ]; | ||
delete AsyncPipe.values[ inputId ]; | ||
} | ||
|
||
transform( input: Observable<any>|ng.IPromise<any>|ng.IHttpPromise<any>, scope?: ng.IScope ): any { | ||
|
||
if ( !isPromiseOrObservable( input ) ) { | ||
return input | ||
} | ||
|
||
if ( isObservable( input ) && !isScope( scope ) ) { | ||
throw new Error( 'AsyncPipe: you have to specify "this" as parameter so we can unsubscribe on scope.$destroy!' ); | ||
} | ||
|
||
const inputId = AsyncPipe._objectId( input ); | ||
|
||
// return cached immediately | ||
if ( inputId in AsyncPipe.subscriptions ) { | ||
return AsyncPipe.values[ inputId ] || undefined; | ||
} | ||
|
||
const subscriptionStrategy = AsyncPipe._getSubscriptionStrategy( input ); | ||
AsyncPipe.subscriptions[ inputId ] = subscriptionStrategy( _setSubscriptionValue ); | ||
|
||
if ( isScope( scope ) ) { | ||
// Clean up subscription and its last value when the scope is destroyed. | ||
scope.$on( '$destroy', () => { AsyncPipe._dispose( inputId ) } ); | ||
} | ||
|
||
function _setSubscriptionValue( value: any ): void { | ||
AsyncPipe.values[ inputId ] = value; | ||
// this is needed only for Observables | ||
AsyncPipe._markForCheck( scope ); | ||
} | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
/** | ||
* @module | ||
* @description | ||
* This module provides a set of common Pipes. | ||
*/ | ||
import { AsyncPipe } from './async_pipe'; | ||
|
||
/** | ||
* A collection of Angular core pipes that are likely to be used in each and every | ||
* application. | ||
* | ||
* This collection can be used to quickly enumerate all the built-in pipes in the `pipes` | ||
* property of the `@Component` decorator. | ||
* | ||
* @experimental Contains i18n pipes which are experimental | ||
*/ | ||
export const COMMON_PIPES = [ | ||
AsyncPipe | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.