refactor(pipes): removed pipes from properties

BREAKING CHANGE:

This PR remove an ability to use pipes in the properties config. Instead, inject the pipe registry.
This commit is contained in:
vsavkin 2015-06-18 15:40:12 -07:00
parent ad7aca631d
commit 20a8f0dbe5
31 changed files with 261 additions and 270 deletions

View File

@ -145,7 +145,7 @@ export class ChangeDetectorJITGenerator {
_getNonNullPipeNames(): List<string> { _getNonNullPipeNames(): List<string> {
var pipes = []; var pipes = [];
this.records.forEach((r) => { this.records.forEach((r) => {
if (r.mode === RecordType.PIPE || r.mode === RecordType.BINDING_PIPE) { if (r.isPipeRecord()) {
pipes.push(this._pipeNames[r.selfIndex]); pipes.push(this._pipeNames[r.selfIndex]);
} }
}); });
@ -245,7 +245,7 @@ export class ChangeDetectorJITGenerator {
var change = this._changeNames[r.selfIndex]; var change = this._changeNames[r.selfIndex];
var pipe = this._pipeNames[r.selfIndex]; var pipe = this._pipeNames[r.selfIndex];
var cdRef = r.mode === RecordType.BINDING_PIPE ? "this.ref" : "null"; var cdRef = "this.ref";
var protoIndex = r.selfIndex - 1; var protoIndex = r.selfIndex - 1;
var pipeType = r.name; var pipeType = r.name;

View File

@ -269,14 +269,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
if (isPresent(storedPipe)) { if (isPresent(storedPipe)) {
storedPipe.onDestroy(); storedPipe.onDestroy();
} }
var pipe = this.pipeRegistry.get(proto.name, context, this.ref);
// Currently, only pipes that used in bindings in the template get
// the changeDetectorRef of the encompassing component.
//
// In the future, pipes declared in the bind configuration should
// be able to access the changeDetectorRef of that component.
var cdr = proto.mode === RecordType.BINDING_PIPE ? this.ref : null;
var pipe = this.pipeRegistry.get(proto.name, context, cdr);
this._writePipe(proto, pipe); this._writePipe(proto, pipe);
return pipe; return pipe;
} }

View File

@ -141,11 +141,8 @@ export class KeyedAccess extends AST {
visit(visitor: AstVisitor) { return visitor.visitKeyedAccess(this); } visit(visitor: AstVisitor) { return visitor.visitKeyedAccess(this); }
} }
export class Pipe extends AST { export class BindingPipe extends AST {
constructor(public exp: AST, public name: string, public args: List<any>, constructor(public exp: AST, public name: string, public args: List<any>) { super(); }
public inBinding: boolean) {
super();
}
visit(visitor: AstVisitor) { return visitor.visitPipe(this); } visit(visitor: AstVisitor) { return visitor.visitPipe(this); }
} }
@ -336,7 +333,7 @@ export interface AstVisitor {
visitChain(ast: Chain): any; visitChain(ast: Chain): any;
visitConditional(ast: Conditional): any; visitConditional(ast: Conditional): any;
visitIf(ast: If): any; visitIf(ast: If): any;
visitPipe(ast: Pipe): any; visitPipe(ast: BindingPipe): any;
visitFunctionCall(ast: FunctionCall): any; visitFunctionCall(ast: FunctionCall): any;
visitImplicitReceiver(ast: ImplicitReceiver): any; visitImplicitReceiver(ast: ImplicitReceiver): any;
visitInterpolation(ast: Interpolation): any; visitInterpolation(ast: Interpolation): any;
@ -394,8 +391,8 @@ export class AstTransformer implements AstVisitor {
ast.falseExp.visit(this)); ast.falseExp.visit(this));
} }
visitPipe(ast: Pipe) { visitPipe(ast: BindingPipe) {
return new Pipe(ast.exp.visit(this), ast.name, this.visitAll(ast.args), ast.inBinding); return new BindingPipe(ast.exp.visit(this), ast.name, this.visitAll(ast.args));
} }
visitKeyedAccess(ast: KeyedAccess) { visitKeyedAccess(ast: KeyedAccess) {

View File

@ -34,7 +34,7 @@ import {
PrefixNot, PrefixNot,
Conditional, Conditional,
If, If,
Pipe, BindingPipe,
Assignment, Assignment,
Chain, Chain,
KeyedAccess, KeyedAccess,
@ -73,15 +73,6 @@ export class Parser {
return new ASTWithSource(ast, input, location); return new ASTWithSource(ast, input, location);
} }
addPipes(bindingAst: ASTWithSource, pipes: List<string>): ASTWithSource {
if (ListWrapper.isEmpty(pipes)) return bindingAst;
var res: AST = ListWrapper.reduce(
pipes, (result, currentPipeName) => new Pipe(result, currentPipeName, [], false),
bindingAst.ast);
return new ASTWithSource(res, bindingAst.source, bindingAst.location);
}
parseTemplateBindings(input: string, location: any): List<TemplateBinding> { parseTemplateBindings(input: string, location: any): List<TemplateBinding> {
var tokens = this._lexer.tokenize(input); var tokens = this._lexer.tokenize(input);
return new _ParseAST(input, location, tokens, this._reflector, false).parseTemplateBindings(); return new _ParseAST(input, location, tokens, this._reflector, false).parseTemplateBindings();
@ -224,7 +215,7 @@ class _ParseAST {
while (this.optionalCharacter($COLON)) { while (this.optionalCharacter($COLON)) {
args.push(this.parsePipe()); args.push(this.parsePipe());
} }
result = new Pipe(result, name, args, true); result = new BindingPipe(result, name, args);
} while (this.optionalOperator("|")); } while (this.optionalOperator("|"));
} }

View File

@ -15,12 +15,10 @@ import {
isArray isArray
} from 'angular2/src/facade/lang'; } from 'angular2/src/facade/lang';
import {WrappedValue, Pipe, PipeFactory} from './pipe'; import {WrappedValue, Pipe, BasePipe, PipeFactory} from './pipe';
@CONST() @CONST()
export class IterableChangesFactory extends PipeFactory { export class IterableChangesFactory implements PipeFactory {
constructor() { super(); }
supports(obj): boolean { return IterableChanges.supportsObj(obj); } supports(obj): boolean { return IterableChanges.supportsObj(obj); }
create(cdRef): Pipe { return new IterableChanges(); } create(cdRef): Pipe { return new IterableChanges(); }
@ -29,7 +27,7 @@ export class IterableChangesFactory extends PipeFactory {
/** /**
* @exportedAs angular2/pipes * @exportedAs angular2/pipes
*/ */
export class IterableChanges extends Pipe { export class IterableChanges extends BasePipe {
private _collection = null; private _collection = null;
private _length: int = null; private _length: int = null;
// Keeps track of the used records at any point in time (during & across `_check()` calls) // Keeps track of the used records at any point in time (during & across `_check()` calls)
@ -95,7 +93,7 @@ export class IterableChanges extends Pipe {
if (this.check(collection)) { if (this.check(collection)) {
return WrappedValue.wrap(this); return WrappedValue.wrap(this);
} else { } else {
return this; return null;
} }
} }

View File

@ -1,5 +1,5 @@
import {isBlank, isPresent, Json} from 'angular2/src/facade/lang'; import {isBlank, isPresent, Json} from 'angular2/src/facade/lang';
import {Pipe, PipeFactory} from './pipe'; import {Pipe, BasePipe, PipeFactory} from './pipe';
/** /**
* Implements json transforms to any object. * Implements json transforms to any object.
@ -26,9 +26,7 @@ import {Pipe, PipeFactory} from './pipe';
* *
* @exportedAs angular2/pipes * @exportedAs angular2/pipes
*/ */
export class JsonPipe extends Pipe { export class JsonPipe extends BasePipe {
supports(obj): boolean { return true; }
transform(value): string { return Json.stringify(value); } transform(value): string { return Json.stringify(value); }
create(cdRef): Pipe { return this } create(cdRef): Pipe { return this }

View File

@ -1,15 +1,13 @@
import {ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; import {ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {stringify, looseIdentical, isJsObject, CONST} from 'angular2/src/facade/lang'; import {stringify, looseIdentical, isJsObject, CONST} from 'angular2/src/facade/lang';
import {WrappedValue, Pipe, PipeFactory} from './pipe'; import {WrappedValue, BasePipe, Pipe, PipeFactory} from './pipe';
/** /**
* @exportedAs angular2/pipes * @exportedAs angular2/pipes
*/ */
@CONST() @CONST()
export class KeyValueChangesFactory extends PipeFactory { export class KeyValueChangesFactory implements PipeFactory {
constructor() { super(); }
supports(obj): boolean { return KeyValueChanges.supportsObj(obj); } supports(obj): boolean { return KeyValueChanges.supportsObj(obj); }
create(cdRef): Pipe { return new KeyValueChanges(); } create(cdRef): Pipe { return new KeyValueChanges(); }
@ -18,7 +16,7 @@ export class KeyValueChangesFactory extends PipeFactory {
/** /**
* @exportedAs angular2/pipes * @exportedAs angular2/pipes
*/ */
export class KeyValueChanges extends Pipe { export class KeyValueChanges extends BasePipe {
private _records: Map<any, any> = new Map(); private _records: Map<any, any> = new Map();
private _mapHead: KVChangeRecord = null; private _mapHead: KVChangeRecord = null;
private _previousMapHead: KVChangeRecord = null; private _previousMapHead: KVChangeRecord = null;
@ -37,7 +35,7 @@ export class KeyValueChanges extends Pipe {
if (this.check(map)) { if (this.check(map)) {
return WrappedValue.wrap(this); return WrappedValue.wrap(this);
} else { } else {
return this; return null;
} }
} }

View File

@ -23,7 +23,7 @@ import {Pipe} from './pipe';
* *
* @exportedAs angular2/pipes * @exportedAs angular2/pipes
*/ */
export class LowerCasePipe extends Pipe { export class LowerCasePipe implements Pipe {
_latestValue: string = null; _latestValue: string = null;
supports(str): boolean { return isString(str); } supports(str): boolean { return isString(str); }

View File

@ -1,13 +1,11 @@
import {isBlank, CONST} from 'angular2/src/facade/lang'; import {isBlank, CONST} from 'angular2/src/facade/lang';
import {Pipe, WrappedValue, PipeFactory} from './pipe'; import {Pipe, BasePipe, WrappedValue, PipeFactory} from './pipe';
/** /**
* @exportedAs angular2/pipes * @exportedAs angular2/pipes
*/ */
@CONST() @CONST()
export class NullPipeFactory extends PipeFactory { export class NullPipeFactory implements PipeFactory {
constructor() { super(); }
supports(obj): boolean { return NullPipe.supportsObj(obj); } supports(obj): boolean { return NullPipe.supportsObj(obj); }
create(cdRef): Pipe { return new NullPipe(); } create(cdRef): Pipe { return new NullPipe(); }
@ -16,7 +14,7 @@ export class NullPipeFactory extends PipeFactory {
/** /**
* @exportedAs angular2/pipes * @exportedAs angular2/pipes
*/ */
export class NullPipe extends Pipe { export class NullPipe extends BasePipe {
called: boolean = false; called: boolean = false;
static supportsObj(obj): boolean { return isBlank(obj); } static supportsObj(obj): boolean { return isBlank(obj); }

View File

@ -29,14 +29,14 @@ import {ChangeDetectorRef} from '../change_detector_ref';
* *
* @exportedAs angular2/pipes * @exportedAs angular2/pipes
*/ */
export class ObservablePipe extends Pipe { export class ObservablePipe implements Pipe {
_latestValue: Object = null; _latestValue: Object = null;
_latestReturnedValue: Object = null; _latestReturnedValue: Object = null;
_subscription: Object = null; _subscription: Object = null;
_observable: Observable = null; _observable: Observable = null;
constructor(public _ref: ChangeDetectorRef) { super(); } constructor(public _ref: ChangeDetectorRef) {}
supports(obs): boolean { return ObservableWrapper.isObservable(obs); } supports(obs): boolean { return ObservableWrapper.isObservable(obs); }
@ -91,9 +91,7 @@ export class ObservablePipe extends Pipe {
* @exportedAs angular2/pipes * @exportedAs angular2/pipes
*/ */
@CONST() @CONST()
export class ObservablePipeFactory extends PipeFactory { export class ObservablePipeFactory implements PipeFactory {
constructor() { super(); }
supports(obs): boolean { return ObservableWrapper.isObservable(obs); } supports(obs): boolean { return ObservableWrapper.isObservable(obs); }
create(cdRef): Pipe { return new ObservablePipe(cdRef); } create(cdRef): Pipe { return new ObservablePipe(cdRef); }

View File

@ -36,11 +36,13 @@ var _wrappedIndex = 0;
* #Example * #Example
* *
* ``` * ```
* class DoublePipe extends Pipe { * class DoublePipe implements Pipe {
* supports(obj) { * supports(obj) {
* return true; * return true;
* } * }
* *
* onDestroy() {}
*
* transform(value) { * transform(value) {
* return `${value}${value}`; * return `${value}${value}`;
* } * }
@ -49,24 +51,34 @@ var _wrappedIndex = 0;
* *
* @exportedAs angular2/pipes * @exportedAs angular2/pipes
*/ */
export class Pipe { export interface Pipe {
supports(obj): boolean { return false; } supports(obj): boolean;
onDestroy() {} onDestroy(): void;
transform(value: any): any { return null; } transform(value: any): any;
} }
// TODO: vsavkin: make it an interface /**
@CONST() * Provides default implementation of supports and onDestroy.
export class PipeFactory { *
supports(obs): boolean { * #Example
_abstract(); *
return false; * ```
} * class DoublePipe extends BasePipe {*
* transform(value) {
* return `${value}${value}`;
* }
* }
* ```
*/
export class BasePipe implements Pipe {
supports(obj): boolean { return true; }
onDestroy(): void {}
transform(value: any): any { return _abstract(); }
}
create(cdRef): Pipe { export interface PipeFactory {
_abstract(); supports(obs): boolean;
return null; create(cdRef): Pipe;
}
} }
function _abstract() { function _abstract() {

View File

@ -1,6 +1,6 @@
import {List, ListWrapper} from 'angular2/src/facade/collection'; import {List, ListWrapper} from 'angular2/src/facade/collection';
import {isBlank, isPresent, BaseException, CONST} from 'angular2/src/facade/lang'; import {isBlank, isPresent, BaseException, CONST} from 'angular2/src/facade/lang';
import {Pipe} from './pipe'; import {Pipe, PipeFactory} from './pipe';
import {Injectable} from 'angular2/src/di/decorators'; import {Injectable} from 'angular2/src/di/decorators';
import {ChangeDetectorRef} from '../change_detector_ref'; import {ChangeDetectorRef} from '../change_detector_ref';
@ -8,18 +8,31 @@ import {ChangeDetectorRef} from '../change_detector_ref';
export class PipeRegistry { export class PipeRegistry {
constructor(public config) {} constructor(public config) {}
get(type: string, obj, cdRef: ChangeDetectorRef): Pipe { get(type: string, obj, cdRef?: ChangeDetectorRef, existingPipe?: Pipe): Pipe {
var listOfConfigs = this.config[type]; if (isPresent(existingPipe) && existingPipe.supports(obj)) return existingPipe;
if (isBlank(listOfConfigs)) {
if (isPresent(existingPipe)) existingPipe.onDestroy();
var factories = this._getListOfFactories(type, obj);
var factory = this._getMatchingFactory(factories, type, obj);
return factory.create(cdRef);
}
private _getListOfFactories(type: string, obj: any): PipeFactory[] {
var listOfFactories = this.config[type];
if (isBlank(listOfFactories)) {
throw new BaseException(`Cannot find '${type}' pipe supporting object '${obj}'`); throw new BaseException(`Cannot find '${type}' pipe supporting object '${obj}'`);
} }
return listOfFactories;
}
var matchingConfig = ListWrapper.find(listOfConfigs, (pipeConfig) => pipeConfig.supports(obj)); private _getMatchingFactory(listOfFactories: PipeFactory[], type: string, obj: any): PipeFactory {
var matchingFactory =
if (isBlank(matchingConfig)) { ListWrapper.find(listOfFactories, pipeFactory => pipeFactory.supports(obj));
if (isBlank(matchingFactory)) {
throw new BaseException(`Cannot find '${type}' pipe supporting object '${obj}'`); throw new BaseException(`Cannot find '${type}' pipe supporting object '${obj}'`);
} }
return matchingFactory;
return matchingConfig.create(cdRef);
} }
} }

View File

@ -28,12 +28,12 @@ import {ChangeDetectorRef} from '../change_detector_ref';
* *
* @exportedAs angular2/pipes * @exportedAs angular2/pipes
*/ */
export class PromisePipe extends Pipe { export class PromisePipe implements Pipe {
_latestValue: Object = null; _latestValue: Object = null;
_latestReturnedValue: Object = null; _latestReturnedValue: Object = null;
_sourcePromise: Promise<any>; _sourcePromise: Promise<any>;
constructor(public _ref: ChangeDetectorRef) { super(); } constructor(public _ref: ChangeDetectorRef) {}
supports(promise): boolean { return isPromise(promise); } supports(promise): boolean { return isPromise(promise); }

View File

@ -23,7 +23,7 @@ import {Pipe} from './pipe';
* *
* @exportedAs angular2/pipes * @exportedAs angular2/pipes
*/ */
export class UpperCasePipe extends Pipe { export class UpperCasePipe implements Pipe {
_latestValue: string = null; _latestValue: string = null;
supports(str): boolean { return isString(str); } supports(str): boolean { return isString(str); }

View File

@ -11,7 +11,7 @@ import {
Chain, Chain,
Conditional, Conditional,
If, If,
Pipe, BindingPipe,
FunctionCall, FunctionCall,
ImplicitReceiver, ImplicitReceiver,
Interpolation, Interpolation,
@ -179,10 +179,9 @@ class _ConvertAstIntoProtoRecords implements AstVisitor {
null, 0); null, 0);
} }
visitPipe(ast: Pipe) { visitPipe(ast: BindingPipe) {
var value = ast.exp.visit(this); var value = ast.exp.visit(this);
var type = ast.inBinding ? RecordType.BINDING_PIPE : RecordType.PIPE; return this._addRecord(RecordType.PIPE, ast.name, ast.name, [], null, value);
return this._addRecord(type, ast.name, ast.name, [], null, value);
} }
visitKeyedAccess(ast: KeyedAccess) { visitKeyedAccess(ast: KeyedAccess) {

View File

@ -12,7 +12,6 @@ export enum RecordType {
INVOKE_CLOSURE, INVOKE_CLOSURE,
KEYED_ACCESS, KEYED_ACCESS,
PIPE, PIPE,
BINDING_PIPE,
INTERPOLATE, INTERPOLATE,
SAFE_PROPERTY, SAFE_PROPERTY,
SAFE_INVOKE_METHOD, SAFE_INVOKE_METHOD,
@ -30,9 +29,7 @@ export class ProtoRecord {
return this.mode === RecordType.INTERPOLATE || this.mode === RecordType.PRIMITIVE_OP; return this.mode === RecordType.INTERPOLATE || this.mode === RecordType.PRIMITIVE_OP;
} }
isPipeRecord(): boolean { isPipeRecord(): boolean { return this.mode === RecordType.PIPE; }
return this.mode === RecordType.PIPE || this.mode === RecordType.BINDING_PIPE;
}
isLifeCycleRecord(): boolean { return this.mode === RecordType.DIRECTIVE_LIFECYCLE; } isLifeCycleRecord(): boolean { return this.mode === RecordType.DIRECTIVE_LIFECYCLE; }
} }

View File

@ -8,12 +8,17 @@ import {DOM} from 'angular2/src/dom/dom_adapter';
export class CSSClass { export class CSSClass {
_domEl; _domEl;
_pipe; _pipe;
_prevRawClass; _rawClass;
rawClass;
constructor(private _pipeRegistry: PipeRegistry, ngEl: ElementRef) { constructor(private _pipeRegistry: PipeRegistry, ngEl: ElementRef) {
this._domEl = ngEl.domElement; this._domEl = ngEl.domElement;
} }
set rawClass(v) {
this._rawClass = v;
this._pipe = this._pipeRegistry.get('keyValDiff', this._rawClass);
}
_toggleClass(className, enabled): void { _toggleClass(className, enabled): void {
if (enabled) { if (enabled) {
DOM.addClass(this._domEl, className); DOM.addClass(this._domEl, className);
@ -23,19 +28,15 @@ export class CSSClass {
} }
onCheck() { onCheck() {
if (this.rawClass != this._prevRawClass) { var diff = this._pipe.transform(this._rawClass);
this._prevRawClass = this.rawClass; if (isPresent(diff)) this._applyChanges(diff.wrapped);
this._pipe = isPresent(this.rawClass) ? }
this._pipeRegistry.get('keyValDiff', this.rawClass, null) :
null;
}
if (isPresent(this._pipe) && this._pipe.check(this.rawClass)) { private _applyChanges(diff) {
this._pipe.forEachAddedItem( if (isPresent(diff)) {
(record) => { this._toggleClass(record.key, record.currentValue); }); diff.forEachAddedItem((record) => { this._toggleClass(record.key, record.currentValue); });
this._pipe.forEachChangedItem( diff.forEachChangedItem((record) => { this._toggleClass(record.key, record.currentValue); });
(record) => { this._toggleClass(record.key, record.currentValue); }); diff.forEachRemovedItem((record) => {
this._pipe.forEachRemovedItem((record) => {
if (record.previousValue) { if (record.previousValue) {
DOM.removeClass(this._domEl, record.key); DOM.removeClass(this._domEl, record.key);
} }

View File

@ -1,5 +1,12 @@
import {Directive} from 'angular2/annotations'; import {Directive} from 'angular2/annotations';
import {ViewContainerRef, ViewRef, ProtoViewRef} from 'angular2/core'; import {
ViewContainerRef,
ViewRef,
ProtoViewRef,
PipeRegistry,
onCheck,
Pipe
} from 'angular2/angular2';
import {isPresent, isBlank} from 'angular2/src/facade/lang'; import {isPresent, isBlank} from 'angular2/src/facade/lang';
/** /**
@ -34,17 +41,25 @@ import {isPresent, isBlank} from 'angular2/src/facade/lang';
* *
* @exportedAs angular2/directives * @exportedAs angular2/directives
*/ */
@Directive( @Directive({selector: '[ng-for][ng-for-of]', properties: ['ngForOf'], lifecycle: [onCheck]})
{selector: '[ng-for][ng-for-of]', properties: ['iterableChanges: ngForOf | iterableDiff']})
export class NgFor { export class NgFor {
viewContainer: ViewContainerRef; _ngForOf: any;
protoViewRef: ProtoViewRef; _pipe: Pipe;
constructor(viewContainer: ViewContainerRef, protoViewRef: ProtoViewRef) {
this.viewContainer = viewContainer; constructor(private viewContainer: ViewContainerRef, private protoViewRef: ProtoViewRef,
this.protoViewRef = protoViewRef; private pipes: PipeRegistry) {}
set ngForOf(value: any) {
this._ngForOf = value;
this._pipe = this.pipes.get("iterableDiff", value, null, this._pipe);
} }
set iterableChanges(changes) { onCheck() {
var diff = this._pipe.transform(this._ngForOf);
if (isPresent(diff)) this._applyChanges(diff.wrapped);
}
private _applyChanges(changes) {
if (isBlank(changes)) { if (isBlank(changes)) {
this.viewContainer.clear(); this.viewContainer.clear();
return; return;
@ -67,11 +82,11 @@ export class NgFor {
NgFor.bulkInsert(insertTuples, this.viewContainer, this.protoViewRef); NgFor.bulkInsert(insertTuples, this.viewContainer, this.protoViewRef);
for (var i = 0; i < insertTuples.length; i++) { for (var i = 0; i < insertTuples.length; i++) {
this.perViewChange(insertTuples[i].view, insertTuples[i].record); this._perViewChange(insertTuples[i].view, insertTuples[i].record);
} }
} }
perViewChange(view, record) { private _perViewChange(view, record) {
view.setLocal('\$implicit', record.item); view.setLocal('\$implicit', record.item);
view.setLocal('index', record.currentIndex); view.setLocal('index', record.currentIndex);
} }

View File

@ -147,8 +147,7 @@ export class DirectiveParser implements CompileStep {
// Bindings are optional, so this binding only needs to be set up if an expression is given. // Bindings are optional, so this binding only needs to be set up if an expression is given.
if (isPresent(bindingAst)) { if (isPresent(bindingAst)) {
var fullExpAstWithBindPipes = this._parser.addPipes(bindingAst, pipes); directiveBinderBuilder.bindProperty(dirProperty, bindingAst);
directiveBinderBuilder.bindProperty(dirProperty, fullExpAstWithBindPipes);
} }
} }

View File

@ -0,0 +1,24 @@
library test_lib.spies;
import 'package:angular2/change_detection.dart';
import './test_lib.dart';
@proxy
class SpyChangeDetector extends SpyObject implements ChangeDetector {
noSuchMethod(m) => super.noSuchMethod(m);
}
@proxy
class SpyProtoChangeDetector extends SpyObject implements ProtoChangeDetector {
noSuchMethod(m) => super.noSuchMethod(m);
}
@proxy
class SpyPipe extends SpyObject implements Pipe {
noSuchMethod(m) => super.noSuchMethod(m);
}
@proxy
class SpyPipeFactory extends SpyObject implements PipeFactory {
noSuchMethod(m) => super.noSuchMethod(m);
}

View File

@ -3,45 +3,19 @@ import {
ProtoChangeDetector, ProtoChangeDetector,
DynamicChangeDetector DynamicChangeDetector
} from 'angular2/change_detection'; } from 'angular2/change_detection';
import {BasePipe} from 'angular2/src/change_detection/pipes/pipe';
import {SpyObject, proxy} from './test_lib'; import {SpyObject, proxy} from './test_lib';
// Remove dummy methods after https://github.com/angular/ts2dart/issues/209 is fixed. export class SpyChangeDetector extends SpyObject {
@proxy constructor() { super(DynamicChangeDetector); }
export class SpyChangeDetector extends SpyObject implements ChangeDetector {
parent: ChangeDetector;
mode: string;
constructor() { super(DynamicChangeDetector, true); }
addChild(cd: ChangeDetector): void { return this.spy("addChild")(cd); }
addShadowDomChild(cd: ChangeDetector): void { return this.spy("addShadowDomChild")(cd); }
removeChild(cd: ChangeDetector): void { return this.spy("removeChild")(cd); }
removeShadowDomChild(cd: ChangeDetector): void { return this.spy("removeShadowDomChild")(cd); }
remove(): void { return this.spy("remove")(); }
hydrate(context: any, locals: any, directives: any): void {
return this.spy("hydrate")(context, locals, directives);
}
dehydrate(): void { return this.spy("dehydrate")(); }
markPathToRootAsCheckOnce(): void { return this.spy("markPathToRootAsCheckOnce")(); }
detectChanges(): void { return this.spy("detectChanges")(); }
checkNoChanges(): void { return this.spy("checkNoChanges")(); }
noSuchMethod(m) { return super.noSuchMethod(m) }
} }
// Remove dummy methods after https://github.com/angular/ts2dart/issues/209 is fixed. export class SpyProtoChangeDetector extends SpyObject {
@proxy constructor() { super(DynamicChangeDetector); }
export class SpyProtoChangeDetector extends SpyObject implements ProtoChangeDetector { }
constructor() { super(DynamicChangeDetector, true); }
instantiate(v: any): any { return this.spy("instantiate")(v); } export class SpyPipe extends SpyObject {
} constructor() { super(BasePipe); }
}
export class SpyPipeFactory extends SpyObject {}

View File

@ -187,7 +187,7 @@ class SpyFunction extends gns.SpyFunction {
class SpyObject extends gns.SpyObject { class SpyObject extends gns.SpyObject {
final Map<String, SpyFunction> _spyFuncs = {}; final Map<String, SpyFunction> _spyFuncs = {};
SpyObject([arg, arg2]) {} SpyObject([arg]) {}
SpyFunction spy(String funcName) => SpyFunction spy(String funcName) =>
_spyFuncs.putIfAbsent(funcName, () => new SpyFunction(funcName)); _spyFuncs.putIfAbsent(funcName, () => new SpyFunction(funcName));

View File

@ -274,7 +274,7 @@ export interface GuinessCompatibleSpy extends jasmine.Spy {
} }
export class SpyObject { export class SpyObject {
constructor(type = null, forceSpyCreation: boolean = false) { constructor(type = null) {
if (type) { if (type) {
for (var prop in type.prototype) { for (var prop in type.prototype) {
var m = null; var m = null;
@ -287,11 +287,7 @@ export class SpyObject {
// should not matter. // should not matter.
} }
if (typeof m === 'function') { if (typeof m === 'function') {
if (forceSpyCreation) { this.spy(prop);
this.createSpy(prop);
} else {
this.spy(prop);
}
} }
} }
} }
@ -301,14 +297,8 @@ export class SpyObject {
spy(name) { spy(name) {
if (!this[name]) { if (!this[name]) {
return this.createSpy(name); this[name] = this._createGuinnessCompatibleSpy(name);
} else {
return this[name];
} }
}
createSpy(name) {
this[name] = this._createGuinnessCompatibleSpy(name);
return this[name]; return this[name];
} }

View File

@ -223,7 +223,7 @@ class _CodegenState {
List<String> _getNonNullPipeNames() { List<String> _getNonNullPipeNames() {
return _records return _records
.where((r) => .where((r) =>
r.mode == RecordType.PIPE || r.mode == RecordType.BINDING_PIPE) r.mode == RecordType.PIPE)
.map((r) => _pipeNames[r.selfIndex]) .map((r) => _pipeNames[r.selfIndex])
.toList(); .toList();
} }
@ -310,7 +310,7 @@ class _CodegenState {
var change = _changeNames[r.selfIndex]; var change = _changeNames[r.selfIndex];
var pipe = _pipeNames[r.selfIndex]; var pipe = _pipeNames[r.selfIndex];
var cdRef = r.mode == RecordType.BINDING_PIPE ? 'this.ref' : 'null'; var cdRef = 'this.ref';
var protoIndex = r.selfIndex - 1; var protoIndex = r.selfIndex - 1;
var pipeType = r.name; var pipeType = r.name;

View File

@ -1,3 +1,4 @@
///<reference path="../../src/change_detection/pipes/pipe.ts"/>
import { import {
ddescribe, ddescribe,
describe, describe,
@ -853,28 +854,19 @@ export function main() {
}); });
} }
class CountingPipe extends Pipe { class CountingPipe implements Pipe {
state: number; state: number = 0;
constructor() { onDestroy() {}
super();
this.state = 0;
}
supports(newValue) { return true; } supports(newValue) { return true; }
transform(value) { return `${value} state:${this.state ++}`; } transform(value) { return `${value} state:${this.state ++}`; }
} }
class OncePipe extends Pipe { class OncePipe implements Pipe {
called: boolean; called: boolean = false;
destroyCalled: boolean; destroyCalled: boolean = false;
constructor() {
super();
this.called = false;
this.destroyCalled = false;
}
supports(newValue) { return !this.called; } supports(newValue) { return !this.called; }
@ -886,11 +878,19 @@ class OncePipe extends Pipe {
} }
} }
class IdentityPipe extends Pipe { class IdentityPipe implements Pipe {
supports(obj): boolean { return true; }
onDestroy() {}
transform(value) { return value; } transform(value) { return value; }
} }
class WrappedPipe extends Pipe { class WrappedPipe implements Pipe {
supports(obj): boolean { return true; }
onDestroy() {}
transform(value) { return WrappedValue.wrap(value); } transform(value) { return WrappedValue.wrap(value); }
} }
@ -907,7 +907,7 @@ class FakePipeRegistry extends PipeRegistry {
this.numberOfLookups = 0; this.numberOfLookups = 0;
} }
get(type: string, obj, cdRef) { get(type: string, obj, cdRef?, existingPipe?) {
if (type != this.pipeType) return null; if (type != this.pipeType) return null;
this.numberOfLookups++; this.numberOfLookups++;
this.cdRef = cdRef; this.cdRef = cdRef;

View File

@ -6,7 +6,7 @@ import {Parser} from 'angular2/src/change_detection/parser/parser';
import {Unparser} from './unparser'; import {Unparser} from './unparser';
import {Lexer} from 'angular2/src/change_detection/parser/lexer'; import {Lexer} from 'angular2/src/change_detection/parser/lexer';
import {Locals} from 'angular2/src/change_detection/parser/locals'; import {Locals} from 'angular2/src/change_detection/parser/locals';
import {Pipe, LiteralPrimitive} from 'angular2/src/change_detection/parser/ast'; import {BindingPipe, LiteralPrimitive} from 'angular2/src/change_detection/parser/ast';
class TestData { class TestData {
constructor(public a?: any, public b?: any, public fnReturnValue?: any) {} constructor(public a?: any, public b?: any, public fnReturnValue?: any) {}
@ -39,8 +39,6 @@ export function main() {
return createParser().parseInterpolation(text, location); return createParser().parseInterpolation(text, location);
} }
function addPipes(ast, pipes): any { return createParser().addPipes(ast, pipes); }
function emptyLocals() { return new Locals(null, new Map()); } function emptyLocals() { return new Locals(null, new Map()); }
function evalAction(text, passedInContext = null, passedInLocals = null) { function evalAction(text, passedInContext = null, passedInLocals = null) {
@ -412,7 +410,7 @@ export function main() {
it("should parse pipes", () => { it("should parse pipes", () => {
var originalExp = '"Foo" | uppercase'; var originalExp = '"Foo" | uppercase';
var ast = parseBinding(originalExp).ast; var ast = parseBinding(originalExp).ast;
expect(ast).toBeAnInstanceOf(Pipe); expect(ast).toBeAnInstanceOf(BindingPipe);
expect(new Unparser().unparse(ast)).toEqual(`(${originalExp})`); expect(new Unparser().unparse(ast)).toEqual(`(${originalExp})`);
}); });
@ -600,7 +598,7 @@ export function main() {
it('should parse pipes', () => { it('should parse pipes', () => {
var bindings = parseTemplateBindings('key value|pipe'); var bindings = parseTemplateBindings('key value|pipe');
var ast = bindings[0].expression.ast; var ast = bindings[0].expression.ast;
expect(ast).toBeAnInstanceOf(Pipe); expect(ast).toBeAnInstanceOf(BindingPipe);
}); });
}); });
@ -622,29 +620,6 @@ export function main() {
}); });
}); });
describe('addPipes', () => {
it('should return the given ast whe the list of pipes is empty', () => {
var ast = parseBinding("1 + 1", "Location");
var transformedAst = addPipes(ast, []);
expect(transformedAst).toBe(ast);
});
it('should append pipe ast nodes', () => {
var ast = parseBinding("1 + 1", "Location");
var transformedAst = addPipes(ast, ['one', 'two']);
expect(transformedAst.ast.name).toEqual("two");
expect(transformedAst.ast.exp.name).toEqual("one");
expect(transformedAst.ast.exp.exp.operation).toEqual("+");
});
it('should preserve location and source', () => {
var ast = parseBinding("1 + 1", "Location");
var transformedAst = addPipes(ast, ['one', 'two']);
expect(transformedAst.source).toEqual("1 + 1");
expect(transformedAst.location).toEqual("Location");
});
});
describe('wrapLiteralPrimitive', () => { describe('wrapLiteralPrimitive', () => {
it('should wrap a literal primitive', () => { it('should wrap a literal primitive', () => {
expect(createParser().wrapLiteralPrimitive("foo", null).eval(null, emptyLocals())) expect(createParser().wrapLiteralPrimitive("foo", null).eval(null, emptyLocals()))

View File

@ -8,7 +8,7 @@ import {
Conditional, Conditional,
EmptyExpr, EmptyExpr,
If, If,
Pipe, BindingPipe,
FunctionCall, FunctionCall,
ImplicitReceiver, ImplicitReceiver,
Interpolation, Interpolation,
@ -81,7 +81,7 @@ export class Unparser implements AstVisitor {
} }
} }
visitPipe(ast: Pipe) { visitPipe(ast: BindingPipe) {
this._expression += '('; this._expression += '(';
this._visit(ast.exp); this._visit(ast.exp);
this._expression += ` | ${ast.name}`; this._expression += ` | ${ast.name}`;

View File

@ -10,7 +10,7 @@ import {
Conditional, Conditional,
EmptyExpr, EmptyExpr,
If, If,
Pipe, BindingPipe,
ImplicitReceiver, ImplicitReceiver,
Interpolation, Interpolation,
KeyedAccess, KeyedAccess,
@ -68,7 +68,7 @@ export function main() {
it('should support Pipe', () => { it('should support Pipe', () => {
var originalExp = '(a | b)'; var originalExp = '(a | b)';
var ast = parseBinding(originalExp).ast; var ast = parseBinding(originalExp).ast;
expect(ast).toBeAnInstanceOf(Pipe); expect(ast).toBeAnInstanceOf(BindingPipe);
expect(unparser.unparse(ast)).toEqual(originalExp); expect(unparser.unparse(ast)).toEqual(originalExp);
}); });

View File

@ -1,45 +1,77 @@
import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib'; import {
ddescribe,
describe,
it,
iit,
xit,
expect,
beforeEach,
afterEach,
SpyPipe,
SpyPipeFactory
} from 'angular2/test_lib';
import {PipeRegistry} from 'angular2/src/change_detection/pipes/pipe_registry'; import {PipeRegistry} from 'angular2/src/change_detection/pipes/pipe_registry';
import {Pipe} from 'angular2/src/change_detection/pipes/pipe';
export function main() { export function main() {
describe("pipe registry", () => { describe("pipe registry", () => {
var firstPipe = new Pipe(); var firstPipe;
var secondPipe = new Pipe(); var secondPipe;
var firstPipeFactory;
var secondPipeFactory;
beforeEach(() => {
firstPipe = <any>new SpyPipe();
secondPipe = <any>new SpyPipe();
firstPipeFactory = <any>new SpyPipeFactory();
secondPipeFactory = <any>new SpyPipeFactory();
});
it("should return an existing pipe if it can support the passed in object", () => {
var r = new PipeRegistry({"type": []});
firstPipe.spy("supports").andReturn(true);
expect(r.get("type", "some object", null, firstPipe)).toEqual(firstPipe);
});
it("should call onDestroy on the provided pipe if it cannot support the provided object",
() => {
firstPipe.spy("supports").andReturn(false);
firstPipeFactory.spy("supports").andReturn(true);
firstPipeFactory.spy("create").andReturn(secondPipe);
var r = new PipeRegistry({"type": [firstPipeFactory]});
expect(r.get("type", "some object", null, firstPipe)).toEqual(secondPipe);
expect(firstPipe.spy("onDestroy")).toHaveBeenCalled();
});
it("should return the first pipe supporting the data type", () => { it("should return the first pipe supporting the data type", () => {
var r = new PipeRegistry( firstPipeFactory.spy("supports").andReturn(false);
{"type": [new PipeFactory(false, firstPipe), new PipeFactory(true, secondPipe)]}); firstPipeFactory.spy("create").andReturn(firstPipe);
expect(r.get("type", "some object", null)).toBe(secondPipe); secondPipeFactory.spy("supports").andReturn(true);
secondPipeFactory.spy("create").andReturn(secondPipe);
var r = new PipeRegistry({"type": [firstPipeFactory, secondPipeFactory]});
expect(r.get("type", "some object")).toBe(secondPipe);
}); });
it("should throw when no matching type", () => { it("should throw when no matching type", () => {
var r = new PipeRegistry({}); var r = new PipeRegistry({});
expect(() => r.get("unknown", "some object", null)) expect(() => r.get("unknown", "some object"))
.toThrowError(`Cannot find 'unknown' pipe supporting object 'some object'`); .toThrowError(`Cannot find 'unknown' pipe supporting object 'some object'`);
}); });
it("should throw when no matching pipe", () => { it("should throw when no matching pipe", () => {
var r = new PipeRegistry({"type": []}); var r = new PipeRegistry({"type": []});
expect(() => r.get("type", "some object", null)) expect(() => r.get("type", "some object"))
.toThrowError(`Cannot find 'type' pipe supporting object 'some object'`); .toThrowError(`Cannot find 'type' pipe supporting object 'some object'`);
}); });
}); });
} }
class PipeFactory {
shouldSupport: boolean;
pipe: any;
constructor(shouldSupport: boolean, pipe: any) {
this.shouldSupport = shouldSupport;
this.pipe = pipe;
}
supports(obj): boolean { return this.shouldSupport; }
create(cdRef): Pipe { return this.pipe; }
}

View File

@ -36,6 +36,7 @@ import {PromiseWrapper, EventEmitter, ObservableWrapper} from 'angular2/src/faca
import {Injector, bind, Injectable, Binding, forwardRef, OpaqueToken, Inject} from 'angular2/di'; import {Injector, bind, Injectable, Binding, forwardRef, OpaqueToken, Inject} from 'angular2/di';
import { import {
PipeFactory,
PipeRegistry, PipeRegistry,
defaultPipeRegistry, defaultPipeRegistry,
ChangeDetection, ChangeDetection,
@ -243,12 +244,11 @@ export function main() {
]; ];
}); });
it("should support pipes in bindings and bind config", it("should support pipes in bindings",
inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => { inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => {
tb.overrideView(MyComp, new viewAnn.View({ tb.overrideView(MyComp, new viewAnn.View({
template: template: '<div [my-dir] #dir="mydir" [elprop]="ctxProp | double"></div>',
'<component-with-pipes #comp [prop]="ctxProp | double"></component-with-pipes>', directives: [MyDir]
directives: [ComponentWithPipes]
})); }));
tb.createView(MyComp, {context: ctx}) tb.createView(MyComp, {context: ctx})
@ -256,10 +256,8 @@ export function main() {
ctx.ctxProp = 'a'; ctx.ctxProp = 'a';
view.detectChanges(); view.detectChanges();
var comp = view.rawView.locals.get("comp"); var dir = view.rawView.locals.get("dir");
expect(dir.dirProp).toEqual('aa');
// it is doubled twice: once in the binding, second time in the bind config
expect(comp.prop).toEqual('aaaa');
async.done(); async.done();
}); });
})); }));
@ -1292,7 +1290,7 @@ class DynamicViewport {
} }
} }
@Directive({selector: '[my-dir]', properties: ['dirProp: elprop']}) @Directive({selector: '[my-dir]', properties: ['dirProp: elprop'], exportAs: 'mydir'})
@Injectable() @Injectable()
class MyDir { class MyDir {
dirProp: string; dirProp: string;
@ -1349,7 +1347,7 @@ class MyComp {
} }
} }
@Component({selector: 'component-with-pipes', properties: ["prop: prop | double"]}) @Component({selector: 'component-with-pipes', properties: ["prop"]})
@View({template: ''}) @View({template: ''})
@Injectable() @Injectable()
class ComponentWithPipes { class ComponentWithPipes {
@ -1430,14 +1428,16 @@ class SomeViewport {
} }
@Injectable() @Injectable()
class DoublePipe extends Pipe { class DoublePipe implements Pipe {
onDestroy() {}
supports(obj) { return true; } supports(obj) { return true; }
transform(value) { return `${value}${value}`; } transform(value) { return `${value}${value}`; }
} }
@Injectable() @Injectable()
class DoublePipeFactory { class DoublePipeFactory implements PipeFactory {
supports(obj) { return true; } supports(obj) { return true; }
create(cdRef) { return new DoublePipe(); } create(cdRef) { return new DoublePipe(); }

View File

@ -86,17 +86,6 @@ export function main() {
expect(directiveBinding.propertyBindings.get('dirProp').source).toEqual('someExpr'); expect(directiveBinding.propertyBindings.get('dirProp').source).toEqual('someExpr');
}); });
it('should bind directive properties with pipes', () => {
var results = process(el('<div some-decor-props></div>'),
{'elProp': parser.parseBinding('someExpr', '')});
var directiveBinding = results[0].directives[0];
var pipedProp = <any>directiveBinding.propertyBindings.get('doubleProp');
var simpleProp = <any>directiveBinding.propertyBindings.get('dirProp');
expect(pipedProp.ast.name).toEqual('double');
expect(pipedProp.ast.exp).toEqual(simpleProp.ast);
expect(simpleProp.source).toEqual('someExpr');
});
it('should bind directive properties from attribute values', () => { it('should bind directive properties from attribute values', () => {
var results = process(el('<div some-decor-props el-prop="someValue"></div>')); var results = process(el('<div some-decor-props el-prop="someValue"></div>'));
var directiveBinding = results[0].directives[0]; var directiveBinding = results[0].directives[0];
@ -237,7 +226,7 @@ var decoratorWithMultipleAttrs = DirectiveMetadata.create({
var someDirectiveWithProps = DirectiveMetadata.create({ var someDirectiveWithProps = DirectiveMetadata.create({
selector: '[some-decor-props]', selector: '[some-decor-props]',
properties: ['dirProp: elProp', 'doubleProp: elProp | double'], properties: ['dirProp: elProp'],
readAttributes: ['some-attr'] readAttributes: ['some-attr']
}); });