refactor(pipes): use angular lifecycle hooks instead of PipeOnDestroy

BREAKING CHANGE:
Previously, pipes that wanted to be notified when they were destroyed
would implement the PipeOnDestroy interface and name the callback
`onDestroy`. This change removes the PipeOnDestroy interface and
instead uses Angular's lifecycle interface `OnDestroy`, with the
`ngOnDestroy` method.

Before:
```
import {Pipe, PipeOnDestroy} from 'angular2/angular2';
@Pipe({pure: false})
export class MyPipe implements PipeOnDestroy {
  onDestroy() {}
}
```

After:
import {Pipe, OnDestroy} from 'angular2/angular2';
@Pipe({pure: false})
export class MyPipe implements PipeOnDestroy {
  ngOnDestroy() {}
}
This commit is contained in:
Jeff Cross 2015-11-17 10:09:23 -08:00 committed by vsavkin
parent 604c8bbad5
commit fcc7ce225e
13 changed files with 79 additions and 82 deletions

View File

@ -4,7 +4,7 @@ import {
Pipe,
Injectable,
ChangeDetectorRef,
PipeOnDestroy,
OnDestroy,
PipeTransform,
WrappedValue
} from 'angular2/core';
@ -55,7 +55,7 @@ var _observableStrategy = new ObservableStrategy();
*/
@Pipe({name: 'async', pure: false})
@Injectable()
export class AsyncPipe implements PipeTransform, PipeOnDestroy {
export class AsyncPipe implements PipeTransform, OnDestroy {
/** @internal */
_latestValue: Object = null;
/** @internal */
@ -70,7 +70,7 @@ export class AsyncPipe implements PipeTransform, PipeOnDestroy {
public _ref: ChangeDetectorRef;
constructor(_ref: ChangeDetectorRef) { this._ref = _ref; }
onDestroy(): void {
ngOnDestroy(): void {
if (isPresent(this._subscription)) {
this._dispose();
}

View File

@ -15,7 +15,6 @@ export {
WrappedValue,
SimpleChange,
PipeTransform,
PipeOnDestroy,
IterableDiffers,
IterableDiffer,
IterableDifferFactory,

View File

@ -39,7 +39,7 @@ export {DynamicChangeDetector} from './dynamic_change_detector';
export {ChangeDetectorRef} from './change_detector_ref';
export {IterableDiffers, IterableDiffer, IterableDifferFactory} from './differs/iterable_differs';
export {KeyValueDiffers, KeyValueDiffer, KeyValueDifferFactory} from './differs/keyvalue_differs';
export {PipeTransform, PipeOnDestroy} from './pipe_transform';
export {PipeTransform} from './pipe_transform';
export {WrappedValue, SimpleChange} from './change_detection_util';
/**

View File

@ -195,7 +195,7 @@ export class ChangeDetectionUtil {
static callPipeOnDestroy(selectedPipe: SelectedPipe): void {
if (implementsOnDestroy(selectedPipe.pipe)) {
(<any>selectedPipe.pipe).onDestroy();
(<any>selectedPipe.pipe).ngOnDestroy();
}
}

View File

@ -1,5 +1,5 @@
library angular2.core.compiler.pipe_lifecycle_reflector;
import 'package:angular2/src/core/change_detection/pipe_transform.dart';
import 'package:angular2/src/core/linker/interfaces.dart';
bool implementsOnDestroy(Object pipe) => pipe is PipeOnDestroy;
bool implementsOnDestroy(Object pipe) => pipe is OnDestroy;

View File

@ -1,3 +1,3 @@
export function implementsOnDestroy(pipe: any): boolean {
return pipe.constructor.prototype.onDestroy;
return pipe.constructor.prototype.ngOnDestroy;
}

View File

@ -31,55 +31,3 @@
*
*/
export interface PipeTransform { transform(value: any, args: any[]): any; }
/**
* To create a stateful Pipe, you should implement this interface and set the `pure`
* parameter to `false` in the {@link PipeMetadata}.
*
* A stateful pipe may produce different output, given the same input. It is
* likely that a stateful pipe may contain state that should be cleaned up when
* a binding is destroyed. For example, a subscription to a stream of data may need to
* be disposed, or an interval may need to be cleared.
*
* ### Example ([live demo](http://plnkr.co/edit/i8pm5brO4sPaLxBx56MR?p=preview))
*
* In this example, a pipe is created to countdown its input value, updating it every
* 50ms. Because it maintains an internal interval, it automatically clears
* the interval when the binding is destroyed or the countdown completes.
*
* ```
* import {Pipe, PipeTransform} from 'angular2/angular2'
* @Pipe({name: 'countdown', pure: false})
* class CountDown implements PipeTransform, PipeOnDestroy {
* remainingTime:Number;
* interval:SetInterval;
* onDestroy() {
* if (this.interval) {
* clearInterval(this.interval);
* }
* }
* transform(value: any, args: any[] = []) {
* if (!parseInt(value, 10)) return null;
* if (typeof this.remainingTime !== 'number') {
* this.remainingTime = parseInt(value, 10);
* }
* if (!this.interval) {
* this.interval = setInterval(() => {
* this.remainingTime-=50;
* if (this.remainingTime <= 0) {
* this.remainingTime = 0;
* clearInterval(this.interval);
* delete this.interval;
* }
* }, 50);
* }
* return this.remainingTime;
* }
* }
* ```
*
* Invoking `{{ 10000 | countdown }}` would cause the value to be decremented by 50,
* every 50ms, until it reaches 0.
*
*/
export interface PipeOnDestroy { onDestroy(): void; }

View File

@ -226,6 +226,56 @@ export interface DoCheck { ngDoCheck(); }
*
* bootstrap(App).catch(err => console.error(err));
* ```
*
*
* To create a stateful Pipe, you should implement this interface and set the `pure`
* parameter to `false` in the {@link PipeMetadata}.
*
* A stateful pipe may produce different output, given the same input. It is
* likely that a stateful pipe may contain state that should be cleaned up when
* a binding is destroyed. For example, a subscription to a stream of data may need to
* be disposed, or an interval may need to be cleared.
*
* ### Example ([live demo](http://plnkr.co/edit/i8pm5brO4sPaLxBx56MR?p=preview))
*
* In this example, a pipe is created to countdown its input value, updating it every
* 50ms. Because it maintains an internal interval, it automatically clears
* the interval when the binding is destroyed or the countdown completes.
*
* ```
* import {OnDestroy, Pipe, PipeTransform} from 'angular2/angular2'
* @Pipe({name: 'countdown', pure: false})
* class CountDown implements PipeTransform, OnDestroy {
* remainingTime:Number;
* interval:SetInterval;
* ngOnDestroy() {
* if (this.interval) {
* clearInterval(this.interval);
* }
* }
* transform(value: any, args: any[] = []) {
* if (!parseInt(value, 10)) return null;
* if (typeof this.remainingTime !== 'number') {
* this.remainingTime = parseInt(value, 10);
* }
* if (!this.interval) {
* this.interval = setInterval(() => {
* this.remainingTime-=50;
* if (this.remainingTime <= 0) {
* this.remainingTime = 0;
* clearInterval(this.interval);
* delete this.interval;
* }
* }, 50);
* }
* return this.remainingTime;
* }
* }
* ```
*
* Invoking `{{ 10000 | countdown }}` would cause the value to be decremented by 50,
* every 50ms, until it reaches 0.
*
*/
export interface OnDestroy { ngOnDestroy(); }

View File

@ -96,13 +96,13 @@ export function main() {
}));
});
describe("onDestroy", () => {
describe("ngOnDestroy", () => {
it("should do nothing when no subscription",
() => { expect(() => pipe.onDestroy()).not.toThrow(); });
() => { expect(() => pipe.ngOnDestroy()).not.toThrow(); });
it("should dispose of the existing subscription", inject([AsyncTestCompleter], (async) => {
pipe.transform(emitter);
pipe.onDestroy();
pipe.ngOnDestroy();
ObservableWrapper.callEmit(emitter, message);
@ -182,9 +182,9 @@ export function main() {
}, timer)
}));
describe("onDestroy", () => {
describe("ngOnDestroy", () => {
it("should do nothing when no source",
() => { expect(() => pipe.onDestroy()).not.toThrow(); });
() => { expect(() => pipe.ngOnDestroy()).not.toThrow(); });
it("should dispose of the existing source", inject([AsyncTestCompleter], (async) => {
pipe.transform(completer.promise);
@ -194,7 +194,7 @@ export function main() {
TimerWrapper.setTimeout(() => {
expect(pipe.transform(completer.promise)).toEqual(new WrappedValue(message));
pipe.onDestroy();
pipe.ngOnDestroy();
expect(pipe.transform(completer.promise)).toBe(null);
async.done();
}, timer);

View File

@ -10,20 +10,20 @@ import {
afterEach
} from 'angular2/testing_internal';
import {Injector, Inject, provide, Pipe, PipeTransform} from 'angular2/core';
import {Injector, Inject, provide, Pipe, PipeTransform, OnDestroy} from 'angular2/core';
import {ProtoPipes, Pipes} from 'angular2/src/core/pipes/pipes';
import {PipeProvider} from 'angular2/src/core/pipes/pipe_provider';
class PipeA implements PipeTransform {
class PipeA implements PipeTransform, OnDestroy {
transform(a, b) {}
onDestroy() {}
ngOnDestroy() {}
}
class PipeB implements PipeTransform {
class PipeB implements PipeTransform, OnDestroy {
dep;
constructor(@Inject("dep") dep: any) { this.dep = dep; }
transform(a, b) {}
onDestroy() {}
ngOnDestroy() {}
}
export function main() {

View File

@ -35,7 +35,6 @@ import {
DirectiveRecord,
DirectiveIndex,
PipeTransform,
PipeOnDestroy,
ChangeDetectionStrategy,
WrappedValue,
DynamicProtoChangeDetector,
@ -48,6 +47,7 @@ import {
import {SelectedPipe, Pipes} from 'angular2/src/core/change_detection/pipes';
import {JitProtoChangeDetector} from 'angular2/src/core/change_detection/jit_proto_change_detector';
import {OnDestroy} from 'angular2/src/core/linker/interfaces';
import {getDefinition} from './change_detector_config';
import {createObservableModel} from './change_detector_spec_util';
@ -1228,7 +1228,7 @@ export function main() {
expect(cd.hydrated()).toBe(true);
});
it('should destroy all active pipes implementing onDestroy during dehyration', () => {
it('should destroy all active pipes implementing ngOnDestroy during dehyration', () => {
var pipe = new PipeWithOnDestroy();
var registry = new FakePipes('pipe', () => pipe);
var cd = _createChangeDetector('name | pipe', new Person('bob'), registry).changeDetector;
@ -1239,7 +1239,7 @@ export function main() {
expect(pipe.destroyCalled).toBe(true);
});
it('should not call onDestroy all pipes that do not implement onDestroy', () => {
it('should not call ngOnDestroy all pipes that do not implement ngOnDestroy', () => {
var pipe = new CountingPipe();
var registry = new FakePipes('pipe', () => pipe);
var cd = _createChangeDetector('name | pipe', new Person('bob'), registry).changeDetector;
@ -1365,9 +1365,9 @@ class CountingPipe implements PipeTransform {
transform(value, args = null) { return `${value} state:${this.state ++}`; }
}
class PipeWithOnDestroy implements PipeTransform, PipeOnDestroy {
class PipeWithOnDestroy implements PipeTransform, OnDestroy {
destroyCalled: boolean = false;
onDestroy() { this.destroyCalled = true; }
ngOnDestroy() { this.destroyCalled = true; }
transform(value, args = null) { return null; }
}

View File

@ -54,7 +54,8 @@ import {
Inject,
Host,
SkipSelf,
SkipSelfMetadata
SkipSelfMetadata,
OnDestroy
} from 'angular2/core';
import {NgIf, NgFor} from 'angular2/common';
@ -1999,8 +2000,8 @@ class SomeViewport {
}
@Pipe({name: 'double'})
class DoublePipe implements PipeTransform {
onDestroy() {}
class DoublePipe implements PipeTransform, OnDestroy {
ngOnDestroy() {}
transform(value, args = null) { return `${value}${value}`; }
}

View File

@ -108,7 +108,7 @@ var NG_ALL = [
'ApplicationRef.tick()',
*/
'AsyncPipe',
'AsyncPipe.onDestroy()',
'AsyncPipe.ngOnDestroy()',
'AsyncPipe.transform()',
'Attribute',
'Attribute.attributeName',
@ -1425,7 +1425,6 @@ var NG_ALL = [
'OnChanges:dart',
'OnDestroy:dart',
'OnInit:dart',
'PipeOnDestroy:dart',
'PipeTransform:dart',
'reflector',
'RenderBeginCmd:dart',