diff --git a/COMMITTER.md b/COMMITTER.md new file mode 100644 index 000000000000..851237eb6ebf --- /dev/null +++ b/COMMITTER.md @@ -0,0 +1,31 @@ +# Pushing changes into the Angular 2 tree + +Please see [Using git with Angular repositories](https://docs.google.com/document/d/1h8nijFSaa1jG_UE8v4WP7glh5qOUXnYtAtJh_gwOQHI/edit) +for details about how we maintain a linear commit history, and the rules for committing. + +As a contributor, just read the instructions in [CONTRIBUTING.md](CONTRIBUTING.md) and send a pull request. +Someone with committer access will do the rest. + +## The `PR: merge` label and `presubmit-*` branches + +We have automated the process for merging pull requests into master. Our goal is to minimize the disruption for +Angular committers and also prevent breakages on master. + +When a PR is ready to merge, a project member in the CoreTeamMember list (see below) can add the special label, +`PR: merge`. +A robot running as [mary-poppins](https://github.com/mary-poppins) +is notified that the label was added by an authorized person, +and will create a new branch in the angular project, using the convention `presubmit-{username}-pr-{number}`. + +(Note: if the automation fails, committers can instead push the commits to a branch following this naming scheme.) + +When a Travis build succeeds for a presubmit branch named following the convention, +Travis will re-base the commits, merge to master, and close the PR automatically. + +Finally, after merge `mary-poppins` removes the presubmit branch. + +## Administration + +The list of users who can trigger a merge by adding the label is stored in our appengine app datastore. +Edit the contents of the [CoreTeamMember Table]( +https://console.developers.google.com/project/angular2-automation/datastore/query?queryType=KindQuery&namespace=&kind=CoreTeamMember) diff --git a/TRIAGE_AND_LABELS.md b/TRIAGE_AND_LABELS.md index b395e4d1eaa5..22280c0c1f24 100644 --- a/TRIAGE_AND_LABELS.md +++ b/TRIAGE_AND_LABELS.md @@ -44,13 +44,13 @@ closing or reviewing PRs is a top priority ahead of other ongoing work. Every triaged PR must have a `pr_action` label assigned to it and an assignee: +* `pr_action: review` -- work is complete and comment is needed from the assignee. * `pr_action: cleanup` -- more work is needed from the current assignee. * `pr_action: discuss` -- discussion is needed, to be led by the current assignee. -* `pr_action: merge` -- OK to merge this as soon as tests are green, `pr_state: LGTM`, and `CLA: -yes` are true. assignee (or anyone else) can merge. -* `pr_action: review` -- work is complete and comment is needed from the assignee. +* `pr_action: merge` -- the PR should be merged. Add this to a PR when you would like to + trigger automatic merging following a successful build. This is described in [COMMITTER.md](COMMITTER.md). -In addition, PRs can have the following states: +In addition, PRs can have the following states: * `pr_state: LGTM` -- PR may have outstanding changes but does not require further review. * `pr_state: WIP` -- PR is experimental or rapidly changing. Not ready for review or triage. diff --git a/docs/angular.io-package/templates/enum.template.html b/docs/angular.io-package/templates/enum.template.html new file mode 100644 index 000000000000..9c59159b296a --- /dev/null +++ b/docs/angular.io-package/templates/enum.template.html @@ -0,0 +1 @@ +{% extends 'class.template.html' -%} \ No newline at end of file diff --git a/docs/public-docs-package/index.js b/docs/public-docs-package/index.js index 8b17e5b33700..08e42b77e94a 100644 --- a/docs/public-docs-package/index.js +++ b/docs/public-docs-package/index.js @@ -10,7 +10,7 @@ module.exports = new Package('angular-v2-public-docs', [basePackage]) 'angular2/core.ts', 'angular2/di.ts', 'angular2/directives.ts', - 'angular2/http.ts', + 'http/http.ts', 'angular2/forms.ts', 'angular2/router.ts', 'angular2/test.ts', diff --git a/docs/typescript-definition-package/index.js b/docs/typescript-definition-package/index.js index 532cfe5d3d1e..0763721606fb 100644 --- a/docs/typescript-definition-package/index.js +++ b/docs/typescript-definition-package/index.js @@ -39,23 +39,35 @@ module.exports = new Package('angular-v2-docs', [jsdocPackage, nunjucksPackage, readFilesProcessor.basePath = path.resolve(__dirname, '../..'); readTypeScriptModules.sourceFiles = [ 'angular2/angular2.ts', - 'angular2/router.ts' + 'angular2/router.ts', + 'http/http.ts' ]; readTypeScriptModules.basePath = path.resolve(path.resolve(__dirname, '../../modules')); - + createTypeDefinitionFile.typeDefinitions = [ { id: 'angular2/angular2', + references: ['../es6-promise/es6-promise.d.ts', '../rx/rx.d.ts'], + namespace: 'ng', modules: { 'angular2/angular2': 'angular2/angular2', } }, { id: 'angular2/router', + namespace: 'ngRouter', + references: ['../es6-promise/es6-promise.d.ts'], modules: { 'angular2/router': 'angular2/router' } - } + }, + { + id: 'http/http', + namespace: 'ngHttp', + modules: { + 'http/http':'http/http' + } + } ]; }) diff --git a/docs/typescript-definition-package/processors/createTypeDefinitionFile.js b/docs/typescript-definition-package/processors/createTypeDefinitionFile.js index f3bc002caa65..6f9bd7392a29 100644 --- a/docs/typescript-definition-package/processors/createTypeDefinitionFile.js +++ b/docs/typescript-definition-package/processors/createTypeDefinitionFile.js @@ -33,7 +33,12 @@ module.exports = function createTypeDefinitionFile(log) { // A type definition may include a number of top level modules // And those modules could be aliased (such as 'angular2/angular2.api' -> 'angular2/angular2') moduleDocs: _.transform(def.modules, function(moduleDocs, id, alias) { - moduleDocs[id] = { id: alias, doc: null }; + moduleDocs[id] = { + id: alias, + doc: null, + namespace: def.namespace, + references: def.references + }; }) }; }); @@ -63,7 +68,7 @@ module.exports = function createTypeDefinitionFile(log) { // Convert this class to an interface with no constructor exportDoc.docType = 'interface'; exportDoc.constructorDoc = null; - + if (exportDoc.heritage) { // convert the heritage since interfaces use `extends` not `implements` exportDoc.heritage = exportDoc.heritage.replace('implements', 'extends'); diff --git a/docs/typescript-definition-package/templates/angular2/angular2.d.ts.template.html b/docs/typescript-definition-package/templates/angular2/angular2.d.ts.template.html index a19f0c41e0b5..ce9778bfdfa0 100644 --- a/docs/typescript-definition-package/templates/angular2/angular2.d.ts.template.html +++ b/docs/typescript-definition-package/templates/angular2/angular2.d.ts.template.html @@ -1,10 +1,5 @@ {% extends '../type-definition.template.html' %} {% block staticDeclarations %} -// Angular depends transitively on these libraries. -// If you don't have them installed you can run -// $ tsd query es6-promise rx rx-lite --action install --save -/// -/// interface List extends Array {} interface Map {} diff --git a/docs/typescript-definition-package/templates/type-definition.template.html b/docs/typescript-definition-package/templates/type-definition.template.html index 33cb3c961bef..b1667ee54a95 100644 --- a/docs/typescript-definition-package/templates/type-definition.template.html +++ b/docs/typescript-definition-package/templates/type-definition.template.html @@ -28,10 +28,18 @@ // Please do not create manual edits or send pull requests // modifying this file. // *********************************************************** +{% for alias, module in doc.moduleDocs %} +{%- if module.references.length %} +// {$ alias $} depends transitively on these libraries. +// If you don't have them installed you can install them using TSD +// https://github.com/DefinitelyTyped/tsd +{%- endif %} +{% for reference in module.references %} +///{% endfor %}{% endfor %} {% block staticDeclarations %}{% endblock %} {% for alias, module in doc.moduleDocs %} {$ commentBlock(module.doc, 1) $} -declare module ng { +declare module {$ module.namespace $} { {%- for export in module.doc.exports -%} {%- if export.content -%} @@ -52,7 +60,8 @@ {%- elif export.docType == 'enum' %} { {%- for member in export.members %} - {$ member $}{% if not loop.last %}, + {$ commentBlock(member, 5) $} + {$ member.name $}{% if not loop.last %}, {%- endif -%} {%- endfor %} } @@ -66,7 +75,7 @@ } declare module "{$ alias $}" { - export = ng; + export = {$ module.namespace $}; } {% endfor %} diff --git a/docs/typescript-package/processors/readTypeScriptModules.js b/docs/typescript-package/processors/readTypeScriptModules.js index cadb1d8fbfc6..4574cf83b16a 100644 --- a/docs/typescript-package/processors/readTypeScriptModules.js +++ b/docs/typescript-package/processors/readTypeScriptModules.js @@ -63,10 +63,11 @@ module.exports = function readTypeScriptModules(tsParser, modules, getFileInfo, var exportDoc = createExportDoc(exportSymbol.name, resolvedExport, moduleDoc, basePath, parseInfo.typeChecker); log.debug('>>>> EXPORT: ' + exportDoc.name + ' (' + exportDoc.docType + ') from ' + moduleDoc.id); + exportDoc.members = []; + // Generate docs for each of the export's members if (resolvedExport.flags & ts.SymbolFlags.HasMembers) { - exportDoc.members = []; for(var memberName in resolvedExport.members) { // FIXME(alexeagle): why do generic type params appear in members? if (memberName === 'T') { @@ -91,21 +92,24 @@ module.exports = function readTypeScriptModules(tsParser, modules, getFileInfo, exportDoc.newMember = memberDoc; } } + } - if (sortClassMembers) { - exportDoc.members.sort(function(a, b) { - if (a.name > b.name) return 1; - if (a.name < b.name) return -1; - return 0; - }); + if (exportDoc.docType === 'enum') { + for(var memberName in resolvedExport.exports) { + log.silly('>>>>>> member: ' + memberName + ' from ' + exportDoc.id + ' in ' + moduleDoc.id); + var memberSymbol = resolvedExport.exports[memberName]; + var memberDoc = createMemberDoc(memberSymbol, exportDoc, basePath, parseInfo.typeChecker); + docs.push(memberDoc); + exportDoc.members.push(memberDoc); } } - if (exportDoc.docType == 'enum') { - exportDoc.members = []; - for (var etype in resolvedExport.exports) { - exportDoc.members.push(etype); - } + if (sortClassMembers) { + exportDoc.members.sort(function(a, b) { + if (a.name > b.name) return 1; + if (a.name < b.name) return -1; + return 0; + }); } // Add this export doc to its module doc diff --git a/gulpfile.js b/gulpfile.js index 83ed74977014..b979007a35b3 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -25,6 +25,7 @@ var pubget = require('./tools/build/pubget'); var linknodemodules = require('./tools/build/linknodemodules'); var pubbuild = require('./tools/build/pubbuild'); var dartanalyzer = require('./tools/build/dartanalyzer'); +var dartapidocs = require('./tools/build/dartapidocs'); var jsserve = require('./tools/build/jsserve'); var pubserve = require('./tools/build/pubserve'); var karma = require('karma'); @@ -250,6 +251,11 @@ gulp.task('build/analyze.ddc.dart', dartanalyzer(gulp, gulpPlugins, { use_ddc: true })); +gulp.task('build/check.apidocs.dart', dartapidocs(gulp, gulpPlugins, { + dest: CONFIG.dest.dart, + command: DART_SDK.DARTDOCGEN +})); + // ------------ // pubbuild // WARNING: this task is very slow (~15m as of July 2015) @@ -615,7 +621,7 @@ gulp.task('test.unit.dart', function (done) { return; } - watch('modules/angular2/**', { ignoreInitial: true }, [ + watch(['modules/angular2/**', 'modules/http/**'], { ignoreInitial: true }, [ '!build/tree.dart', '!test.unit.dart/karma-run' ]); @@ -655,7 +661,7 @@ gulp.task('test.unit.dart/ci', function (done) { gulp.task('test.unit.cjs/ci', function(done) { - runJasmineTests(['dist/js/cjs/{angular2,benchpress}/test/**/*_spec.js'], done); + runJasmineTests(['dist/js/cjs/{angular2,benchpress,http}/test/**/*_spec.js'], done); }); @@ -761,7 +767,7 @@ gulp.task('!pre.test.typings', ['docs/typings'], function() { // ----------------- gulp.task('test.typings', ['!pre.test.typings'], function() { - return gulp.src(['typing_spec/*.ts', 'dist/docs/typings/angular2/*.d.ts']) + return gulp.src(['typing_spec/*.ts', 'dist/docs/typings/angular2/*.d.ts', 'dist/docs/typings/http.d.ts']) .pipe(tsc({target: 'ES5', module: 'commonjs', experimentalDecorators: true, noImplicitAny: true, @@ -843,6 +849,7 @@ gulp.task('build.dart', function(done) { 'build/packages.dart', 'build/pubspec.dart', 'build/analyze.dart', + 'build/check.apidocs.dart', 'build.dart.material.css', sequenceComplete(done) ); @@ -941,9 +948,12 @@ var bundleConfig = { meta: { // auto-detection fails to detect properly here - https://github.com/systemjs/builder/issues/123 'rx': { - format: 'cjs' - } + format: 'cjs' + }, + 'angular2/src/router/route_definition': { + format: 'es6' } + } }; // production build @@ -954,6 +964,16 @@ gulp.task('!bundle.js.prod', ['build.js.prod'], function() { './dist/build/angular2.js', { sourceMaps: true + }). + then(function(){ + return bundler.bundle( + bundleConfig, + 'http/http', + './dist/build/http.js', + { + sourceMaps: true + } + ) }); }); @@ -966,6 +986,17 @@ gulp.task('!bundle.js.min', ['build.js.prod'], function() { { sourceMaps: true, minify: true + }). + then(function(){ + return bundler.bundle( + bundleConfig, + 'http/http', + './dist/build/http.min.js', + { + sourceMaps: true, + minify: true + } + ) }); }); @@ -980,7 +1011,14 @@ gulp.task('!bundle.js.dev', ['build.js.dev'], function() { devBundleConfig, 'angular2/angular2', './dist/build/angular2.dev.js', - { sourceMaps: true }); + { sourceMaps: true }). + then(function() { + return bundler.bundle( + devBundleConfig, + 'http/http', + './dist/build/http.dev.js', + { sourceMaps: true }); + }); }); gulp.task('!router.bundle.js.dev', ['build.js.dev'], function() { @@ -1025,25 +1063,41 @@ gulp.task('!bundle.js.sfx.dev', ['build.js.dev'], function() { 'angular2/angular2_sfx', './dist/build/angular2.sfx.dev.js', { sourceMaps: true }, - /* self-executing */ true); + /* self-executing */ true). + then(function() { + return bundler.bundle( + devBundleConfig, + 'http/http_sfx', + './dist/build/http.sfx.dev.js', + { sourceMaps: true }, + true) + }); }); gulp.task('!bundle.js.prod.deps', ['!bundle.js.prod'], function() { - return bundler.modify( + return merge2(bundler.modify( ['node_modules/zone.js/dist/zone-microtask.js', 'node_modules/reflect-metadata/Reflect.js', 'dist/build/angular2.js'], 'angular2.js' - ).pipe(gulp.dest('dist/js/bundle')); + ), + bundler.modify( + ['node_modules/reflect-metadata/Reflect.js', 'node_modules/rx/dist/rx.lite.js', 'dist/build/http.js'], + 'http.js' + )).pipe(gulp.dest('dist/js/bundle')); }); gulp.task('!bundle.js.min.deps', ['!bundle.js.min'], function() { - return bundler.modify( + return merge2(bundler.modify( ['node_modules/zone.js/dist/zone-microtask.min.js', 'node_modules/reflect-metadata/Reflect.js', 'dist/build/angular2.min.js'], 'angular2.min.js' - ) - .pipe(uglify()) - .pipe(gulp.dest('dist/js/bundle')); + ), + bundler.modify( + ['node_modules/reflect-metadata/Reflect.js', 'node_modules/rx/dist/rx.lite.js','dist/build/http.min.js'], + 'http.min.js' + )) + .pipe(uglify()) + .pipe(gulp.dest('dist/js/bundle')); }); var JS_DEV_DEPS = [ @@ -1068,17 +1122,26 @@ function insertRXLicense(source) { } gulp.task('!bundle.js.dev.deps', ['!bundle.js.dev'], function() { - return bundler.modify(JS_DEV_DEPS.concat(['dist/build/angular2.dev.js']), 'angular2.dev.js') - .pipe(insert.transform(insertRXLicense)) - .pipe(insert.append('\nSystem.config({"paths":{"*":"*.js","angular2/*":"angular2/*"}});\n')) - .pipe(gulp.dest('dist/js/bundle')); + return merge2( + bundler.modify( + JS_DEV_DEPS.concat(['dist/build/angular2.dev.js']), + 'angular2.dev.js') + .pipe(insert.transform(insertRXLicense)) + .pipe(insert.append('\nSystem.config({"paths":{"*":"*.js","angular2/*":"angular2/*"}});\n')) + .pipe(gulp.dest('dist/js/bundle')), + bundler.modify( + ['dist/build/http.dev.js'], 'http.dev.js') + .pipe(gulp.dest('dist/js/bundle'))); }); gulp.task('!bundle.js.sfx.dev.deps', ['!bundle.js.sfx.dev'], function() { - return bundler.modify(JS_DEV_DEPS.concat(['dist/build/angular2.sfx.dev.js']), + return merge2( + bundler.modify(JS_DEV_DEPS.concat(['dist/build/angular2.sfx.dev.js']), 'angular2.sfx.dev.js') - .pipe(insert.transform(insertRXLicense)) - .pipe(gulp.dest('dist/js/bundle')); + .pipe(gulp.dest('dist/js/bundle')), + bundler.modify(['dist/build/http.sfx.dev.js'], + 'http.sfx.dev.js') + .pipe(gulp.dest('dist/js/bundle'))); }); gulp.task('bundles.js', [ diff --git a/karma-dart.conf.js b/karma-dart.conf.js index cc84598efc60..e07c7212918a 100644 --- a/karma-dart.conf.js +++ b/karma-dart.conf.js @@ -45,6 +45,7 @@ module.exports = function(config) { // Local dependencies, transpiled from the source. '/packages/angular2': '/base/dist/dart/angular2/lib', + '/packages/http': '/base/dist/dart/http/lib', '/packages/angular2_material': '/base/dist/dart/angular2_material/lib', '/packages/benchpress': '/base/dist/dart/benchpress/lib', '/packages/examples': '/base/dist/dart/examples/lib' diff --git a/modules/angular2/angular2.ts b/modules/angular2/angular2.ts index 8f5032ee1806..640a4d18ddf1 100644 --- a/modules/angular2/angular2.ts +++ b/modules/angular2/angular2.ts @@ -12,7 +12,6 @@ export * from './change_detection'; export * from './core'; export * from './di'; export * from './directives'; -export * from './http'; export * from './forms'; export * from './render'; export * from './profile'; diff --git a/modules/angular2/angular2_exports.ts b/modules/angular2/angular2_exports.ts index ad7b5b6e0387..ed8c911a4b09 100644 --- a/modules/angular2/angular2_exports.ts +++ b/modules/angular2/angular2_exports.ts @@ -3,7 +3,6 @@ export * from './change_detection'; export * from './core'; export * from './di'; export * from './directives'; -export * from './http'; export * from './forms'; export * from './render'; export * from './profile'; diff --git a/modules/angular2/angular2_sfx.ts b/modules/angular2/angular2_sfx.ts index 63e8b0042a0b..edd420b58c30 100644 --- a/modules/angular2/angular2_sfx.ts +++ b/modules/angular2/angular2_sfx.ts @@ -9,7 +9,7 @@ var _prevNg = (window).ng; (window).ng = ng; -(ng).router = router; +(window).ngRouter = router; /** * Calling noConflict will restore window.angular to its pre-angular loading state * and return the angular module object. diff --git a/modules/angular2/annotations.ts b/modules/angular2/annotations.ts index efa3a48e9062..bb17a972adcb 100644 --- a/modules/angular2/annotations.ts +++ b/modules/angular2/annotations.ts @@ -12,6 +12,7 @@ export { ComponentAnnotation, DirectiveAnnotation, + PipeAnnotation, LifecycleEvent } from './src/core/annotations/annotations'; @@ -43,5 +44,7 @@ export { ViewFactory, Query, QueryFactory, - ViewQuery + ViewQuery, + Pipe, + PipeFactory } from 'angular2/src/core/annotations/decorators'; diff --git a/modules/angular2/bootstrap.ts b/modules/angular2/bootstrap.ts index 1c378b2e72cb..ddc827d5de90 100644 --- a/modules/angular2/bootstrap.ts +++ b/modules/angular2/bootstrap.ts @@ -12,6 +12,5 @@ export * from './change_detection'; export * from './core'; export * from './di'; export * from './directives'; -export * from './http'; export * from './forms'; export * from './render'; diff --git a/modules/angular2/change_detection.ts b/modules/angular2/change_detection.ts index a6efae990861..e3c0c349d620 100644 --- a/modules/angular2/change_detection.ts +++ b/modules/angular2/change_detection.ts @@ -20,17 +20,12 @@ export { ChangeDetectorRef, WrappedValue, - defaultPipes, - Pipe, - Pipes, + PipeTransform, IterableDiffers, IterableDiffer, IterableDifferFactory, KeyValueDiffers, KeyValueDiffer, KeyValueDifferFactory, - PipeFactory, - BasePipe, - NullPipe, - NullPipeFactory + BasePipeTransform } from 'angular2/src/change_detection/change_detection'; diff --git a/modules/angular2/core.ts b/modules/angular2/core.ts index efbb7ddd13c8..4ac6b3f0a7b2 100644 --- a/modules/angular2/core.ts +++ b/modules/angular2/core.ts @@ -23,7 +23,6 @@ export {LifeCycle} from 'angular2/src/core/life_cycle/life_cycle'; export {ElementRef} from 'angular2/src/core/compiler/element_ref'; export {TemplateRef} from 'angular2/src/core/compiler/template_ref'; -export {RenderElementRef} from 'angular2/src/render/api'; export {ViewRef, HostViewRef, ProtoViewRef} from 'angular2/src/core/compiler/view_ref'; export {ViewContainerRef} from 'angular2/src/core/compiler/view_container_ref'; export {ComponentRef} from 'angular2/src/core/compiler/dynamic_component_loader'; diff --git a/modules/angular2/directives.ts b/modules/angular2/directives.ts index 4a482ac9bca0..668450834249 100644 --- a/modules/angular2/directives.ts +++ b/modules/angular2/directives.ts @@ -5,12 +5,13 @@ */ import {CONST_EXPR, Type} from './src/facade/lang'; +import {NgClass} from './src/directives/ng_class'; import {NgFor} from './src/directives/ng_for'; import {NgIf} from './src/directives/ng_if'; import {NgNonBindable} from './src/directives/ng_non_bindable'; import {NgSwitch, NgSwitchWhen, NgSwitchDefault} from './src/directives/ng_switch'; -export * from './src/directives/class'; +export * from './src/directives/ng_class'; export * from './src/directives/ng_for'; export * from './src/directives/ng_if'; export * from './src/directives/ng_non_bindable'; @@ -60,4 +61,4 @@ export * from './src/directives/ng_switch'; * */ export const coreDirectives: List = - CONST_EXPR([NgFor, NgIf, NgNonBindable, NgSwitch, NgSwitchWhen, NgSwitchDefault]); + CONST_EXPR([NgClass, NgFor, NgIf, NgNonBindable, NgSwitch, NgSwitchWhen, NgSwitchDefault]); diff --git a/modules/angular2/docs/core/01_templates.md b/modules/angular2/docs/core/01_templates.md index ebc63a0bd4f3..864d0ca4c85a 100644 --- a/modules/angular2/docs/core/01_templates.md +++ b/modules/angular2/docs/core/01_templates.md @@ -94,7 +94,7 @@ Example:
 ```
-
+
```
diff --git a/modules/angular2/http.ts b/modules/angular2/http.ts deleted file mode 100644 index 3e293410cad6..000000000000 --- a/modules/angular2/http.ts +++ /dev/null @@ -1,80 +0,0 @@ -/** - * @module - * @description - * The http module provides services to perform http requests. To get started, see the {@link Http} - * class. - */ -import {bind, Binding} from 'angular2/di'; -import {Http, Jsonp} from 'angular2/src/http/http'; -import {XHRBackend, XHRConnection} from 'angular2/src/http/backends/xhr_backend'; -import {JSONPBackend, JSONPConnection} from 'angular2/src/http/backends/jsonp_backend'; -import {BrowserXhr} from 'angular2/src/http/backends/browser_xhr'; -import {BrowserJsonp} from 'angular2/src/http/backends/browser_jsonp'; -import {BaseRequestOptions, RequestOptions} from 'angular2/src/http/base_request_options'; -import {ConnectionBackend} from 'angular2/src/http/interfaces'; -import {BaseResponseOptions, ResponseOptions} from 'angular2/src/http/base_response_options'; - -export {MockConnection, MockBackend} from 'angular2/src/http/backends/mock_backend'; -export {Request} from 'angular2/src/http/static_request'; -export {Response} from 'angular2/src/http/static_response'; - -export { - IRequestOptions, - IResponseOptions, - Connection, - ConnectionBackend -} from 'angular2/src/http/interfaces'; - -export {BrowserXhr} from 'angular2/src/http/backends/browser_xhr'; -export {BaseRequestOptions, RequestOptions} from 'angular2/src/http/base_request_options'; -export {BaseResponseOptions, ResponseOptions} from 'angular2/src/http/base_response_options'; -export {XHRBackend, XHRConnection} from 'angular2/src/http/backends/xhr_backend'; -export {JSONPBackend, JSONPConnection} from 'angular2/src/http/backends/jsonp_backend'; -export {Http, Jsonp} from 'angular2/src/http/http'; - -export {Headers} from 'angular2/src/http/headers'; - -export { - ResponseTypes, - ReadyStates, - RequestMethods, - RequestCredentialsOpts, - RequestCacheOpts, - RequestModesOpts -} from 'angular2/src/http/enums'; -export {URLSearchParams} from 'angular2/src/http/url_search_params'; - -/** - * Provides a basic set of injectables to use the {@link Http} service in any application. - * - * #Example - * - * ``` - * import {httpInjectables, Http} from 'angular2/http'; - * @Component({selector: 'http-app', viewBindings: [httpInjectables]}) - * @View({template: '{{data}}'}) - * class MyApp { - * constructor(http:Http) { - * http.request('data.txt').subscribe(res => this.data = res.text()); - * } - * } - * ``` - * - */ -export var httpInjectables: List = [ - bind(ConnectionBackend) - .toClass(XHRBackend), - BrowserXhr, - bind(RequestOptions).toClass(BaseRequestOptions), - bind(ResponseOptions).toClass(BaseResponseOptions), - Http -]; - -export var jsonpInjectables: List = [ - bind(ConnectionBackend) - .toClass(JSONPBackend), - BrowserJsonp, - bind(RequestOptions).toClass(BaseRequestOptions), - bind(ResponseOptions).toClass(BaseResponseOptions), - Jsonp -]; diff --git a/modules/angular2/pipes.ts b/modules/angular2/pipes.ts index 256ce582fd47..5bf87b165beb 100644 --- a/modules/angular2/pipes.ts +++ b/modules/angular2/pipes.ts @@ -4,11 +4,11 @@ * This module provides advanced support for extending change detection. */ -export {PromisePipe} from './src/change_detection/pipes/promise_pipe'; -export {UpperCasePipe} from './src/change_detection/pipes/uppercase_pipe'; -export {LowerCasePipe} from './src/change_detection/pipes/lowercase_pipe'; -export {ObservablePipe} from './src/change_detection/pipes/observable_pipe'; -export {JsonPipe} from './src/change_detection/pipes/json_pipe'; -export {DatePipe} from './src/change_detection/pipes/date_pipe'; -export {DecimalPipe, PercentPipe, CurrencyPipe} from './src/change_detection/pipes/number_pipe'; -export {LimitToPipe} from './src/change_detection/pipes/limit_to_pipe'; +export {UpperCasePipe} from './src/pipes/uppercase_pipe'; +export {LowerCasePipe} from './src/pipes/lowercase_pipe'; +export {AsyncPipe} from './src/pipes/async_pipe'; +export {JsonPipe} from './src/pipes/json_pipe'; +export {DatePipe} from './src/pipes/date_pipe'; +export {DecimalPipe, PercentPipe, CurrencyPipe} from './src/pipes/number_pipe'; +export {LimitToPipe} from './src/pipes/limit_to_pipe'; +export {DEFAULT_PIPES_TOKEN, DEFAULT_PIPES} from './src/pipes/default_pipes'; diff --git a/modules/angular2/pubspec.yaml b/modules/angular2/pubspec.yaml index 6fb8b6c9674d..568a544c1b7d 100644 --- a/modules/angular2/pubspec.yaml +++ b/modules/angular2/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: analyzer: '>=0.24.4 <0.26.0' barback: '^0.15.2+2' code_transformers: '^0.2.8' - dart_style: '^0.1.8' + dart_style: '>=0.1.8 <0.3.0' html: '^0.12.0' intl: '^0.12.4' logging: '>=0.9.0 <0.12.0' diff --git a/modules/angular2/router.ts b/modules/angular2/router.ts index 33c29c510e29..5dc032fc7ecf 100644 --- a/modules/angular2/router.ts +++ b/modules/angular2/router.ts @@ -19,7 +19,9 @@ export * from './src/router/route_config_decorator'; export * from './src/router/route_definition'; export {OnActivate, OnDeactivate, OnReuse, CanDeactivate, CanReuse} from './src/router/interfaces'; export {CanActivate} from './src/router/lifecycle_annotations'; -export {Instruction} from './src/router/instruction'; +export {Instruction, ComponentInstruction} from './src/router/instruction'; +export {Url} from './src/router/url_parser'; +export {OpaqueToken, Type} from 'angular2/angular2'; import {LocationStrategy} from './src/router/location_strategy'; import {HTML5LocationStrategy} from './src/router/html5_location_strategy'; diff --git a/modules/angular2/src/change_detection/abstract_change_detector.ts b/modules/angular2/src/change_detection/abstract_change_detector.ts index 4fdf665cce85..77e27586d056 100644 --- a/modules/angular2/src/change_detection/abstract_change_detector.ts +++ b/modules/angular2/src/change_detection/abstract_change_detector.ts @@ -4,6 +4,7 @@ import {ChangeDetectionUtil} from './change_detection_util'; import {ChangeDetectorRef} from './change_detector_ref'; import {DirectiveRecord} from './directive_record'; import {ChangeDetector, ChangeDispatcher} from './interfaces'; +import {Pipes} from './pipes'; import { ChangeDetectionError, ExpressionChangedAfterItHasBeenCheckedException, @@ -12,7 +13,6 @@ import { import {ProtoRecord} from './proto_record'; import {BindingRecord} from './binding_record'; import {Locals} from './parser/locals'; -import {Pipes} from './pipes/pipes'; import {CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED, ON_PUSH} from './constants'; import {wtfCreateScope, wtfLeave, WtfScopeFn} from '../profile/profile'; diff --git a/modules/angular2/src/change_detection/change_detection.ts b/modules/angular2/src/change_detection/change_detection.ts index caa512cdf7c3..b2846ca573b6 100644 --- a/modules/angular2/src/change_detection/change_detection.ts +++ b/modules/angular2/src/change_detection/change_detection.ts @@ -1,23 +1,12 @@ import {JitProtoChangeDetector} from './jit_proto_change_detector'; import {PregenProtoChangeDetector} from './pregen_proto_change_detector'; import {DynamicProtoChangeDetector} from './proto_change_detector'; -import {PipeFactory, Pipe} from './pipes/pipe'; -import {Pipes} from './pipes/pipes'; import {IterableDiffers, IterableDifferFactory} from './differs/iterable_differs'; import {DefaultIterableDifferFactory} from './differs/default_iterable_differ'; import {KeyValueDiffers, KeyValueDifferFactory} from './differs/keyvalue_differs'; import {DefaultKeyValueDifferFactory} from './differs/default_keyvalue_differ'; -import {ObservablePipeFactory} from './pipes/observable_pipe'; -import {PromisePipeFactory} from './pipes/promise_pipe'; -import {UpperCasePipe} from './pipes/uppercase_pipe'; -import {LowerCasePipe} from './pipes/lowercase_pipe'; -import {JsonPipe} from './pipes/json_pipe'; -import {LimitToPipeFactory} from './pipes/limit_to_pipe'; -import {DatePipe} from './pipes/date_pipe'; -import {DecimalPipe, PercentPipe, CurrencyPipe} from './pipes/number_pipe'; -import {NullPipeFactory} from './pipes/null_pipe'; import {ChangeDetection, ProtoChangeDetector, ChangeDetectorDefinition} from './interfaces'; -import {Inject, Injectable, OpaqueToken, Optional} from 'angular2/di'; +import {Injector, Inject, Injectable, OpaqueToken, Optional, Binding} from 'angular2/di'; import {List, StringMap, StringMapWrapper} from 'angular2/src/facade/collection'; import {CONST, CONST_EXPR, isPresent, BaseException} from 'angular2/src/facade/lang'; @@ -53,12 +42,10 @@ export {BindingRecord} from './binding_record'; export {DirectiveIndex, DirectiveRecord} from './directive_record'; export {DynamicChangeDetector} from './dynamic_change_detector'; export {ChangeDetectorRef} from './change_detector_ref'; -export {Pipes} from './pipes/pipes'; export {IterableDiffers, IterableDiffer, IterableDifferFactory} from './differs/iterable_differs'; export {KeyValueDiffers, KeyValueDiffer, KeyValueDifferFactory} from './differs/keyvalue_differs'; -export {WrappedValue, Pipe, PipeFactory, BasePipe} from './pipes/pipe'; -export {NullPipe, NullPipeFactory} from './pipes/null_pipe'; - +export {PipeTransform, BasePipeTransform} from './pipe_transform'; +export {WrappedValue} from './change_detection_util'; /** * Structural diffing for `Object`s and `Map`s. @@ -72,76 +59,6 @@ export const keyValDiff: KeyValueDifferFactory[] = export const iterableDiff: IterableDifferFactory[] = CONST_EXPR([CONST_EXPR(new DefaultIterableDifferFactory())]); -/** - * Async binding to such types as Observable. - */ -export const async: List = CONST_EXPR([ - CONST_EXPR(new ObservablePipeFactory()), - CONST_EXPR(new PromisePipeFactory()), - CONST_EXPR(new NullPipeFactory()) -]); - -/** - * Uppercase text transform. - */ -export const uppercase: List = - CONST_EXPR([CONST_EXPR(new UpperCasePipe()), CONST_EXPR(new NullPipeFactory())]); - -/** - * Lowercase text transform. - */ -export const lowercase: List = - CONST_EXPR([CONST_EXPR(new LowerCasePipe()), CONST_EXPR(new NullPipeFactory())]); - -/** - * Json stringify transform. - */ -export const json: List = - CONST_EXPR([CONST_EXPR(new JsonPipe()), CONST_EXPR(new NullPipeFactory())]); - -/** - * LimitTo text transform. - */ -export const limitTo: List = - CONST_EXPR([CONST_EXPR(new LimitToPipeFactory()), CONST_EXPR(new NullPipeFactory())]); - -/** - * Number number transform. - */ -export const decimal: List = - CONST_EXPR([CONST_EXPR(new DecimalPipe()), CONST_EXPR(new NullPipeFactory())]); - -/** - * Percent number transform. - */ -export const percent: List = - CONST_EXPR([CONST_EXPR(new PercentPipe()), CONST_EXPR(new NullPipeFactory())]); - -/** - * Currency number transform. - */ -export const currency: List = - CONST_EXPR([CONST_EXPR(new CurrencyPipe()), CONST_EXPR(new NullPipeFactory())]); - -/** - * Date/time formatter. - */ -export const date: List = - CONST_EXPR([CONST_EXPR(new DatePipe()), CONST_EXPR(new NullPipeFactory())]); - - -export const defaultPipes: Pipes = CONST_EXPR(new Pipes({ - "async": async, - "uppercase": uppercase, - "lowercase": lowercase, - "json": json, - "limitTo": limitTo, - "number": decimal, - "percent": percent, - "currency": currency, - "date": date -})); - export const defaultIterableDiffers = CONST_EXPR(new IterableDiffers(iterableDiff)); export const defaultKeyValueDiffers = CONST_EXPR(new KeyValueDiffers(keyValDiff)); diff --git a/modules/angular2/src/change_detection/change_detection_jit_generator.ts b/modules/angular2/src/change_detection/change_detection_jit_generator.ts index 6eb3914684f0..d2ce2b8d1ec3 100644 --- a/modules/angular2/src/change_detection/change_detection_jit_generator.ts +++ b/modules/angular2/src/change_detection/change_detection_jit_generator.ts @@ -179,15 +179,10 @@ export class ChangeDetectorJITGenerator { var newValue = this._names.getLocalName(r.selfIndex); var pipe = this._names.getPipeName(r.selfIndex); - var cdRef = "this.ref"; var pipeType = r.name; - var read = ` if (${pipe} === ${UTIL}.uninitialized) { - ${pipe} = ${this._names.getPipesAccessorName()}.get('${pipeType}', ${context}, ${cdRef}); - } else if (!${pipe}.supports(${context})) { - ${pipe}.onDestroy(); - ${pipe} = ${this._names.getPipesAccessorName()}.get('${pipeType}', ${context}, ${cdRef}); + ${pipe} = ${this._names.getPipesAccessorName()}.get('${pipeType}'); } ${newValue} = ${pipe}.transform(${context}, [${argString}]); `; diff --git a/modules/angular2/src/change_detection/change_detection_util.ts b/modules/angular2/src/change_detection/change_detection_util.ts index d924ecea9df0..9b38195cf743 100644 --- a/modules/angular2/src/change_detection/change_detection_util.ts +++ b/modules/angular2/src/change_detection/change_detection_util.ts @@ -1,9 +1,47 @@ import {CONST_EXPR, isPresent, isBlank, BaseException, Type} from 'angular2/src/facade/lang'; import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; import {ProtoRecord} from './proto_record'; -import {WrappedValue} from './pipes/pipe'; import {CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED, ON_PUSH} from './constants'; + +/** + * Indicates that the result of a {@link Pipe} transformation has changed even though the reference + * has not changed. + * + * The wrapped value will be unwrapped by change detection, and the unwrapped value will be stored. + * + * Example: + * + * ``` + * if (this._latestValue === this._latestReturnedValue) { + * return this._latestReturnedValue; + * } else { + * this._latestReturnedValue = this._latestValue; + * return WrappedValue.wrap(this._latestValue); // this will force update + * } + * ``` + */ +export class WrappedValue { + constructor(public wrapped: any) {} + + static wrap(value: any): WrappedValue { + var w = _wrappedValues[_wrappedIndex++ % 5]; + w.wrapped = value; + return w; + } +} + +var _wrappedValues = [ + new WrappedValue(null), + new WrappedValue(null), + new WrappedValue(null), + new WrappedValue(null), + new WrappedValue(null) +]; + +var _wrappedIndex = 0; + + export class SimpleChange { constructor(public previousValue: any, public currentValue: any) {} diff --git a/modules/angular2/src/change_detection/dynamic_change_detector.ts b/modules/angular2/src/change_detection/dynamic_change_detector.ts index 21eaa4b14161..6aa0d14a9f6d 100644 --- a/modules/angular2/src/change_detection/dynamic_change_detector.ts +++ b/modules/angular2/src/change_detection/dynamic_change_detector.ts @@ -267,13 +267,9 @@ export class DynamicChangeDetector extends AbstractChangeDetector { _pipeFor(proto: ProtoRecord, context) { var storedPipe = this._readPipe(proto); - if (isPresent(storedPipe) && storedPipe.supports(context)) { - return storedPipe; - } - if (isPresent(storedPipe)) { - storedPipe.onDestroy(); - } - var pipe = this.pipes.get(proto.name, context, this.ref); + if (isPresent(storedPipe)) return storedPipe; + + var pipe = this.pipes.get(proto.name); this._writePipe(proto, pipe); return pipe; } diff --git a/modules/angular2/src/change_detection/interfaces.ts b/modules/angular2/src/change_detection/interfaces.ts index 6b49661846e1..5068d4609be0 100644 --- a/modules/angular2/src/change_detection/interfaces.ts +++ b/modules/angular2/src/change_detection/interfaces.ts @@ -3,6 +3,7 @@ import {CONST} from 'angular2/src/facade/lang'; import {Locals} from './parser/locals'; import {BindingRecord} from './binding_record'; import {DirectiveIndex, DirectiveRecord} from './directive_record'; +import {ChangeDetectorRef} from './change_detector_ref'; /** * Interface used by Angular to control the change detection strategy for an application. @@ -50,6 +51,7 @@ export interface ChangeDispatcher { export interface ChangeDetector { parent: ChangeDetector; mode: string; + ref: ChangeDetectorRef; addChild(cd: ChangeDetector): void; addShadowDomChild(cd: ChangeDetector): void; diff --git a/modules/angular2/src/change_detection/pipe_transform.ts b/modules/angular2/src/change_detection/pipe_transform.ts new file mode 100644 index 000000000000..6e88a1f1b6e4 --- /dev/null +++ b/modules/angular2/src/change_detection/pipe_transform.ts @@ -0,0 +1,45 @@ +import {ABSTRACT, BaseException, CONST, Type} from 'angular2/src/facade/lang'; + +/** + * An interface which all pipes must implement. + * + * #Example + * + * ``` + * class DoublePipe implements PipeTransform { + * onDestroy() {} + * + * transform(value, args = []) { + * return `${value}${value}`; + * } + * } + * ``` + */ +export interface PipeTransform { + onDestroy(): void; + + transform(value: any, args: List): any; +} + +/** + * Provides default implementation of the `onDestroy` method. + * + * #Example + * + * ``` + * class DoublePipe extends BasePipe { + * transform(value) { + * return `${value}${value}`; + * } + * } + * ``` + */ +@CONST() +export class BasePipeTransform implements PipeTransform { + onDestroy(): void {} + transform(value: any, args: List): any { return _abstract(); } +} + +function _abstract() { + throw new BaseException('This method is abstract'); +} diff --git a/modules/angular2/src/change_detection/pipes.ts b/modules/angular2/src/change_detection/pipes.ts new file mode 100644 index 000000000000..b9eb15ee0c4e --- /dev/null +++ b/modules/angular2/src/change_detection/pipes.ts @@ -0,0 +1,3 @@ +import {PipeTransform} from './pipe_transform'; + +export interface Pipes { get(name: string): PipeTransform; } \ No newline at end of file diff --git a/modules/angular2/src/change_detection/pipes/lowercase_pipe.ts b/modules/angular2/src/change_detection/pipes/lowercase_pipe.ts deleted file mode 100644 index 84f2ae82d445..000000000000 --- a/modules/angular2/src/change_detection/pipes/lowercase_pipe.ts +++ /dev/null @@ -1,34 +0,0 @@ -import {isString, StringWrapper, CONST} from 'angular2/src/facade/lang'; -import {Pipe, BasePipe, PipeFactory} from './pipe'; -import {ChangeDetectorRef} from '../change_detector_ref'; - -/** - * Implements lowercase transforms to text. - * - * # Example - * - * In this example we transform the user text lowercase. - * - * ``` - * @Component({ - * selector: "username-cmp" - * }) - * @View({ - * template: "Username: {{ user | lowercase }}" - * }) - * class Username { - * user:string; - * } - * - * ``` - */ -@CONST() -export class LowerCasePipe extends BasePipe implements PipeFactory { - supports(str: any): boolean { return isString(str); } - - transform(value: string, args: List = null): string { - return StringWrapper.toLowerCase(value); - } - - create(cdRef: ChangeDetectorRef): Pipe { return this; } -} diff --git a/modules/angular2/src/change_detection/pipes/null_pipe.ts b/modules/angular2/src/change_detection/pipes/null_pipe.ts deleted file mode 100644 index 93492424ad57..000000000000 --- a/modules/angular2/src/change_detection/pipes/null_pipe.ts +++ /dev/null @@ -1,27 +0,0 @@ -import {isBlank, CONST} from 'angular2/src/facade/lang'; -import {Pipe, BasePipe, WrappedValue, PipeFactory} from './pipe'; -import {ChangeDetectorRef} from '../change_detector_ref'; - -@CONST() -export class NullPipeFactory implements PipeFactory { - supports(obj: any): boolean { return NullPipe.supportsObj(obj); } - - create(cdRef: ChangeDetectorRef): Pipe { return new NullPipe(); } -} - -export class NullPipe extends BasePipe { - called: boolean = false; - - static supportsObj(obj: any): boolean { return isBlank(obj); } - - supports(obj: any): boolean { return NullPipe.supportsObj(obj); } - - transform(value: any, args: List = null): WrappedValue { - if (!this.called) { - this.called = true; - return WrappedValue.wrap(null); - } else { - return null; - } - } -} diff --git a/modules/angular2/src/change_detection/pipes/observable_pipe.ts b/modules/angular2/src/change_detection/pipes/observable_pipe.ts deleted file mode 100644 index 7abfc5deee49..000000000000 --- a/modules/angular2/src/change_detection/pipes/observable_pipe.ts +++ /dev/null @@ -1,94 +0,0 @@ -import {Observable, ObservableWrapper} from 'angular2/src/facade/async'; -import {isBlank, isPresent, CONST} from 'angular2/src/facade/lang'; -import {Pipe, WrappedValue, PipeFactory} from './pipe'; -import {ChangeDetectorRef} from '../change_detector_ref'; - -/** - * Implements async bindings to Observable. - * - * # Example - * - * In this example we bind the description observable to the DOM. The async pipe will convert an - *observable to the - * latest value it emitted. It will also request a change detection check when a new value is - *emitted. - * - * ``` - * @Component({ - * selector: "task-cmp", - * changeDetection: ON_PUSH - * }) - * @View({ - * template: "Task Description {{ description | async }}" - * }) - * class Task { - * description:Observable; - * } - * - * ``` - */ -export class ObservablePipe implements Pipe { - _latestValue: Object = null; - _latestReturnedValue: Object = null; - - _subscription: Object = null; - _observable: Observable = null; - - constructor(public _ref: ChangeDetectorRef) {} - - supports(obs: any): boolean { return ObservableWrapper.isObservable(obs); } - - onDestroy(): void { - if (isPresent(this._subscription)) { - this._dispose(); - } - } - - transform(obs: Observable, args: List = null): any { - if (isBlank(this._subscription)) { - this._subscribe(obs); - return null; - } - - if (obs !== this._observable) { - this._dispose(); - return this.transform(obs); - } - - if (this._latestValue === this._latestReturnedValue) { - return this._latestReturnedValue; - } else { - this._latestReturnedValue = this._latestValue; - return WrappedValue.wrap(this._latestValue); - } - } - - _subscribe(obs: Observable): void { - this._observable = obs; - this._subscription = ObservableWrapper.subscribe(obs, value => this._updateLatestValue(value), - e => { throw e; }); - } - - _dispose(): void { - ObservableWrapper.dispose(this._subscription); - this._latestValue = null; - this._latestReturnedValue = null; - this._subscription = null; - this._observable = null; - } - - _updateLatestValue(value: Object) { - this._latestValue = value; - this._ref.requestCheck(); - } -} - -/** - * Provides a factory for [ObervablePipe]. - */ -@CONST() -export class ObservablePipeFactory implements PipeFactory { - supports(obs: any): boolean { return ObservableWrapper.isObservable(obs); } - - create(cdRef: ChangeDetectorRef): Pipe { return new ObservablePipe(cdRef); } -} diff --git a/modules/angular2/src/change_detection/pipes/pipe.ts b/modules/angular2/src/change_detection/pipes/pipe.ts deleted file mode 100644 index e4f747343d01..000000000000 --- a/modules/angular2/src/change_detection/pipes/pipe.ts +++ /dev/null @@ -1,90 +0,0 @@ -import {ABSTRACT, BaseException, CONST} from 'angular2/src/facade/lang'; -import {ChangeDetectorRef} from '../change_detector_ref'; - -/** - * Indicates that the result of a {@link Pipe} transformation has changed even though the reference - * has not changed. - * - * The wrapped value will be unwrapped by change detection, and the unwrapped value will be stored. - */ -export class WrappedValue { - constructor(public wrapped: any) {} - - static wrap(value: any): WrappedValue { - var w = _wrappedValues[_wrappedIndex++ % 5]; - w.wrapped = value; - return w; - } -} - -var _wrappedValues = [ - new WrappedValue(null), - new WrappedValue(null), - new WrappedValue(null), - new WrappedValue(null), - new WrappedValue(null) -]; - -var _wrappedIndex = 0; - -/** - * An interface which all pipes must implement. - * - * #Example - * - * ``` - * class DoublePipe implements Pipe { - * supports(obj) { - * return true; - * } - * - * onDestroy() {} - * - * transform(value, args = []) { - * return `${value}${value}`; - * } - * } - * ``` - */ -export interface Pipe { - /** - * Query if a pipe supports a particular object instance. - */ - supports(obj): boolean; - - onDestroy(): void; - - transform(value: any, args: List): any; -} - -/** - * Provides default implementation of `supports` and `onDestroy` method. - * - * #Example - * - * ``` - * class DoublePipe extends BasePipe { - * transform(value) { - * return `${value}${value}`; - * } - * } - * ``` - */ -@CONST() -export class BasePipe implements Pipe { - supports(obj: any): boolean { return true; } - onDestroy(): void {} - transform(value: any, args: List): any { return _abstract(); } -} - -/** - * - */ -export interface PipeFactory { - supports(obs): boolean; - create(cdRef: ChangeDetectorRef): Pipe; -} - -function _abstract() { - throw new BaseException('This method is abstract'); -} diff --git a/modules/angular2/src/change_detection/pipes/pipes.ts b/modules/angular2/src/change_detection/pipes/pipes.ts deleted file mode 100644 index 7e3a6e51e900..000000000000 --- a/modules/angular2/src/change_detection/pipes/pipes.ts +++ /dev/null @@ -1,117 +0,0 @@ -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, OptionalMetadata, SkipSelfMetadata} from 'angular2/di'; -import {ChangeDetectorRef} from '../change_detector_ref'; -import {Binding} from 'angular2/di'; - -@Injectable() -@CONST() -export class Pipes { - /** - * Map of {@link Pipe} names to {@link PipeFactory} lists used to configure the - * {@link Pipes} registry. - * - * #Example - * - * ``` - * var pipesConfig = { - * 'json': [jsonPipeFactory] - * } - * @Component({ - * viewBindings: [ - * bind(Pipes).toValue(new Pipes(pipesConfig)) - * ] - * }) - * ``` - */ - config: StringMap; - - - constructor(config: StringMap) { this.config = config; } - - get(type: string, obj: any, cdRef?: ChangeDetectorRef, existingPipe?: Pipe): Pipe { - if (isPresent(existingPipe) && existingPipe.supports(obj)) return existingPipe; - - if (isPresent(existingPipe)) existingPipe.onDestroy(); - - var factories = this._getListOfFactories(type, obj); - var factory = this._getMatchingFactory(factories, type, obj); - - return factory.create(cdRef); - } - - /** - * Takes a {@link Pipes} config object and returns a binding used to extend the - * inherited {@link Pipes} instance with the provided config and 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 prepending pipes to their respective keys, without mutating - * the inherited {@link Pipes}. - * - * The following example shows how to extend an existing list of `async` factories - * with a new {@link PipeFactory}, 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({ - * viewBindings: [ - * Pipes.extend({ - * async: [newAsyncPipe] - * }) - * ] - * }) - * ``` - */ - static extend(config: StringMap): Binding { - return new Binding(Pipes, { - toFactory: (pipes: Pipes) => { - if (isBlank(pipes)) { - // Typically would occur when calling Pipe.extend inside of dependencies passed to - // bootstrap(), which would override default pipes instead of extending them. - throw new BaseException('Cannot extend Pipes without a parent injector'); - } - return Pipes.create(config, pipes); - }, - // Dependency technically isn't optional, but we can provide a better error message this way. - deps: [[Pipes, new SkipSelfMetadata(), new OptionalMetadata()]] - }); - } - - static create(config: StringMap, pipes: Pipes = null): Pipes { - if (isPresent(pipes)) { - StringMapWrapper.forEach(pipes.config, (v: PipeFactory[], k: string) => { - if (StringMapWrapper.contains(config, k)) { - var configFactories: PipeFactory[] = config[k]; - config[k] = configFactories.concat(v); - } else { - config[k] = ListWrapper.clone(v); - } - }); - } - return new Pipes(config); - } - - 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}'`); - } - return listOfFactories; - } - - private _getMatchingFactory(listOfFactories: PipeFactory[], type: string, obj: any): PipeFactory { - var matchingFactory = - ListWrapper.find(listOfFactories, pipeFactory => pipeFactory.supports(obj)); - if (isBlank(matchingFactory)) { - throw new BaseException(`Cannot find '${type}' pipe supporting object '${obj}'`); - } - return matchingFactory; - } -} diff --git a/modules/angular2/src/change_detection/pipes/promise_pipe.ts b/modules/angular2/src/change_detection/pipes/promise_pipe.ts deleted file mode 100644 index 4b8b00a8bc02..000000000000 --- a/modules/angular2/src/change_detection/pipes/promise_pipe.ts +++ /dev/null @@ -1,84 +0,0 @@ -import {Promise, PromiseWrapper} from 'angular2/src/facade/async'; -import {isBlank, isPresent, isPromise, CONST} from 'angular2/src/facade/lang'; -import {Pipe, PipeFactory, WrappedValue} from './pipe'; -import {ChangeDetectorRef} from '../change_detector_ref'; - -/** - * Implements async bindings to Promise. - * - * # Example - * - * In this example we bind the description promise to the DOM. - * The async pipe will convert a promise to the value with which it is resolved. It will also - * request a change detection check when the promise is resolved. - * - * ``` - * @Component({ - * selector: "task-cmp", - * changeDetection: ON_PUSH - * }) - * @View({ - * template: "Task Description {{ description | async }}" - * }) - * class Task { - * description:Promise; - * } - * - * ``` - */ -export class PromisePipe implements Pipe { - _latestValue: Object = null; - _latestReturnedValue: Object = null; - _sourcePromise: Promise; - - constructor(public _ref: ChangeDetectorRef) {} - - supports(promise: any): boolean { return isPromise(promise); } - - onDestroy(): void { - if (isPresent(this._sourcePromise)) { - this._latestValue = null; - this._latestReturnedValue = null; - this._sourcePromise = null; - } - } - - transform(promise: Promise, args: List = null): any { - if (isBlank(this._sourcePromise)) { - this._sourcePromise = promise; - promise.then((val) => { - if (this._sourcePromise === promise) { - this._updateLatestValue(val); - } - }); - return null; - } - - if (promise !== this._sourcePromise) { - this._sourcePromise = null; - return this.transform(promise); - } - - if (this._latestValue === this._latestReturnedValue) { - return this._latestReturnedValue; - } else { - this._latestReturnedValue = this._latestValue; - return WrappedValue.wrap(this._latestValue); - } - } - - _updateLatestValue(value: Object) { - this._latestValue = value; - this._ref.requestCheck(); - } -} - -/** - * Provides a factory for [PromisePipe]. - */ -@CONST() -export class PromisePipeFactory implements PipeFactory { - supports(promise: any): boolean { return isPromise(promise); } - - create(cdRef: ChangeDetectorRef): Pipe { return new PromisePipe(cdRef); } -} diff --git a/modules/angular2/src/change_detection/pipes/uppercase_pipe.ts b/modules/angular2/src/change_detection/pipes/uppercase_pipe.ts deleted file mode 100644 index 033ba7c838c8..000000000000 --- a/modules/angular2/src/change_detection/pipes/uppercase_pipe.ts +++ /dev/null @@ -1,34 +0,0 @@ -import {isString, StringWrapper, CONST} from 'angular2/src/facade/lang'; -import {Pipe, BasePipe, PipeFactory} from './pipe'; -import {ChangeDetectorRef} from '../change_detector_ref'; - -/** - * Implements uppercase transforms to text. - * - * # Example - * - * In this example we transform the user text uppercase. - * - * ``` - * @Component({ - * selector: "username-cmp" - * }) - * @View({ - * template: "Username: {{ user | uppercase }}" - * }) - * class Username { - * user:string; - * } - * - * ``` - */ -@CONST() -export class UpperCasePipe extends BasePipe implements PipeFactory { - supports(str: any): boolean { return isString(str); } - - transform(value: string, args: List = null): string { - return StringWrapper.toUpperCase(value); - } - - create(cdRef: ChangeDetectorRef): Pipe { return this; } -} diff --git a/modules/angular2/src/change_detection/pregen_proto_change_detector.dart b/modules/angular2/src/change_detection/pregen_proto_change_detector.dart index 14469b80e82c..bdb9a7546dc9 100644 --- a/modules/angular2/src/change_detection/pregen_proto_change_detector.dart +++ b/modules/angular2/src/change_detection/pregen_proto_change_detector.dart @@ -16,7 +16,7 @@ export 'package:angular2/src/change_detection/directive_record.dart' show DirectiveIndex, DirectiveRecord; export 'package:angular2/src/change_detection/interfaces.dart' show ChangeDetector, ChangeDetectorDefinition, ProtoChangeDetector; -export 'package:angular2/src/change_detection/pipes/pipes.dart' show Pipes; +export 'package:angular2/src/change_detection/pipes.dart' show Pipes; export 'package:angular2/src/change_detection/proto_record.dart' show ProtoRecord; export 'package:angular2/src/change_detection/change_detection_util.dart' diff --git a/modules/angular2/src/core/annotations/annotations.ts b/modules/angular2/src/core/annotations/annotations.ts index 97833ac6d00c..8e11e0eb543b 100644 --- a/modules/angular2/src/core/annotations/annotations.ts +++ b/modules/angular2/src/core/annotations/annotations.ts @@ -6,5 +6,6 @@ export { Component as ComponentAnnotation, Directive as DirectiveAnnotation, + Pipe as PipeAnnotation, LifecycleEvent } from '../annotations_impl/annotations'; diff --git a/modules/angular2/src/core/annotations/decorators.ts b/modules/angular2/src/core/annotations/decorators.ts index ccc294d61346..c1c40a89dc16 100644 --- a/modules/angular2/src/core/annotations/decorators.ts +++ b/modules/angular2/src/core/annotations/decorators.ts @@ -1,4 +1,9 @@ -import {ComponentAnnotation, DirectiveAnnotation, LifecycleEvent} from './annotations'; +import { + ComponentAnnotation, + DirectiveAnnotation, + PipeAnnotation, + LifecycleEvent +} from './annotations'; import {ViewAnnotation} from './view'; import {AttributeAnnotation, QueryAnnotation, ViewQueryAnnotation} from './di'; import {makeDecorator, makeParamDecorator, TypeDecorator, Class} from '../../util/decorators'; @@ -25,6 +30,7 @@ export interface ComponentDecorator extends TypeDecorator { templateUrl?: string, template?: string, directives?: List>, + pipes?: List>, renderer?: string, styles?: List, styleUrls?: List, @@ -44,6 +50,7 @@ export interface ViewDecorator extends TypeDecorator { templateUrl?: string, template?: string, directives?: List>, + pipes?: List>, renderer?: string, styles?: List, styleUrls?: List, @@ -337,6 +344,30 @@ export interface QueryFactory { new (selector: Type | string, {descendants}?: {descendants?: boolean}): QueryAnnotation; } +/** + * {@link Pipe} factory for creating decorators. + * + * ## Example as TypeScript Decorator + * + * ``` + * import {Pipe} from "angular2/angular2"; + * + * @Pipe({...}) + * class MyPipe { + * constructor() { + * ... + * } + * + * transform(v, args) {} + * } + * ``` + */ +export interface PipeFactory { + (obj: {name: string}): any; + new (obj: { + name: string, + }): any; +} /** * {@link Component} factory function. @@ -369,3 +400,8 @@ export var Query: QueryFactory = makeParamDecorator(QueryAnnotation); * {@link ViewQuery} factory function. */ export var ViewQuery: QueryFactory = makeParamDecorator(ViewQueryAnnotation); + +/** + * {@link Pipe} factory function. + */ +export var Pipe: PipeFactory = makeDecorator(PipeAnnotation); diff --git a/modules/angular2/src/core/annotations_impl/annotations.ts b/modules/angular2/src/core/annotations_impl/annotations.ts index 8a1c59b8cd42..111624f7d305 100644 --- a/modules/angular2/src/core/annotations_impl/annotations.ts +++ b/modules/angular2/src/core/annotations_impl/annotations.ts @@ -923,7 +923,6 @@ export enum LifecycleEvent { * } * } * ``` - * @exportedAs angular2/annotations */ onDestroy, @@ -961,7 +960,6 @@ export enum LifecycleEvent { * } * } * ``` - * @exportedAs angular2/annotations */ onChange, @@ -973,7 +971,7 @@ export enum LifecycleEvent { * * It is invoked every time even when none of the directive's bindings has changed. * - * ## Example: + * ## Example * * ``` * @Directive({ @@ -985,7 +983,6 @@ export enum LifecycleEvent { * } * } * ``` - * @exportedAs angular2/annotations */ onCheck, @@ -997,7 +994,7 @@ export enum LifecycleEvent { * * It is invoked only once. * - * ## Example: + * ## Example * * ``` * @Directive({ @@ -1009,7 +1006,6 @@ export enum LifecycleEvent { * } * } * ``` - * @exportedAs angular2/annotations */ onInit, @@ -1017,7 +1013,7 @@ export enum LifecycleEvent { * Notify a directive when the bindings of all its children have been checked (whether they have * changed or not). * - * ## Example: + * ## Example * * ``` * @Directive({ @@ -1031,7 +1027,30 @@ export enum LifecycleEvent { * * } * ``` - * @exportedAs angular2/annotations */ onAllChangesDone } + +/** + * Declare reusable pipe function. + * + * ## Example + * + * ``` + * @Pipe({ + * name: 'lowercase' + * }) + * class Lowercase { + * transform(v, args) { return v.toLowerCase(); } + * } + * ``` + */ +@CONST() +export class Pipe extends InjectableMetadata { + name: string; + + constructor({name}: {name: string}) { + super(); + this.name = name; + } +} diff --git a/modules/angular2/src/core/annotations_impl/view.ts b/modules/angular2/src/core/annotations_impl/view.ts index cbaf1cd9eb17..eb83dfd0ea67 100644 --- a/modules/angular2/src/core/annotations_impl/view.ts +++ b/modules/angular2/src/core/annotations_impl/view.ts @@ -87,6 +87,8 @@ export class View { // for an unused import. directives: List>; + pipes: List>; + /** * Specify how the template and the styles should be encapsulated. * The default is {@link ViewEncapsulation#EMULATED `ViewEncapsulation.EMULATED`} if the view @@ -95,10 +97,11 @@ export class View { */ encapsulation: ViewEncapsulation; - constructor({templateUrl, template, directives, encapsulation, styles, styleUrls}: { + constructor({templateUrl, template, directives, pipes, encapsulation, styles, styleUrls}: { templateUrl?: string, template?: string, directives?: List>, + pipes?: List>, encapsulation?: ViewEncapsulation, styles?: List, styleUrls?: List, @@ -108,6 +111,7 @@ export class View { this.styleUrls = styleUrls; this.styles = styles; this.directives = directives; + this.pipes = pipes; this.encapsulation = encapsulation; } } diff --git a/modules/angular2/src/core/application_common.ts b/modules/angular2/src/core/application_common.ts index f3a9bcf0d0e6..13c9b441f579 100644 --- a/modules/angular2/src/core/application_common.ts +++ b/modules/angular2/src/core/application_common.ts @@ -21,19 +21,19 @@ import { DynamicChangeDetection, JitChangeDetection, PreGeneratedChangeDetection, - Pipes, - defaultPipes, IterableDiffers, defaultIterableDiffers, KeyValueDiffers, defaultKeyValueDiffers } from 'angular2/src/change_detection/change_detection'; +import {DEFAULT_PIPES} from 'angular2/pipes'; import {ExceptionHandler} from './exception_handler'; import {ViewLoader} from 'angular2/src/render/dom/compiler/view_loader'; import {StyleUrlResolver} from 'angular2/src/render/dom/compiler/style_url_resolver'; import {StyleInliner} from 'angular2/src/render/dom/compiler/style_inliner'; import {ViewResolver} from './compiler/view_resolver'; import {DirectiveResolver} from './compiler/directive_resolver'; +import {PipeResolver} from './compiler/pipe_resolver'; import {List, ListWrapper} from 'angular2/src/facade/collection'; import {Promise, PromiseWrapper, PromiseCompleter} from 'angular2/src/facade/async'; import {NgZone} from 'angular2/src/core/zone/ng_zone'; @@ -137,12 +137,13 @@ function _injectorBindings(appComponentType): List> { Compiler, CompilerCache, ViewResolver, - bind(Pipes).toValue(defaultPipes), + DEFAULT_PIPES, bind(IterableDiffers).toValue(defaultIterableDiffers), bind(KeyValueDiffers).toValue(defaultKeyValueDiffers), bind(ChangeDetection).toClass(bestChangeDetection), ViewLoader, DirectiveResolver, + PipeResolver, Parser, Lexer, bind(ExceptionHandler).toFactory(() => new ExceptionHandler(DOM, isDart ? false : true), []), diff --git a/modules/angular2/src/core/compiler/compiler.ts b/modules/angular2/src/core/compiler/compiler.ts index ef5b0d60748a..45e13164e6b2 100644 --- a/modules/angular2/src/core/compiler/compiler.ts +++ b/modules/angular2/src/core/compiler/compiler.ts @@ -1,4 +1,4 @@ -import {Binding, resolveForwardRef, Injectable} from 'angular2/di'; +import {Binding, resolveForwardRef, Injectable, Inject} from 'angular2/di'; import { Type, isBlank, @@ -19,6 +19,7 @@ import {AppProtoView, AppProtoViewMergeMapping} from './view'; import {ProtoViewRef} from './view_ref'; import {DirectiveBinding} from './element_injector'; import {ViewResolver} from './view_resolver'; +import {PipeResolver} from './pipe_resolver'; import {View} from '../annotations_impl/view'; import {ComponentUrlMapper} from './component_url_mapper'; import {ProtoViewFactory} from './proto_view_factory'; @@ -26,6 +27,8 @@ import {UrlResolver} from 'angular2/src/services/url_resolver'; import {AppRootUrl} from 'angular2/src/services/app_root_url'; import {ElementBinder} from './element_binder'; import {wtfStartTimeRange, wtfEndTimeRange} from '../../profile/profile'; +import {PipeBinding} from '../pipes/pipe_binding'; +import {DEFAULT_PIPES_TOKEN} from 'angular2/pipes'; import * as renderApi from 'angular2/src/render/api'; @@ -83,46 +86,40 @@ export class CompilerCache { */ @Injectable() export class Compiler { - private _reader: DirectiveResolver; - private _compilerCache: CompilerCache; - private _compiling: Map>; - private _viewResolver: ViewResolver; - private _componentUrlMapper: ComponentUrlMapper; - private _urlResolver: UrlResolver; + private _compiling: Map> = new Map(); private _appUrl: string; - private _render: renderApi.RenderCompiler; - private _protoViewFactory: ProtoViewFactory; + private _defaultPipes: Type[]; /** * @private */ - constructor(reader: DirectiveResolver, cache: CompilerCache, viewResolver: ViewResolver, - componentUrlMapper: ComponentUrlMapper, urlResolver: UrlResolver, - render: renderApi.RenderCompiler, protoViewFactory: ProtoViewFactory, - appUrl: AppRootUrl) { - this._reader = reader; - this._compilerCache = cache; - this._compiling = new Map(); - this._viewResolver = viewResolver; - this._componentUrlMapper = componentUrlMapper; - this._urlResolver = urlResolver; + constructor(private _directiveResolver: DirectiveResolver, private _pipeResolver: PipeResolver, + @Inject(DEFAULT_PIPES_TOKEN) _defaultPipes: Type[], + private _compilerCache: CompilerCache, private _viewResolver: ViewResolver, + private _componentUrlMapper: ComponentUrlMapper, private _urlResolver: UrlResolver, + private _render: renderApi.RenderCompiler, + private _protoViewFactory: ProtoViewFactory, appUrl: AppRootUrl) { + this._defaultPipes = _defaultPipes; this._appUrl = appUrl.value; - this._render = render; - this._protoViewFactory = protoViewFactory; } private _bindDirective(directiveTypeOrBinding): DirectiveBinding { if (directiveTypeOrBinding instanceof DirectiveBinding) { return directiveTypeOrBinding; } else if (directiveTypeOrBinding instanceof Binding) { - let annotation = this._reader.resolve(directiveTypeOrBinding.token); + let annotation = this._directiveResolver.resolve(directiveTypeOrBinding.token); return DirectiveBinding.createFromBinding(directiveTypeOrBinding, annotation); } else { - let annotation = this._reader.resolve(directiveTypeOrBinding); + let annotation = this._directiveResolver.resolve(directiveTypeOrBinding); return DirectiveBinding.createFromType(directiveTypeOrBinding, annotation); } } + private _bindPipe(typeOrBinding): PipeBinding { + let meta = this._pipeResolver.resolve(typeOrBinding); + return PipeBinding.createFromType(typeOrBinding, meta); + } + // Create a hostView as if the compiler encountered . // Used for bootstrapping. compileInHost(componentTypeOrBinding: Type | Binding): Promise { @@ -143,7 +140,7 @@ export class Compiler { this._render.compileHost(directiveMetadata) .then((hostRenderPv) => { var protoViews = this._protoViewFactory.createAppProtoViews( - componentBinding, hostRenderPv, [componentBinding]); + componentBinding, hostRenderPv, [componentBinding], []); return this._compileNestedProtoViews(protoViews, componentType, new Map()); }) .then((appProtoView) => { @@ -186,14 +183,17 @@ export class Compiler { } var boundDirectives = this._removeDuplicatedDirectives( - ListWrapper.map(directives, (directive) => this._bindDirective(directive))); + directives.map(directive => this._bindDirective(directive))); + + var pipes = this._flattenPipes(view); + var boundPipes = pipes.map(pipe => this._bindPipe(pipe)); var renderTemplate = this._buildRenderTemplate(component, view, boundDirectives); resultPromise = this._render.compile(renderTemplate) .then((renderPv) => { var protoViews = this._protoViewFactory.createAppProtoViews( - componentBinding, renderPv, boundDirectives); + componentBinding, renderPv, boundDirectives, boundPipes); return this._compileNestedProtoViews(protoViews, component, componentPath); }) .then((appProtoView) => { @@ -317,12 +317,17 @@ export class Compiler { }); } - private _flattenDirectives(template: View): List { - if (isBlank(template.directives)) return []; + private _flattenPipes(view: View): any[] { + if (isBlank(view.pipes)) return this._defaultPipes; + var pipes = ListWrapper.clone(this._defaultPipes); + this._flattenList(view.pipes, pipes); + return pipes; + } + private _flattenDirectives(view: View): List { + if (isBlank(view.directives)) return []; var directives = []; - this._flattenList(template.directives, directives); - + this._flattenList(view.directives, directives); return directives; } diff --git a/modules/angular2/src/core/compiler/element_injector.ts b/modules/angular2/src/core/compiler/element_injector.ts index adde9c88b1b5..289e245f1c7e 100644 --- a/modules/angular2/src/core/compiler/element_injector.ts +++ b/modules/angular2/src/core/compiler/element_injector.ts @@ -42,14 +42,11 @@ import {ElementRef} from './element_ref'; import {TemplateRef} from './template_ref'; import {Directive, Component, LifecycleEvent} from 'angular2/src/core/annotations_impl/annotations'; import {hasLifecycleHook} from './directive_lifecycle_reflector'; -import { - ChangeDetector, - ChangeDetectorRef, - Pipes -} from 'angular2/src/change_detection/change_detection'; +import {ChangeDetector, ChangeDetectorRef} from 'angular2/src/change_detection/change_detection'; import {QueryList} from './query_list'; import {reflector} from 'angular2/src/reflection/reflection'; import {DirectiveMetadata} from 'angular2/src/render/api'; +import {PipeBinding} from '../pipes/pipe_binding'; var _staticKeys; @@ -59,7 +56,6 @@ export class StaticKeys { viewContainerId: number; changeDetectorRefId: number; elementRefId: number; - pipesKey: Key; constructor() { this.viewManagerId = Key.get(avmModule.AppViewManager).id; @@ -67,8 +63,6 @@ export class StaticKeys { this.viewContainerId = Key.get(ViewContainerRef).id; this.changeDetectorRefId = Key.get(ChangeDetectorRef).id; this.elementRefId = Key.get(ElementRef).id; - // not an id because the public API of injector works only with keys and tokens - this.pipesKey = Key.get(Pipes); } static instance(): StaticKeys { @@ -427,7 +421,7 @@ class _Context { export class ElementInjector extends TreeNode implements DependencyProvider { private _host: ElementInjector; - private _preBuiltObjects = null; + private _preBuiltObjects: PreBuiltObjects = null; // Queries are added during construction or linking with a new parent. // They are removed only through unlinking. @@ -483,19 +477,35 @@ export class ElementInjector extends TreeNode implements Depend this._host = host; this._preBuiltObjects = preBuiltObjects; - this._reattachInjectors(imperativelyCreatedInjector); - this._strategy.hydrate(); - if (isPresent(host)) { this._addViewQueries(host); } + this._reattachInjectors(imperativelyCreatedInjector); + this._strategy.hydrate(); + this._addDirectivesToQueries(); this._addVarBindingsToQueries(); + // TODO(rado): optimize this call, if view queries are not moved around, + // simply appending to the query list is faster than updating. + this._updateViewQueries(); + this.hydrated = true; } + private _updateViewQueries() { + if (isPresent(this._query0) && this._query0.isViewQuery) { + this._query0.update(); + } + if (isPresent(this._query1) && this._query1.isViewQuery) { + this._query1.update(); + } + if (isPresent(this._query2) && this._query2.isViewQuery) { + this._query2.update(); + } + } + private _debugContext(): any { var p = this._preBuiltObjects; var index = p.elementRef.boundElementIndex - p.view.elementOffset; @@ -541,11 +551,6 @@ export class ElementInjector extends TreeNode implements Depend injector.internalStrategy.attach(parentInjector, isBoundary); } - getPipes(): Pipes { - var pipesKey = StaticKeys.instance().pipesKey; - return this._injector.getOptional(pipesKey); - } - hasVariableBinding(name: string): boolean { var vb = this._proto.directiveVariableBindings; return isPresent(vb) && vb.has(name); @@ -589,49 +594,55 @@ export class ElementInjector extends TreeNode implements Depend getDependency(injector: Injector, binding: ResolvedBinding, dep: Dependency): any { var key: Key = dep.key; - if (!(dep instanceof DirectiveDependency)) return undefinedValue; - if (!(binding instanceof DirectiveBinding)) return undefinedValue; + if (binding instanceof DirectiveBinding) { + var dirDep = dep; + var dirBin = binding; + var staticKeys = StaticKeys.instance(); - var dirDep = dep; - var dirBin = binding; - var staticKeys = StaticKeys.instance(); + if (key.id === staticKeys.viewManagerId) return this._preBuiltObjects.viewManager; - if (key.id === staticKeys.viewManagerId) return this._preBuiltObjects.viewManager; + if (isPresent(dirDep.attributeName)) return this._buildAttribute(dirDep); - if (isPresent(dirDep.attributeName)) return this._buildAttribute(dirDep); + if (isPresent(dirDep.queryDecorator)) return this._findQuery(dirDep.queryDecorator).list; - if (isPresent(dirDep.queryDecorator)) return this._findQuery(dirDep.queryDecorator).list; + if (dirDep.key.id === StaticKeys.instance().changeDetectorRefId) { + // We provide the component's view change detector to components and + // the surrounding component's change detector to directives. + if (dirBin.metadata.type === DirectiveMetadata.COMPONENT_TYPE) { + var componentView = this._preBuiltObjects.view.getNestedView( + this._preBuiltObjects.elementRef.boundElementIndex); + return componentView.changeDetector.ref; + } else { + return this._preBuiltObjects.view.changeDetector.ref; + } + } - if (dirDep.key.id === StaticKeys.instance().changeDetectorRefId) { - // We provide the component's view change detector to components and - // the surrounding component's change detector to directives. - if (dirBin.metadata.type === DirectiveMetadata.COMPONENT_TYPE) { - var componentView = this._preBuiltObjects.view.getNestedView( - this._preBuiltObjects.elementRef.boundElementIndex); - return componentView.changeDetector.ref; - } else { - return this._preBuiltObjects.view.changeDetector.ref; + if (dirDep.key.id === StaticKeys.instance().elementRefId) { + return this.getElementRef(); } - } - if (dirDep.key.id === StaticKeys.instance().elementRefId) { - return this.getElementRef(); - } + if (dirDep.key.id === StaticKeys.instance().viewContainerId) { + return this.getViewContainerRef(); + } - if (dirDep.key.id === StaticKeys.instance().viewContainerId) { - return this.getViewContainerRef(); - } + if (dirDep.key.id === StaticKeys.instance().templateRefId) { + if (isBlank(this._preBuiltObjects.templateRef)) { + if (dirDep.optional) { + return null; + } - if (dirDep.key.id === StaticKeys.instance().templateRefId) { - if (isBlank(this._preBuiltObjects.templateRef)) { - if (dirDep.optional) { - return null; + throw new NoBindingError(null, dirDep.key); } + return this._preBuiltObjects.templateRef; + } - throw new NoBindingError(null, dirDep.key); + } else if (binding instanceof PipeBinding) { + if (dep.key.id === StaticKeys.instance().changeDetectorRefId) { + var componentView = this._preBuiltObjects.view.getNestedView( + this._preBuiltObjects.elementRef.boundElementIndex); + return componentView.changeDetector.ref; } - return this._preBuiltObjects.templateRef; } return undefinedValue; @@ -656,22 +667,19 @@ export class ElementInjector extends TreeNode implements Depend } private _addViewQueries(host: ElementInjector): void { - if (isPresent(host._query0) && host._query0.originator == host && - host._query0.query.isViewQuery) - this._addViewQuery(host._query0); - if (isPresent(host._query1) && host._query1.originator == host && - host._query1.query.isViewQuery) - this._addViewQuery(host._query1); - if (isPresent(host._query2) && host._query2.originator == host && - host._query2.query.isViewQuery) - this._addViewQuery(host._query2); - } - - private _addViewQuery(queryRef: QueryRef): void { - // TODO(rado): Replace this.parent check with distanceToParent = 1 when - // https://github.com/angular/angular/issues/2707 is fixed. - if (!queryRef.query.descendants && isPresent(this.parent)) return; - this._assignQueryRef(queryRef); + this._addViewQuery(host._query0, host); + this._addViewQuery(host._query1, host); + this._addViewQuery(host._query2, host); + } + + private _addViewQuery(queryRef: QueryRef, host: ElementInjector): void { + if (isBlank(queryRef) || !queryRef.isViewQuery || this._hasQuery(queryRef)) return; + if (host._query0.originator == host) { + // TODO(rado): Replace this.parent check with distanceToParent = 1 when + // https://github.com/angular/angular/issues/2707 is fixed. + if (!queryRef.query.descendants && isPresent(this.parent)) return; + this._assignQueryRef(queryRef); + } } private _addVarBindingsToQueries(): void { @@ -699,6 +707,7 @@ export class ElementInjector extends TreeNode implements Depend private _addDirectivesToQuery(queryRef: QueryRef): void { if (isBlank(queryRef) || queryRef.query.isVarBindingQuery) return; + if (queryRef.isViewQuery && queryRef.originator == this) return; var matched = []; this.addDirectivesMatchingQuery(queryRef.query, matched); @@ -719,6 +728,10 @@ export class ElementInjector extends TreeNode implements Depend } addDirectivesMatchingQuery(query: Query, list: any[]): void { + var templateRef = this._preBuiltObjects.templateRef; + if (query.selector === TemplateRef && isPresent(templateRef)) { + list.push(templateRef); + } this._strategy.addDirectivesMatchingQuery(query, list); } @@ -755,42 +768,37 @@ export class ElementInjector extends TreeNode implements Depend this._addParentQueries(); } + unlink(): void { + var parent = this.parent; + this.remove(); + this._removeParentQueries(parent); + } + private _addParentQueries(): void { if (isBlank(this.parent)) return; - if (isPresent(this.parent._query0) && !this.parent._query0.query.isViewQuery) { - this._addQueryToTree(this.parent._query0); - if (this.hydrated) this.parent._query0.update(); - } - if (isPresent(this.parent._query1) && !this.parent._query1.query.isViewQuery) { - this._addQueryToTree(this.parent._query1); - if (this.hydrated) this.parent._query1.update(); - } - if (isPresent(this.parent._query2) && !this.parent._query2.query.isViewQuery) { - this._addQueryToTree(this.parent._query2); - if (this.hydrated) this.parent._query2.update(); - } + this._addParentQuery(this.parent._query0); + this._addParentQuery(this.parent._query1); + this._addParentQuery(this.parent._query2); } - unlink(): void { - var queriesToUpdate = []; - if (isPresent(this.parent._query0)) { - this._pruneQueryFromTree(this.parent._query0); - queriesToUpdate.push(this.parent._query0); - } - if (isPresent(this.parent._query1)) { - this._pruneQueryFromTree(this.parent._query1); - queriesToUpdate.push(this.parent._query1); - } - if (isPresent(this.parent._query2)) { - this._pruneQueryFromTree(this.parent._query2); - queriesToUpdate.push(this.parent._query2); + private _addParentQuery(query): void { + if (isPresent(query) && !this._hasQuery(query)) { + this._addQueryToTree(query); + if (this.hydrated) query.update(); } + } - this.remove(); - // TODO(rado): update should work on view queries too, however currently it - // is not implemented, so we filter to non-view queries. - var nonViewQueries = ListWrapper.filter(queriesToUpdate, (q) => !q.query.isViewQuery); - ListWrapper.forEach(nonViewQueries, (q) => q.update()); + private _removeParentQueries(parent: ElementInjector): void { + this._removeParentQuery(parent._query0); + this._removeParentQuery(parent._query1); + this._removeParentQuery(parent._query2); + } + + private _removeParentQuery(query: QueryRef) { + if (isPresent(query)) { + this._pruneQueryFromTree(query); + query.update(); + } } private _pruneQueryFromTree(query: QueryRef): void { @@ -853,6 +861,12 @@ export class ElementInjector extends TreeNode implements Depend getHost(): ElementInjector { return this._host; } getBoundElementIndex(): number { return this._proto.index; } + + getRootViewInjectors(): ElementInjector[] { + var view = this._preBuiltObjects.view; + return view.getNestedView(view.elementOffset + this.getBoundElementIndex()) + .rootElementInjectors; + } } interface _ElementInjectorStrategy { @@ -1134,9 +1148,19 @@ export class QueryRef { constructor(public query: Query, public list: QueryList, public originator: ElementInjector) {} + get isViewQuery(): boolean { return this.query.isViewQuery; } + update(): void { var aggregator = []; - this.visit(this.originator, aggregator); + if (this.query.isViewQuery) { + // intentionally skipping originator for view queries. + var rootViewInjectors = this.originator.getRootViewInjectors(); + for (var i = 0; i < rootViewInjectors.length; i++) { + this.visit(rootViewInjectors[i], aggregator); + } + } else { + this.visit(this.originator, aggregator); + } this.list.reset(aggregator); } diff --git a/modules/angular2/src/core/compiler/pipe_resolver.ts b/modules/angular2/src/core/compiler/pipe_resolver.ts new file mode 100644 index 000000000000..a18923bb644f --- /dev/null +++ b/modules/angular2/src/core/compiler/pipe_resolver.ts @@ -0,0 +1,30 @@ +import {resolveForwardRef, Injectable} from 'angular2/di'; +import {Type, isPresent, BaseException, stringify} from 'angular2/src/facade/lang'; +import {Pipe} from '../annotations_impl/annotations'; +import {reflector} from 'angular2/src/reflection/reflection'; + +/** + * Resolve a `Type` for {@link Pipe}. + * + * This interface can be overridden by the application developer to create custom behavior. + * + * See {@link Compiler} + */ +@Injectable() +export class PipeResolver { + /** + * Return {@link Pipe} for a given `Type`. + */ + resolve(type: Type): Pipe { + var metas = reflector.annotations(resolveForwardRef(type)); + if (isPresent(metas)) { + for (var i = 0; i < metas.length; i++) { + var annotation = metas[i]; + if (annotation instanceof Pipe) { + return annotation; + } + } + } + throw new BaseException(`No Pipe decorator found on ${stringify(type)}`); + } +} diff --git a/modules/angular2/src/core/compiler/proto_view_factory.ts b/modules/angular2/src/core/compiler/proto_view_factory.ts index 2f1bb11f9ba5..a510865afe12 100644 --- a/modules/angular2/src/core/compiler/proto_view_factory.ts +++ b/modules/angular2/src/core/compiler/proto_view_factory.ts @@ -15,6 +15,9 @@ import { ASTWithSource } from 'angular2/src/change_detection/change_detection'; +import {PipeBinding} from 'angular2/src/core/pipes/pipe_binding'; +import {ProtoPipes} from 'angular2/src/core/pipes/pipes'; + import * as renderApi from 'angular2/src/render/api'; import {AppProtoView} from './view'; import {ElementBinder} from './element_binder'; @@ -160,7 +163,7 @@ export class ProtoViewFactory { createAppProtoViews(hostComponentBinding: DirectiveBinding, rootRenderProtoView: renderApi.ProtoViewDto, - allDirectives: List): AppProtoView[] { + allDirectives: List, pipes: PipeBinding[]): AppProtoView[] { var allRenderDirectiveMetadata = ListWrapper.map(allDirectives, directiveBinding => directiveBinding.metadata); var nestedPvsWithIndex = _collectNestedProtoViews(rootRenderProtoView); @@ -177,7 +180,7 @@ export class ProtoViewFactory { ListWrapper.forEach(nestedPvsWithIndex, (pvWithIndex: RenderProtoViewWithIndex) => { var appProtoView = _createAppProtoView(pvWithIndex.renderProtoView, protoChangeDetectors[pvWithIndex.index], - nestedPvVariableBindings[pvWithIndex.index], allDirectives); + nestedPvVariableBindings[pvWithIndex.index], allDirectives, pipes); if (isPresent(pvWithIndex.parentIndex)) { var parentView = appProtoViews[pvWithIndex.parentIndex]; parentView.elementBinders[pvWithIndex.boundElementIndex].nestedProtoView = appProtoView; @@ -252,14 +255,16 @@ function _getChangeDetectorDefinitions( function _createAppProtoView( renderProtoView: renderApi.ProtoViewDto, protoChangeDetector: ProtoChangeDetector, - variableBindings: Map, allDirectives: List): AppProtoView { + variableBindings: Map, allDirectives: List, + pipes: PipeBinding[]): AppProtoView { var elementBinders = renderProtoView.elementBinders; // Embedded ProtoViews that contain `` will be merged into their parents and use // a RenderFragmentRef. I.e. renderProtoView.transitiveNgContentCount > 0. + var protoPipes = new ProtoPipes(pipes); var protoView = new AppProtoView( renderProtoView.type, renderProtoView.transitiveNgContentCount > 0, renderProtoView.render, protoChangeDetector, variableBindings, createVariableLocations(elementBinders), - renderProtoView.textBindings.length); + renderProtoView.textBindings.length, protoPipes); _createElementBinders(protoView, elementBinders, allDirectives); _bindDirectiveEvents(protoView, elementBinders); @@ -361,10 +366,13 @@ function _createProtoElementInjector(binderIndex, parentPeiWithDistance, renderE componentDirectiveBinding, directiveBindings) { var protoElementInjector = null; // Create a protoElementInjector for any element that either has bindings *or* has one - // or more var- defined. Elements with a var- defined need a their own element injector - // so that, when hydrating, $implicit can be set to the element. + // or more var- defined *or* for