From 318637ffdef8ccbd1d05b8962772b8d59182088e Mon Sep 17 00:00:00 2001 From: Jeff Cross Date: Thu, 9 Jul 2015 13:08:33 -0700 Subject: [PATCH] feat(change_detection): add static append method to Pipes This change allows creation of a new Pipes object with new pipes appended to pipes of an inherited Pipes. Closes #2901 --- .../src/change_detection/pipes/pipes.ts | 72 ++++++++++++++++++- .../test/change_detection/pipes/pipes_spec.ts | 16 +++++ 2 files changed, 86 insertions(+), 2 deletions(-) diff --git a/modules/angular2/src/change_detection/pipes/pipes.ts b/modules/angular2/src/change_detection/pipes/pipes.ts index b6ac1745915890..65a684bbc9a7f6 100644 --- a/modules/angular2/src/change_detection/pipes/pipes.ts +++ b/modules/angular2/src/change_detection/pipes/pipes.ts @@ -1,4 +1,4 @@ -import {List, ListWrapper} from 'angular2/src/facade/collection'; +import {ListWrapper, isListLikeIterable, StringMapWrapper} from 'angular2/src/facade/collection'; import {isBlank, isPresent, BaseException, CONST} from 'angular2/src/facade/lang'; import {Pipe, PipeFactory} from './pipe'; import {Injectable} from 'angular2/src/di/decorators'; @@ -7,7 +7,25 @@ import {ChangeDetectorRef} from '../change_detector_ref'; @Injectable() @CONST() export class Pipes { - constructor(public config) {} + /** + * Map of {@link Pipe} names to {@link PipeFactory} lists used to configure the + * {@link Pipes} registry. + * + * #Example + * + * ``` + * var pipesConfig = { + * 'json': [jsonPipeFactory] + * } + * @Component({ + * viewInjector: [ + * bind(Pipes).toValue(new Pipes(pipesConfig)) + * ] + * }) + * ``` + */ + config: StringMap; + constructor(config: StringMap) { this.config = config; } get(type: string, obj, cdRef?: ChangeDetectorRef, existingPipe?: Pipe): Pipe { if (isPresent(existingPipe) && existingPipe.supports(obj)) return existingPipe; @@ -20,6 +38,56 @@ export class Pipes { return factory.create(cdRef); } + /** + * Takes a {@link Pipes} config object and returns a factory used to append the + * provided config with an existing {@link Pipes} instance to return a new + * {@link Pipes} instance. + * + * If the provided config contains a key that is not yet present in the + * inherited {@link Pipes}' config, a new {@link PipeFactory} list will be created + * for that key. Otherwise, the provided config will be merged with the inherited + * {@link Pipes} instance by appending pipes to their respective keys, without mutating + * the inherited {@link Pipes}. + * + * The following example shows how to append a new {@link PipeFactory} to the + * existing list of `async` factories, which will only be applied to the injector + * for this component and its children. This step is all that's required to make a new + * pipe available to this component's template. + * + * # Example + * + * ``` + * @Component({ + * viewInjector: [ + * bind(Pipes).toFactory(Pipes.append({ + * async: [newAsyncPipe] + * }) + * ] + * }) + * ``` + */ + static append(config) { + return (pipes: Pipes): Pipes => { + var mergedConfig: StringMap = >{}; + + // Manual deep copy of existing Pipes config, + // so that lists of PipeFactories don't get mutated. + StringMapWrapper.forEach(pipes.config, (v: PipeFactory[], k: string) => { + var localPipeList: PipeFactory[] = mergedConfig[k] = []; + v.forEach((p: PipeFactory) => { localPipeList.push(p); }); + }); + + StringMapWrapper.forEach(config, (v: PipeFactory[], k: string) => { + if (isListLikeIterable(mergedConfig[k])) { + mergedConfig[k] = ListWrapper.concat(mergedConfig[k], config[k]); + } else { + mergedConfig[k] = config[k]; + } + }); + return new Pipes(mergedConfig); + }; + } + private _getListOfFactories(type: string, obj: any): PipeFactory[] { var listOfFactories = this.config[type]; if (isBlank(listOfFactories)) { diff --git a/modules/angular2/test/change_detection/pipes/pipes_spec.ts b/modules/angular2/test/change_detection/pipes/pipes_spec.ts index ac1984eec2a597..400004ed2ad6e1 100644 --- a/modules/angular2/test/change_detection/pipes/pipes_spec.ts +++ b/modules/angular2/test/change_detection/pipes/pipes_spec.ts @@ -12,6 +12,7 @@ import { } from 'angular2/test_lib'; import {Pipes} from 'angular2/src/change_detection/pipes/pipes'; +import {PipeFactory} from 'angular2/src/change_detection/pipes/pipe'; export function main() { describe("pipe registry", () => { @@ -73,5 +74,20 @@ export function main() { expect(() => r.get("type", "some object")) .toThrowError(`Cannot find 'type' pipe supporting object 'some object'`); }); + + describe('.append()', () => { + it('should create a factory that appends new pipes to old', () => { + firstPipeFactory.spy("supports").andReturn(false); + secondPipeFactory.spy("supports").andReturn(true); + secondPipeFactory.spy("create").andReturn(secondPipe); + var originalPipes: Pipes = new Pipes({'async': [firstPipeFactory]}); + var factory = Pipes.append({'async':[secondPipeFactory]}); + var pipes = factory(originalPipes); + + expect(pipes.config['async'].length).toBe(2); + expect(originalPipes.config['async'].length).toBe(1); + expect(pipes.get('async', 'second plz')).toBe(secondPipe); + }); + }); }); } \ No newline at end of file