From 743d8bc845d3b58667315fefb53559d9544a3a15 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Sat, 27 Jan 2018 13:07:03 -0800 Subject: [PATCH] feat(ivy): add canonical example of a pipe. (#21834) PR Close #21834 --- packages/core/src/metadata/directives.ts | 23 +++++ packages/core/src/render3/definition.ts | 29 +++++- packages/core/src/render3/index.ts | 12 ++- .../core/src/render3/interfaces/definition.ts | 28 ++++++ packages/core/src/render3/pipe.ts | 91 +++++++++++++++++++ .../test/render3/compiler_canonical_spec.ts | 65 ++++++++++++- 6 files changed, 243 insertions(+), 5 deletions(-) create mode 100644 packages/core/src/render3/pipe.ts diff --git a/packages/core/src/metadata/directives.ts b/packages/core/src/metadata/directives.ts index f2dc5d9e13..43f340c60a 100644 --- a/packages/core/src/metadata/directives.ts +++ b/packages/core/src/metadata/directives.ts @@ -783,13 +783,36 @@ export interface PipeDecorator { * @stable */ export interface Pipe { + /** + * Name of the pipe. + * + * The pipe name is used in template bindings. For example if a pipe is named + * `myPipe` then it would be used in the template binding expression like + * so: `{{ exp | myPipe }}`. + */ name: string; + + /** + * If Pipe is pure (its output depends only on its input.) + * + * Normally pipe's `translate` method is invoked on each change detection + * cycle. If the cost of invoking the `translated` method is non-trivial and + * if the output of the pipe depends only on its input, then declaring a pipe + * as pure would short circuit the invocation of the `translate` method and + * invoke the method only when the inputs to the pipe change. + */ pure?: boolean; } /** * Pipe decorator and metadata. * + * Use the `@Pipe` annotation to declare that a given class is a pipe. A pipe + * class must also implement {@link PipeTransform} interface. + * + * To use the pipe include a reference to the pipe class in + * {@link NgModule#declarations}. + * * @stable * @Annotation */ diff --git a/packages/core/src/render3/definition.ts b/packages/core/src/render3/definition.ts index b96eaa3fd6..da7f24d4b8 100644 --- a/packages/core/src/render3/definition.ts +++ b/packages/core/src/render3/definition.ts @@ -7,13 +7,14 @@ */ import {SimpleChange} from '../change_detection/change_detection_util'; +import {PipeTransform} from '../change_detection/pipe_transform'; import {OnChanges, SimpleChanges} from '../metadata/lifecycle_hooks'; import {RendererType2} from '../render/api'; import {Type} from '../type'; import {resolveRendererType2} from '../view/util'; import {diPublic} from './di'; -import {ComponentDef, ComponentDefArgs, DirectiveDef, DirectiveDefArgs} from './interfaces/definition'; +import {ComponentDef, ComponentDefArgs, DirectiveDef, DirectiveDefArgs, PipeDef, PipeType} from './interfaces/definition'; @@ -26,7 +27,7 @@ import {ComponentDef, ComponentDefArgs, DirectiveDef, DirectiveDefArgs} from './ * class MyDirective { * // Generated by Angular Template Compiler * // [Symbol] syntax will not be supported by TypeScript until v2.7 - * static [COMPONENT_DEF_SYMBOL] = defineComponent({ + * static ngComponentDef = defineComponent({ * ... * }); * } @@ -147,7 +148,7 @@ function invertObject(obj: any): any { * class MyDirective { * // Generated by Angular Template Compiler * // [Symbol] syntax will not be supported by TypeScript until v2.7 - * static [DIRECTIVE_DEF_SYMBOL] = defineDirective({ + * static ngDirectiveDef = defineDirective({ * ... * }); * } @@ -155,3 +156,25 @@ function invertObject(obj: any): any { */ export const defineDirective = defineComponent as(directiveDefinition: DirectiveDefArgs) => DirectiveDef; + +/** + * Create a pipe definition object. + * + * # Example + * ``` + * class MyPipe implements PipeTransform { + * // Generated by Angular Template Compiler + * static ngPipeDef = definePipe({ + * ... + * }); + * } + * ``` + * @param type Pipe class reference. Needed to extract pipe lifecycle hooks. + * @param factory A factory for creating a pipe instance. + * @param pure Whether the pipe is pure. + */ +export function definePipe( + {type, factory, pure}: {type: Type, factory: () => PipeTransform, pure?: boolean}): + PipeDef { + throw new Error('TODO: implement!'); +} \ No newline at end of file diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index 11c3200fc5..39e5f71974 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -7,7 +7,7 @@ */ import {createComponentRef, detectChanges, getHostElement, markDirty, renderComponent} from './component'; -import {NgOnChangesFeature, PublicFeature, defineComponent, defineDirective} from './definition'; +import {NgOnChangesFeature, PublicFeature, defineComponent, defineDirective, definePipe} from './definition'; import {InjectFlags} from './di'; import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveType} from './interfaces/definition'; @@ -63,6 +63,15 @@ export { viewEnd as v, } from './instructions'; +export { + pipe as Pp, + pipeBind1 as pb1, + pipeBind2 as pb2, + pipeBind3 as pb3, + pipeBind4 as pb4, + pipeBindV as pbV, +} from './pipe'; + export { QueryList, @@ -83,6 +92,7 @@ export { PublicFeature, defineComponent, defineDirective, + definePipe, }; export {createComponentRef, detectChanges, getHostElement, markDirty, renderComponent}; export {CssSelector} from './interfaces/projection'; diff --git a/packages/core/src/render3/interfaces/definition.ts b/packages/core/src/render3/interfaces/definition.ts index b93e500e19..ed95c7a986 100644 --- a/packages/core/src/render3/interfaces/definition.ts +++ b/packages/core/src/render3/interfaces/definition.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ +import {PipeTransform} from '../../change_detection/pipe_transform'; import {RendererType2} from '../../render/api'; import {Type} from '../../type'; import {resolveRendererType2} from '../../view/util'; @@ -23,6 +24,8 @@ export interface DirectiveType extends Type { ngDirectiveDef: DirectiveDef export const enum DirectiveDefFlags {ContentQuery = 0b10} +export interface PipeType extends Type { ngPipeDef: PipeDef; } + /** * `DirectiveDef` is a compiled version of the Directive used by the renderer instructions. */ @@ -109,6 +112,31 @@ export interface ComponentDef extends DirectiveDef { readonly rendererType: RendererType2|null; } +/** + * + */ +export interface PipeDef { + /** + * factory function used to create a new directive instance. + * + * NOTE: this property is short (1 char) because it is used in + * component templates which is sensitive to size. + */ + n: () => PipeTransform; + + /** + * Whether or not the pipe is pure. + * + * Pure pipes result only depends on the pipe input and not on internal + * state of the pipe. + */ + pure: boolean; + + /* The following are lifecycle hooks for this pipe */ + onDestroy: (() => void)|null; +} + + export interface DirectiveDefArgs { type: Type; factory: () => T | [T]; diff --git a/packages/core/src/render3/pipe.ts b/packages/core/src/render3/pipe.ts new file mode 100644 index 0000000000..a915a17c73 --- /dev/null +++ b/packages/core/src/render3/pipe.ts @@ -0,0 +1,91 @@ +/** + * @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 {PipeDef} from './interfaces/definition'; + +/** + * Create a pipe. + * + * @param index Pipe index where the pipe will be stored. + * @param pipeDef Pipe definition object for registering life cycle hooks. + * @param pipe A Pipe instance. + */ +export function pipe(index: number, pipeDef: PipeDef, pipe: T): void { + throw new Error('TODO: implement!'); +} + +/** + * Invokes a pure pipe with 4 arguments. + * + * This instruction acts as a guard to {@link PipeTransform#transform} invoking + * the pipe only when an input to the pipe changes. + * + * @param index Pipe index where the pipe was stored on creation. + * @param v1 1st argument to {@link PipeTransform#transform}. + */ +export function pipeBind1(index: number, v1: any): any { + throw new Error('TODO: implement!'); +} + +/** + * Invokes a pure pipe with 4 arguments. + * + * This instruction acts as a guard to {@link PipeTransform#transform} invoking + * the pipe only when an input to the pipe changes. + * + * @param index Pipe index where the pipe was stored on creation. + * @param v1 1st argument to {@link PipeTransform#transform}. + * @param v2 2nd argument to {@link PipeTransform#transform}. + */ +export function pipeBind2(index: number, v1: any, v2: any): any { + throw new Error('TODO: implement!'); +} + +/** + * Invokes a pure pipe with 4 arguments. + * + * This instruction acts as a guard to {@link PipeTransform#transform} invoking + * the pipe only when an input to the pipe changes. + * + * @param index Pipe index where the pipe was stored on creation. + * @param v1 1st argument to {@link PipeTransform#transform}. + * @param v2 2nd argument to {@link PipeTransform#transform}. + * @param v3 4rd argument to {@link PipeTransform#transform}. + */ +export function pipeBind3(index: number, v1: any, v2: any, v3: any): any { + throw new Error('TODO: implement!'); +} + +/** + * Invokes a pure pipe with 4 arguments. + * + * This instruction acts as a guard to {@link PipeTransform#transform} invoking + * the pipe only when an input to the pipe changes. + * + * @param index Pipe index where the pipe was stored on creation. + * @param v1 1st argument to {@link PipeTransform#transform}. + * @param v2 2nd argument to {@link PipeTransform#transform}. + * @param v3 3rd argument to {@link PipeTransform#transform}. + * @param v4 4th argument to {@link PipeTransform#transform}. + */ +export function pipeBind4(index: number, v1: any, v2: any, v3: any, v4: any): any { + throw new Error('TODO: implement!'); +} + +/** + * Invokes a pure pipe with variable number of arguments. + * + * This instruction acts as a guard to {@link PipeTransform#transform} invoking + * the pipe only when an input to the pipe changes. + * + * @param index Pipe index where the pipe was stored on creation. + * @param values Array of arguments to pass to {@link PipeTransform#transform} method. + */ +export function pipeBindV(index: number, values: any[]): any { + throw new Error('TODO: implement!'); +} \ No newline at end of file diff --git a/packages/core/test/render3/compiler_canonical_spec.ts b/packages/core/test/render3/compiler_canonical_spec.ts index 6bb2c9f289..1c45986a19 100644 --- a/packages/core/test/render3/compiler_canonical_spec.ts +++ b/packages/core/test/render3/compiler_canonical_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component, ContentChild, Directive, Injectable, Input, NgModule, Optional, QueryList, SimpleChanges, TemplateRef, Type, ViewChild, ViewContainerRef} from '../../src/core'; +import {Component, ContentChild, Directive, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, Type, ViewChild, ViewContainerRef} from '../../src/core'; import * as r3 from '../../src/render3/index'; import {containerEl, renderComponent, requestAnimationFrame, toHtml} from './render_util'; @@ -369,6 +369,69 @@ describe('compiler specification', () => { }); + xdescribe('pipes', () => { + @Pipe({ + name: 'myPipe', + }) + class MyPipe implements PipeTransform, + OnDestroy { + transform(value: any, ...args: any[]) { throw new Error('Method not implemented.'); } + ngOnDestroy(): void { throw new Error('Method not implemented.'); } + + // NORMATIVE + static ngPipeDef = r3.definePipe( + {type: MyPipe, factory: function MyPipe_Factory() { return new MyPipe(); }}); + // /NORMATIVE + } + + @Pipe({ + name: 'myPurePipe', + pure: true, + }) + class MyPurePipe implements PipeTransform { + transform(value: any, ...args: any[]) { throw new Error('Method not implemented.'); } + + // NORMATIVE + static ngPipeDef = r3.definePipe({ + type: MyPurePipe, + factory: function MyPurePipe_Factory() { return new MyPurePipe(); }, + pure: true + }); + // /NORMATIVE + } + + @Component({template: `{{name | myPipe:size | myPurePipe:size }}`}) + class MyApp { + name = 'World'; + size = 0; + + // NORMATIVE + static ngComponentDef = r3.defineComponent({ + type: MyApp, + tag: 'my-app', + factory: function MyApp_Factory() { return new MyApp(); }, + template: function MyApp_Template(ctx: MyApp, cm: boolean) { + if (cm) { + r3.Pp(0, MyPipe_ngPipeDef, MyPipe_ngPipeDef.n()); + r3.Pp(1, MyPurePipe_ngPipeDef, MyPurePipe_ngPipeDef.n()); + r3.T(2); + } + r3.t( + 2, r3.b1('', r3.pb2(1, r3.m(0).transform(ctx.name, ctx.size), ctx.size), '')); + } + }); + // /NORMATIVE + } + // NORMATIVE + const MyPipe_ngPipeDef = MyPipe.ngPipeDef; + const MyPurePipe_ngPipeDef = MyPurePipe.ngPipeDef; + // /NORMATIVE + + it('should render pipes', () => { + // TODO(misko): write a test once pipes runtime is implemented. + }); + }); + describe('local references', () => { // TODO(misko): currently disabled until local refs are working xit('should translate DOM structure', () => {