diff --git a/modules/angular2/angular2.dart b/modules/angular2/angular2.dart index 697c5230b8..d0c2e10178 100644 --- a/modules/angular2/angular2.dart +++ b/modules/angular2/angular2.dart @@ -7,6 +7,7 @@ library angular2; */ export 'package:angular2/core.dart' hide forwardRef, resolveForwardRef, ForwardRefFn; +export 'package:angular2/common.dart'; export 'package:angular2/profile.dart'; export 'package:angular2/lifecycle_hooks.dart'; export 'package:angular2/src/core/application_tokens.dart' diff --git a/modules/angular2/angular2.ts b/modules/angular2/angular2.ts index fb3ffaab93..c4358d1b21 100644 --- a/modules/angular2/angular2.ts +++ b/modules/angular2/angular2.ts @@ -1,3 +1,4 @@ +export * from './common'; export * from './core'; export * from './profile'; export * from './lifecycle_hooks'; diff --git a/modules/angular2/common.ts b/modules/angular2/common.ts new file mode 100644 index 0000000000..f23536805b --- /dev/null +++ b/modules/angular2/common.ts @@ -0,0 +1,51 @@ +import {CONST_EXPR, Type} from './src/core/facade/lang'; + +import {FORM_DIRECTIVES} from './src/core/forms'; +import {CORE_DIRECTIVES} from './src/core/directives'; +export * from './src/core/pipes'; +export * from './src/core/directives'; + +/** + * A collection of Angular core directives that are likely to be used in each and every Angular + * application. This includes core directives (e.g., NgIf and NgFor), and forms directives (e.g., + * NgModel). + * + * This collection can be used to quickly enumerate all the built-in directives in the `directives` + * property of the `@Component` or `@View` decorators. + * + * ### Example + * + * Instead of writing: + * + * ```typescript + * import {NgClass, NgIf, NgFor, NgSwitch, NgSwitchWhen, NgSwitchDefault, NgModel, NgForm} from + * 'angular2/angular2'; + * import {OtherDirective} from './myDirectives'; + * + * @Component({ + * selector: 'my-component', + * templateUrl: 'myComponent.html', + * directives: [NgClass, NgIf, NgFor, NgSwitch, NgSwitchWhen, NgSwitchDefault, NgModel, NgForm, + * OtherDirective] + * }) + * export class MyComponent { + * ... + * } + * ``` + * one could import all the common directives at once: + * + * ```typescript + * import {COMMON_DIRECTIVES} from 'angular2/angular2'; + * import {OtherDirective} from './myDirectives'; + * + * @Component({ + * selector: 'my-component', + * templateUrl: 'myComponent.html', + * directives: [COMMON_DIRECTIVES, OtherDirective] + * }) + * export class MyComponent { + * ... + * } + * ``` + */ +export const COMMON_DIRECTIVES: Type[][] = CONST_EXPR([CORE_DIRECTIVES, FORM_DIRECTIVES]); \ No newline at end of file diff --git a/modules/angular2/core.ts b/modules/angular2/core.ts index 10a89e462d..7a8efa80e3 100644 --- a/modules/angular2/core.ts +++ b/modules/angular2/core.ts @@ -19,3 +19,4 @@ export * from './src/core/directives'; export * from './src/core/forms'; export * from './src/core/debug'; export * from './src/core/change_detection'; +export * from './src/core/compiler/ambient'; diff --git a/modules/angular2/src/core/application_ref.ts b/modules/angular2/src/core/application_ref.ts index cdf5e3f84e..b74f089982 100644 --- a/modules/angular2/src/core/application_ref.ts +++ b/modules/angular2/src/core/application_ref.ts @@ -38,7 +38,6 @@ import {AppViewManager} from 'angular2/src/core/linker/view_manager'; import {AppViewManagerUtils} from 'angular2/src/core/linker/view_manager_utils'; import {AppViewListener} from 'angular2/src/core/linker/view_listener'; import {ProtoViewFactory} from './linker/proto_view_factory'; -import {DEFAULT_PIPES} from 'angular2/src/core/pipes'; import {ViewResolver} from './linker/view_resolver'; import {DirectiveResolver} from './linker/directive_resolver'; import {PipeResolver} from './linker/pipe_resolver'; @@ -48,6 +47,8 @@ import {AppViewManager_} from "./linker/view_manager"; import {Compiler_} from "./linker/compiler"; import {wtfLeave, wtfCreateScope, WtfScopeFn} from './profile/profile'; import {ChangeDetectorRef} from 'angular2/src/core/change_detection/change_detector_ref'; +import {AMBIENT_DIRECTIVES, AMBIENT_PIPES} from "angular2/src/core/compiler/ambient"; +import {COMMON_DIRECTIVES, COMMON_PIPES} from "angular2/common"; /** * Constructs the set of providers meant for use at the platform level. @@ -104,11 +105,12 @@ export function applicationCommonProviders(): Array { AppViewListener, ProtoViewFactory, ViewResolver, - DEFAULT_PIPES, provide(IterableDiffers, {useValue: defaultIterableDiffers}), provide(KeyValueDiffers, {useValue: defaultKeyValueDiffers}), DirectiveResolver, PipeResolver, + provide(AMBIENT_PIPES, {useValue: COMMON_PIPES, multi: true}), + provide(AMBIENT_DIRECTIVES, {useValue: COMMON_DIRECTIVES, multi: true}), provide(DynamicComponentLoader, {useClass: DynamicComponentLoader_}) ]; } diff --git a/modules/angular2/src/core/compiler/ambient.ts b/modules/angular2/src/core/compiler/ambient.ts new file mode 100644 index 0000000000..d0a1de4ebf --- /dev/null +++ b/modules/angular2/src/core/compiler/ambient.ts @@ -0,0 +1,53 @@ +import {OpaqueToken} from "angular2/src/core/di"; +import {CONST_EXPR} from "angular2/src/core/facade/lang"; + +/** + * A token that can be provided when bootstraping an application to make an array of directives + * available in every component of the application. + * + * ### Example + * + * ```typescript + * import {AMBIENT_DIRECTIVES} from 'angular2/angular2'; + * import {OtherDirective} from './myDirectives'; + * + * @Component({ + * selector: 'my-component', + * template: ` + * + * + * ` + * }) + * export class MyComponent { + * ... + * } + * + * bootstrap(MyComponent, [provide(AMBIENT_DIRECTIVES, {useValue: [OtherDirective], multi:true})]); + * ``` + */ +export const AMBIENT_DIRECTIVES: OpaqueToken = CONST_EXPR(new OpaqueToken("Ambient Directives")); + +/** + * A token that can be provided when bootstraping an application to make an array of pipes + * available in every component of the application. + * + * ### Example + * + * ```typescript + * import {AMBIENT_PIPES} from 'angular2/angular2'; + * import {OtherPipe} from './myPipe'; + * + * @Component({ + * selector: 'my-component', + * template: ` + * {{123 | other-pipe}} + * ` + * }) + * export class MyComponent { + * ... + * } + * + * bootstrap(MyComponent, [provide(AMBIENT_PIPES, {useValue: [OtherPipe], multi:true})]); + * ``` + */ +export const AMBIENT_PIPES: OpaqueToken = CONST_EXPR(new OpaqueToken("Ambient Pipes")); diff --git a/modules/angular2/src/core/compiler/compiler.ts b/modules/angular2/src/core/compiler/compiler.ts index 09ac936f90..a87cfec5f5 100644 --- a/modules/angular2/src/core/compiler/compiler.ts +++ b/modules/angular2/src/core/compiler/compiler.ts @@ -6,6 +6,7 @@ export { CompileTemplateMetadata } from './directive_metadata'; export {SourceModule, SourceWithImports} from './source_module'; +export {AMBIENT_DIRECTIVES, AMBIENT_PIPES} from './ambient'; import {assertionsEnabled, Type} from 'angular2/src/core/facade/lang'; import {provide, Provider} from 'angular2/src/core/di'; diff --git a/modules/angular2/src/core/compiler/runtime_metadata.ts b/modules/angular2/src/core/compiler/runtime_metadata.ts index 2dfbf27484..9baa736be9 100644 --- a/modules/angular2/src/core/compiler/runtime_metadata.ts +++ b/modules/angular2/src/core/compiler/runtime_metadata.ts @@ -17,14 +17,16 @@ import {ViewMetadata} from 'angular2/src/core/metadata/view'; import {hasLifecycleHook} from 'angular2/src/core/linker/directive_lifecycle_reflector'; import {LifecycleHooks, LIFECYCLE_HOOKS_VALUES} from 'angular2/src/core/linker/interfaces'; import {reflector} from 'angular2/src/core/reflection/reflection'; -import {Injectable} from 'angular2/src/core/di'; +import {Injectable, Inject, Optional} from 'angular2/src/core/di'; +import {AMBIENT_DIRECTIVES} from 'angular2/src/core/compiler/ambient'; import {MODULE_SUFFIX} from './util'; @Injectable() export class RuntimeMetadataResolver { private _cache = new Map(); - constructor(private _directiveResolver: DirectiveResolver, private _viewResolver: ViewResolver) {} + constructor(private _directiveResolver: DirectiveResolver, private _viewResolver: ViewResolver, + @Optional() @Inject(AMBIENT_DIRECTIVES) private _ambientDirectives: Type[]) {} getMetadata(directiveType: Type): cpl.CompileDirectiveMetadata { var meta = this._cache.get(directiveType); @@ -68,7 +70,7 @@ export class RuntimeMetadataResolver { getViewDirectivesMetadata(component: Type): cpl.CompileDirectiveMetadata[] { var view = this._viewResolver.resolve(component); - var directives = flattenDirectives(view); + var directives = flattenDirectives(view, this._ambientDirectives); for (var i = 0; i < directives.length; i++) { if (!isValidDirective(directives[i])) { throw new BaseException( @@ -86,18 +88,22 @@ function removeDuplicates(items: any[]): any[] { return MapWrapper.keys(m); } -function flattenDirectives(view: ViewMetadata): Type[] { - if (isBlank(view.directives)) return []; - var directives = []; - flattenList(view.directives, directives); +function flattenDirectives(view: ViewMetadata, ambientDirectives: any[]): Type[] { + let directives = []; + if (isPresent(ambientDirectives)) { + flattenArray(ambientDirectives, directives); + } + if (isPresent(view.directives)) { + flattenArray(view.directives, directives); + } return directives; } -function flattenList(tree: any[], out: Array): void { +function flattenArray(tree: any[], out: Array): void { for (var i = 0; i < tree.length; i++) { var item = resolveForwardRef(tree[i]); if (isArray(item)) { - flattenList(item, out); + flattenArray(item, out); } else { out.push(item); } diff --git a/modules/angular2/src/core/linker/proto_view_factory.ts b/modules/angular2/src/core/linker/proto_view_factory.ts index 8e7bd33471..c6ab82fe81 100644 --- a/modules/angular2/src/core/linker/proto_view_factory.ts +++ b/modules/angular2/src/core/linker/proto_view_factory.ts @@ -1,9 +1,8 @@ -import {ListWrapper} from 'angular2/src/core/facade/collection'; import {isPresent, isBlank, Type, isArray, isNumber} from 'angular2/src/core/facade/lang'; import {RenderProtoViewRef} from 'angular2/src/core/render/api'; -import {Injectable, Provider, resolveForwardRef, Inject} from 'angular2/src/core/di'; +import {Optional, Injectable, Provider, resolveForwardRef, Inject} from 'angular2/src/core/di'; import {PipeProvider} from '../pipes/pipe_provider'; import {ProtoPipes} from '../pipes/pipes'; @@ -15,7 +14,7 @@ import {DirectiveResolver} from './directive_resolver'; import {ViewResolver} from './view_resolver'; import {PipeResolver} from './pipe_resolver'; import {ViewMetadata} from '../metadata/view'; -import {DEFAULT_PIPES_TOKEN} from 'angular2/src/core/pipes'; +import {AMBIENT_PIPES} from 'angular2/src/core/compiler/ambient'; import { visitAllCommands, @@ -38,15 +37,10 @@ import {APP_ID} from 'angular2/src/core/application_tokens'; @Injectable() export class ProtoViewFactory { private _cache: Map = new Map(); - private _defaultPipes: Type[]; - private _appId: string; - - constructor(private _renderer: Renderer, @Inject(DEFAULT_PIPES_TOKEN) defaultPipes: Type[], + constructor(private _renderer: Renderer, + @Optional() @Inject(AMBIENT_PIPES) private _ambientPipes: Array, private _directiveResolver: DirectiveResolver, private _viewResolver: ViewResolver, - private _pipeResolver: PipeResolver, @Inject(APP_ID) appId: string) { - this._defaultPipes = defaultPipes; - this._appId = appId; - } + private _pipeResolver: PipeResolver, @Inject(APP_ID) private _appId: string) {} clearCache() { this._cache.clear(); } @@ -118,9 +112,13 @@ export class ProtoViewFactory { } private _flattenPipes(view: ViewMetadata): any[] { - if (isBlank(view.pipes)) return this._defaultPipes; - var pipes = ListWrapper.clone(this._defaultPipes); - _flattenList(view.pipes, pipes); + let pipes = []; + if (isPresent(this._ambientPipes)) { + _flattenArray(this._ambientPipes, pipes); + } + if (isPresent(view.pipes)) { + _flattenArray(view.pipes, pipes); + } return pipes; } } @@ -313,11 +311,11 @@ function arrayToMap(arr: string[], inverse: boolean): Map { return result; } -function _flattenList(tree: any[], out: Array): void { +function _flattenArray(tree: any[], out: Array): void { for (var i = 0; i < tree.length; i++) { var item = resolveForwardRef(tree[i]); if (isArray(item)) { - _flattenList(item, out); + _flattenArray(item, out); } else { out.push(item); } diff --git a/modules/angular2/src/core/pipes.ts b/modules/angular2/src/core/pipes.ts index 8487d3ef89..a1dea1f563 100644 --- a/modules/angular2/src/core/pipes.ts +++ b/modules/angular2/src/core/pipes.ts @@ -3,12 +3,31 @@ * @description * This module provides a set of common Pipes. */ +import {AsyncPipe} from './pipes/async_pipe'; +import {UpperCasePipe} from './pipes/uppercase_pipe'; +import {LowerCasePipe} from './pipes/lowercase_pipe'; +import {JsonPipe} from './pipes/json_pipe'; +import {SlicePipe} from './pipes/slice_pipe'; +import {DatePipe} from './pipes/date_pipe'; +import {DecimalPipe, PercentPipe, CurrencyPipe} from './pipes/number_pipe'; +import {CONST_EXPR} from 'angular2/src/core/facade/lang'; export {AsyncPipe} from './pipes/async_pipe'; export {DatePipe} from './pipes/date_pipe'; -export {DEFAULT_PIPES, DEFAULT_PIPES_TOKEN} from './pipes/default_pipes'; export {JsonPipe} from './pipes/json_pipe'; export {SlicePipe} from './pipes/slice_pipe'; export {LowerCasePipe} from './pipes/lowercase_pipe'; export {NumberPipe, DecimalPipe, PercentPipe, CurrencyPipe} from './pipes/number_pipe'; export {UpperCasePipe} from './pipes/uppercase_pipe'; + +export const COMMON_PIPES = CONST_EXPR([ + AsyncPipe, + UpperCasePipe, + LowerCasePipe, + JsonPipe, + SlicePipe, + DecimalPipe, + PercentPipe, + CurrencyPipe, + DatePipe +]); diff --git a/modules/angular2/src/core/pipes/default_pipes.ts b/modules/angular2/src/core/pipes/default_pipes.ts deleted file mode 100644 index 0c9179490c..0000000000 --- a/modules/angular2/src/core/pipes/default_pipes.ts +++ /dev/null @@ -1,27 +0,0 @@ -import {AsyncPipe} from './async_pipe'; -import {UpperCasePipe} from './uppercase_pipe'; -import {LowerCasePipe} from './lowercase_pipe'; -import {JsonPipe} from './json_pipe'; -import {SlicePipe} from './slice_pipe'; -import {DatePipe} from './date_pipe'; -import {DecimalPipe, PercentPipe, CurrencyPipe} from './number_pipe'; - -import {CONST_EXPR} from 'angular2/src/core/facade/lang'; -import {Provider, OpaqueToken} from 'angular2/src/core/di'; - -const DEFAULT_PIPES_LIST = CONST_EXPR([ - AsyncPipe, - UpperCasePipe, - LowerCasePipe, - JsonPipe, - SlicePipe, - DecimalPipe, - PercentPipe, - CurrencyPipe, - DatePipe -]); - -export const DEFAULT_PIPES_TOKEN: OpaqueToken = CONST_EXPR(new OpaqueToken("Default Pipes")); - -export const DEFAULT_PIPES: Provider = - CONST_EXPR(new Provider(DEFAULT_PIPES_TOKEN, {useValue: DEFAULT_PIPES_LIST})); diff --git a/modules/angular2/src/testing/test_injector.ts b/modules/angular2/src/testing/test_injector.ts index ce7494634a..eb3554b524 100644 --- a/modules/angular2/src/testing/test_injector.ts +++ b/modules/angular2/src/testing/test_injector.ts @@ -1,5 +1,4 @@ import {provide, Provider} from 'angular2/src/core/di'; -import {DEFAULT_PIPES} from 'angular2/src/core/pipes'; import {AnimationBuilder} from 'angular2/src/animate/animation_builder'; import {MockAnimationBuilder} from 'angular2/src/mock/animation_builder_mock'; @@ -107,7 +106,6 @@ function _getAppBindings() { ProtoViewFactory, provide(DirectiveResolver, {useClass: MockDirectiveResolver}), provide(ViewResolver, {useClass: MockViewResolver}), - DEFAULT_PIPES, provide(IterableDiffers, {useValue: defaultIterableDiffers}), provide(KeyValueDiffers, {useValue: defaultKeyValueDiffers}), Log, diff --git a/modules/angular2/src/web_workers/ui/di_bindings.ts b/modules/angular2/src/web_workers/ui/di_bindings.ts index 24351a3203..9ce2bf54d1 100644 --- a/modules/angular2/src/web_workers/ui/di_bindings.ts +++ b/modules/angular2/src/web_workers/ui/di_bindings.ts @@ -1,7 +1,6 @@ // TODO (jteplitz602): This whole file is nearly identical to core/application.ts. // There should be a way to refactor application so that this file is unnecessary. See #3277 import {Injector, provide, Provider} from "angular2/src/core/di"; -import {DEFAULT_PIPES} from 'angular2/src/core/pipes'; import {AnimationBuilder} from 'angular2/src/animate/animation_builder'; import {BrowserDetails} from 'angular2/src/animate/browser_details'; import {Reflector, reflector} from 'angular2/src/core/reflection/reflection'; @@ -64,6 +63,8 @@ import { ClientMessageBrokerFactory, ClientMessageBrokerFactory_ } from 'angular2/src/web_workers/shared/client_message_broker'; +import {AMBIENT_DIRECTIVES, AMBIENT_PIPES} from "angular2/src/core/compiler/ambient"; +import {COMMON_DIRECTIVES, COMMON_PIPES} from "angular2/common"; var _rootInjector: Injector; @@ -96,7 +97,8 @@ function _injectorProviders(): any[] { AppViewListener, ProtoViewFactory, ViewResolver, - DEFAULT_PIPES, + provide(AMBIENT_PIPES, {useValue: COMMON_PIPES, multi: true}), + provide(AMBIENT_DIRECTIVES, {useValue: COMMON_DIRECTIVES, multi: true}), DirectiveResolver, Parser, Lexer, diff --git a/modules/angular2/test/core/compiler/runtime_metadata_spec.ts b/modules/angular2/test/core/compiler/runtime_metadata_spec.ts index a767b8474d..17965066c8 100644 --- a/modules/angular2/test/core/compiler/runtime_metadata_spec.ts +++ b/modules/angular2/test/core/compiler/runtime_metadata_spec.ts @@ -10,7 +10,7 @@ import { afterEach, AsyncTestCompleter, inject, - beforeEachBindings + beforeEachProviders } from 'angular2/testing_internal'; import {stringify} from 'angular2/src/core/facade/lang'; @@ -30,15 +30,17 @@ import { AfterContentChecked, AfterViewInit, AfterViewChecked, - SimpleChange + SimpleChange, + provide } from 'angular2/core'; import {TEST_PROVIDERS} from './test_bindings'; import {MODULE_SUFFIX, IS_DART} from 'angular2/src/core/compiler/util'; +import {AMBIENT_DIRECTIVES} from 'angular2/src/core/compiler/ambient'; export function main() { describe('RuntimeMetadataResolver', () => { - beforeEachBindings(() => TEST_PROVIDERS); + beforeEachProviders(() => TEST_PROVIDERS); describe('getMetadata', () => { it('should read metadata', @@ -82,6 +84,19 @@ export function main() { expect(resolver.getViewDirectivesMetadata(ComponentWithEverything)) .toEqual([resolver.getMetadata(DirectiveWithoutModuleId)]); })); + + describe("ambient directives", () => { + beforeEachProviders(() => [provide(AMBIENT_DIRECTIVES, {useValue: [ADirective]})]); + + it('should include ambient directives when available', + inject([RuntimeMetadataResolver], (resolver: RuntimeMetadataResolver) => { + expect(resolver.getViewDirectivesMetadata(ComponentWithEverything)) + .toEqual([ + resolver.getMetadata(ADirective), + resolver.getMetadata(DirectiveWithoutModuleId) + ]); + })); + }); }); }); @@ -89,6 +104,10 @@ export function main() { +@Directive({selector: 'a-directive'}) +class ADirective { +} + @Directive({selector: 'someSelector'}) class DirectiveWithoutModuleId { } diff --git a/modules/angular2/test/core/linker/integration_spec.ts b/modules/angular2/test/core/linker/integration_spec.ts index e26331f51b..e2bc3ca673 100644 --- a/modules/angular2/test/core/linker/integration_spec.ts +++ b/modules/angular2/test/core/linker/integration_spec.ts @@ -59,6 +59,8 @@ import { NgFor } from 'angular2/core'; +import {AsyncPipe} from 'angular2/common'; + import { PipeTransform, ChangeDetectorRef, @@ -1867,8 +1869,12 @@ class PushCmpWithRef { propagate() { this.ref.markForCheck(); } } -@Component({selector: 'push-cmp-with-async', changeDetection: ChangeDetectionStrategy.OnPush}) -@View({template: '{{field | async}}'}) +@Component({ + selector: 'push-cmp-with-async', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '{{field | async}}', + pipes: [AsyncPipe] +}) @Injectable() class PushCmpWithAsyncPipe { numberOfChecks: number = 0; diff --git a/modules/angular2/test/public_api_spec.ts b/modules/angular2/test/public_api_spec.ts index 3594e58494..9d13af6e09 100644 --- a/modules/angular2/test/public_api_spec.ts +++ b/modules/angular2/test/public_api_spec.ts @@ -147,6 +147,8 @@ var NG_ALL = [ 'By#directive()', 'By', 'CORE_DIRECTIVES', + 'COMMON_DIRECTIVES', + 'AMBIENT_DIRECTIVES:js', 'ChangeDetectionError', 'ChangeDetectionError.context', 'ChangeDetectionError.location', @@ -398,8 +400,8 @@ var NG_ALL = [ 'CyclicDependencyError.message', 'CyclicDependencyError.message=', 'CyclicDependencyError.stackTrace', - 'DEFAULT_PIPES', - 'DEFAULT_PIPES_TOKEN', + 'COMMON_PIPES', + 'AMBIENT_PIPES:js', 'DOCUMENT', 'DatePipe', 'DatePipe.supports()',