From 406db413247f868d2f48be27ff22d2bce82fd1d1 Mon Sep 17 00:00:00 2001 From: Vojta Jina Date: Thu, 30 Oct 2014 23:53:20 +0100 Subject: [PATCH 0001/1265] WIP: rename to *.ats --- modules/rtts_assert/src/{rtts_assert.es6 => rtts_assert.ats} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename modules/rtts_assert/src/{rtts_assert.es6 => rtts_assert.ats} (100%) diff --git a/modules/rtts_assert/src/rtts_assert.es6 b/modules/rtts_assert/src/rtts_assert.ats similarity index 100% rename from modules/rtts_assert/src/rtts_assert.es6 rename to modules/rtts_assert/src/rtts_assert.ats From 09e0b0ac6ffa39844303b61eba2dae4cbb11debe Mon Sep 17 00:00:00 2001 From: Brian Ford Date: Thu, 20 Aug 2015 11:17:36 -0700 Subject: [PATCH 0002/1265] chore(benchmarks): disable broken benchmarks Remove once https://github.com/angular/angular/issues/3757 is resolved --- protractor-dart2js.conf.js | 7 +++++-- protractor-js.conf.js | 4 ++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/protractor-dart2js.conf.js b/protractor-dart2js.conf.js index 00f274da7185..f6b547187d0d 100644 --- a/protractor-dart2js.conf.js +++ b/protractor-dart2js.conf.js @@ -10,8 +10,11 @@ config.exclude.push( // TODO: remove this line when largetable dart has been added 'dist/js/cjs/benchmarks_external/e2e_test/largetable_perf.js', 'dist/js/cjs/benchmarks_external/e2e_test/polymer_tree_perf.js', - 'dist/js/cjs/benchmarks_external/e2e_test/react_tree_perf.js' - + 'dist/js/cjs/benchmarks_external/e2e_test/react_tree_perf.js', + // TODO: remove once https://github.com/angular/angular/issues/3757 is resolved + 'dist/js/cjs/benchmarks_external/e2e_test/compiler_perf.js', + 'dist/js/cjs/benchmarks_external/e2e_test/naive_infinite_scroll.js', + 'dist/js/cjs/benchmarks_external/e2e_test/tree.js' ); data.createBenchpressRunner({ lang: 'dart' }); diff --git a/protractor-js.conf.js b/protractor-js.conf.js index b032bfad2f19..0695569ffe31 100644 --- a/protractor-js.conf.js +++ b/protractor-js.conf.js @@ -5,6 +5,10 @@ config.baseUrl = 'http://localhost:8001/'; // TODO: remove exclusion when JS verison of scrolling benchmark is available config.exclude.push('dist/js/cjs/benchmarks_external/e2e_test/naive_infinite_scroll_spec.js'); config.exclude.push('dist/js/cjs/benchmarks_external/e2e_test/naive_infinite_scroll_perf.js'); +// TODO: remove once https://github.com/angular/angular/issues/3757 is resolved +config.exclude.push('dist/js/cjs/benchmarks_external/e2e_test/compiler_perf.js'); +config.exclude.push('dist/js/cjs/benchmarks_external/e2e_test/naive_infinite_scroll.js'); +config.exclude.push('dist/js/cjs/benchmarks_external/e2e_test/tree.js'); data.createBenchpressRunner({ lang: 'js' }); From 964884e76189d892484481430f81dc97bd196c20 Mon Sep 17 00:00:00 2001 From: Brian Ford Date: Thu, 20 Aug 2015 13:19:18 -0700 Subject: [PATCH 0003/1265] Revert "Revert "refactor(router): move ROUTE_DATA token into own file"" This reverts commit abb3bd266b3defc2c1030f3d8f0dff92df2634c4. --- modules/angular2/router.ts | 1 + modules/angular2/src/router/route_config_decorator.ts | 9 +-------- modules/angular2/src/router/route_config_impl.ts | 5 +---- modules/angular2/src/router/route_data.ts | 4 ++++ modules/angular2/src/router/router_outlet.ts | 2 +- 5 files changed, 8 insertions(+), 13 deletions(-) create mode 100644 modules/angular2/src/router/route_data.ts diff --git a/modules/angular2/router.ts b/modules/angular2/router.ts index e47435e654de..8e938a4b281c 100644 --- a/modules/angular2/router.ts +++ b/modules/angular2/router.ts @@ -22,6 +22,7 @@ export {CanActivate} from './src/router/lifecycle_annotations'; export {Instruction, ComponentInstruction} from './src/router/instruction'; export {Url} from './src/router/url_parser'; export {OpaqueToken, Type} from 'angular2/angular2'; +export {ROUTE_DATA} from './src/router/route_data'; import {LocationStrategy} from './src/router/location_strategy'; import {HTML5LocationStrategy} from './src/router/html5_location_strategy'; diff --git a/modules/angular2/src/router/route_config_decorator.ts b/modules/angular2/src/router/route_config_decorator.ts index badeba28c009..ba518e6b7dc8 100644 --- a/modules/angular2/src/router/route_config_decorator.ts +++ b/modules/angular2/src/router/route_config_decorator.ts @@ -2,13 +2,6 @@ import {RouteConfig as RouteConfigAnnotation, RouteDefinition} from './route_con import {makeDecorator} from 'angular2/src/util/decorators'; import {List} from 'angular2/src/facade/collection'; -export { - Route, - Redirect, - AuxRoute, - AsyncRoute, - RouteDefinition, - ROUTE_DATA -} from './route_config_impl'; +export {Route, Redirect, AuxRoute, AsyncRoute, RouteDefinition} from './route_config_impl'; export var RouteConfig: (configs: List) => ClassDecorator = makeDecorator(RouteConfigAnnotation); diff --git a/modules/angular2/src/router/route_config_impl.ts b/modules/angular2/src/router/route_config_impl.ts index e64d9787401f..d4d2548b34b1 100644 --- a/modules/angular2/src/router/route_config_impl.ts +++ b/modules/angular2/src/router/route_config_impl.ts @@ -1,10 +1,7 @@ -import {CONST, CONST_EXPR, Type} from 'angular2/src/facade/lang'; +import {CONST, Type} from 'angular2/src/facade/lang'; import {List} from 'angular2/src/facade/collection'; import {RouteDefinition} from './route_definition'; export {RouteDefinition} from './route_definition'; -import {OpaqueToken} from 'angular2/di'; - -export const ROUTE_DATA: OpaqueToken = CONST_EXPR(new OpaqueToken('routeData')); /** * You use the RouteConfig annotation to add routes to a component. diff --git a/modules/angular2/src/router/route_data.ts b/modules/angular2/src/router/route_data.ts new file mode 100644 index 000000000000..f7cba6aa0229 --- /dev/null +++ b/modules/angular2/src/router/route_data.ts @@ -0,0 +1,4 @@ +import {OpaqueToken} from 'angular2/di'; +import {CONST_EXPR} from 'angular2/src/facade/lang'; + +export const ROUTE_DATA: OpaqueToken = CONST_EXPR(new OpaqueToken('routeData')); diff --git a/modules/angular2/src/router/router_outlet.ts b/modules/angular2/src/router/router_outlet.ts index 0fc9af59dd4e..2fa1772cf1ee 100644 --- a/modules/angular2/src/router/router_outlet.ts +++ b/modules/angular2/src/router/router_outlet.ts @@ -8,7 +8,7 @@ import {Injector, bind, Dependency, UNDEFINED} from 'angular2/di'; import * as routerMod from './router'; import {Instruction, ComponentInstruction, RouteParams} from './instruction'; -import {ROUTE_DATA} from './route_config_impl'; +import {ROUTE_DATA} from './route_data'; import * as hookMod from './lifecycle_annotations'; import {hasLifecycleHook} from './route_lifecycle_reflector'; From 06487237e57c69d3de3fa00a3b63503c0446f638 Mon Sep 17 00:00:00 2001 From: Brian Ford Date: Thu, 20 Aug 2015 13:19:34 -0700 Subject: [PATCH 0004/1265] Revert "Revert "feat(router): add angular 1.x router"" This reverts commit 298f1fb6a60a799d6c8e0c34bae71a55f1207210. --- .travis.yml | 1 + gulpfile.js | 35 + karma-dart.conf.js | 1 + karma-js.conf.js | 1 + modules/angular1_router/build.js | 123 +++ modules/angular1_router/index.html | 22 + modules/angular1_router/karma-router.conf.js | 28 + modules/angular1_router/lib/facades.es5 | 305 +++++++ modules/angular1_router/src/ng_outlet.js | 432 ++++++++++ .../test/component_mapper_spec.js | 77 ++ .../test/controller_introspector_spec.js | 38 + .../angular1_router/test/ng_outlet_spec.js | 800 ++++++++++++++++++ modules/angular1_router/test/util.es5.js | 85 ++ package.json | 2 + scripts/ci/build_router.sh | 11 + scripts/ci/test_router.sh | 10 + tools/broccoli/trees/browser_tree.ts | 1 + tools/broccoli/trees/dart_tree.ts | 6 +- tools/broccoli/trees/node_tree.ts | 3 +- 19 files changed, 1977 insertions(+), 4 deletions(-) create mode 100644 modules/angular1_router/build.js create mode 100644 modules/angular1_router/index.html create mode 100644 modules/angular1_router/karma-router.conf.js create mode 100644 modules/angular1_router/lib/facades.es5 create mode 100644 modules/angular1_router/src/ng_outlet.js create mode 100644 modules/angular1_router/test/component_mapper_spec.js create mode 100644 modules/angular1_router/test/controller_introspector_spec.js create mode 100644 modules/angular1_router/test/ng_outlet_spec.js create mode 100644 modules/angular1_router/test/util.es5.js create mode 100755 scripts/ci/build_router.sh create mode 100755 scripts/ci/test_router.sh diff --git a/.travis.yml b/.travis.yml index 428b8ed1be99..db7f319fd376 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,6 +36,7 @@ env: - MODE=saucelabs DART_CHANNEL=dev - MODE=dart_experimental DART_CHANNEL=dev - MODE=js DART_CHANNEL=dev + - MODE=router DART_CHANNEL=dev - MODE=lint DART_CHANNEL=dev matrix: diff --git a/gulpfile.js b/gulpfile.js index 6dc1b77cc086..92ba289cabfb 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -37,6 +37,7 @@ var util = require('./tools/build/util'); var bundler = require('./tools/build/bundle'); var replace = require('gulp-replace'); var insert = require('gulp-insert'); +var buildRouter = require('./modules/angular1_router/build'); var uglify = require('gulp-uglify'); var shouldLog = require('./tools/build/logging'); var tslint = require('gulp-tslint'); @@ -604,6 +605,34 @@ gulp.task('!test.unit.js/karma-run', function(done) { runKarma('karma-js.conf.js', done); }); +gulp.task('test.unit.router', function (done) { + runSequence( + '!test.unit.router/karma-server', + function() { + watch('modules/**', [ + 'buildRouter.dev', + '!test.unit.router/karma-run' + ]); + } + ); +}); + +gulp.task('!test.unit.router/karma-server', function() { + karma.server.start({configFile: __dirname + '/modules/angular1_router/karma-router.conf.js'}); +}); + + +gulp.task('!test.unit.router/karma-run', function(done) { + karma.runner.run({configFile: __dirname + '/modules/angular1_router/karma-router.conf.js'}, function(exitCode) { + // ignore exitCode, we don't want to fail the build in the interactive (non-ci) mode + // karma will print all test failures + done(); + }); +}); + +gulp.task('buildRouter.dev', function () { + buildRouter(); +}); gulp.task('test.unit.dart', function (done) { runSequence( @@ -641,6 +670,12 @@ gulp.task('!test.unit.dart/karma-server', function() { }); +gulp.task('test.unit.router/ci', function (done) { + var browserConf = getBrowsersFromCLI(); + karma.server.start({configFile: __dirname + '/modules/angular1_router/karma-router.conf.js', + singleRun: true, reporters: ['dots'], browsers: browserConf.browsersToRun}, done); +}); + gulp.task('test.unit.js/ci', function (done) { var browserConf = getBrowsersFromCLI(); karma.server.start({configFile: __dirname + '/karma-js.conf.js', diff --git a/karma-dart.conf.js b/karma-dart.conf.js index e07c7212918a..c3e42ea5c7f4 100644 --- a/karma-dart.conf.js +++ b/karma-dart.conf.js @@ -27,6 +27,7 @@ module.exports = function(config) { exclude: [ 'dist/dart/**/packages/**', + 'modules/angular1_router/**' ], karmaDartImports: { diff --git a/karma-js.conf.js b/karma-js.conf.js index ba4e6e531029..f607206ba66c 100644 --- a/karma-js.conf.js +++ b/karma-js.conf.js @@ -31,6 +31,7 @@ module.exports = function(config) { exclude: [ 'dist/js/dev/es5/**/e2e_test/**', + 'dist/angular1_router.js' ], customLaunchers: sauceConf.customLaunchers, diff --git a/modules/angular1_router/build.js b/modules/angular1_router/build.js new file mode 100644 index 000000000000..62c790ec7cf9 --- /dev/null +++ b/modules/angular1_router/build.js @@ -0,0 +1,123 @@ +var fs = require('fs'); +var ts = require('typescript'); + +var files = [ + 'lifecycle_annotations_impl.ts', + 'url_parser.ts', + 'path_recognizer.ts', + 'route_config_impl.ts', + 'async_route_handler.ts', + 'sync_route_handler.ts', + 'route_recognizer.ts', + 'instruction.ts', + 'route_config_nomalizer.ts', + 'route_lifecycle_reflector.ts', + 'route_registry.ts', + 'router.ts' +]; + +var PRELUDE = '(function(){\n'; +var POSTLUDE = '\n}());\n'; +var FACADES = fs.readFileSync(__dirname + '/lib/facades.es5', 'utf8'); +var TRACEUR_RUNTIME = fs.readFileSync(__dirname + '/../../node_modules/traceur/bin/traceur-runtime.js', 'utf8'); +var DIRECTIVES = fs.readFileSync(__dirname + '/src/ng_outlet.js', 'utf8'); +function main() { + var dir = __dirname + '/../angular2/src/router/'; + + var out = ''; + + var sharedCode = ''; + files.forEach(function (file) { + var moduleName = 'router/' + file.replace(/\.ts$/, ''); + + sharedCode += transform(moduleName, fs.readFileSync(dir + file, 'utf8')); + }); + + out += "angular.module('ngComponentRouter')"; + out += angularFactory('$router', ['$q', '$location', '$$controllerIntrospector', + '$browser', '$rootScope', '$injector'], [ + FACADES, + "var exports = {Injectable: function () {}};", + "var require = function () {return exports;};", + sharedCode, + "var RouteConfig = exports.RouteConfig;", + "angular.annotations = {RouteConfig: RouteConfig, CanActivate: exports.CanActivate};", + "angular.stringifyInstruction = exports.stringifyInstruction;", + "var RouteRegistry = exports.RouteRegistry;", + "var RootRouter = exports.RootRouter;", + //TODO: move this code into a templated JS file + "var registry = new RouteRegistry();", + "var location = new Location();", + + "$$controllerIntrospector(function (name, constructor) {", + "if (constructor.$canActivate) {", + "constructor.annotations = constructor.annotations || [];", + "constructor.annotations.push(new angular.annotations.CanActivate(function (instruction) {", + "return $injector.invoke(constructor.$canActivate, constructor, {", + "$routeParams: instruction.component ? instruction.component.params : instruction.params", + "});", + "}));", + "}", + "if (constructor.annotations) {", + "constructor.annotations.forEach(function(annotation) {", + "if (annotation instanceof RouteConfig) {", + "annotation.configs.forEach(function (config) {", + "registry.config(constructor, config);", + "});", + "}", + "});", + "}", + "});", + + "var router = new RootRouter(registry, undefined, location, new Object());", + "$rootScope.$watch(function () { return $location.path(); }, function (path) { router.navigate(path); });", + + "return router;" + ].join('\n')); + + return PRELUDE + TRACEUR_RUNTIME + DIRECTIVES + out + POSTLUDE; +} + + +/* + * Given a directory name and a file's TypeScript content, return an object with the ES5 code, + * sourcemap, anf exported variable identifier name for the content. + */ +var IMPORT_RE = new RegExp("import \\{?([\\w\\n_, ]+)\\}? from '(.+)';?", 'g'); +function transform(dir, contents) { + contents = contents.replace(IMPORT_RE, function (match, imports, includePath) { + //TODO: remove special-case + if (isFacadeModule(includePath) || includePath === './router_outlet') { + return ''; + } + return match; + }); + return ts.transpile(contents, { + target: ts.ScriptTarget.ES5, + module: ts.ModuleKind.CommonJS, + sourceRoot: dir + }); +} + + +function angularFactory(name, deps, body) { + return ".factory('" + name + "', [" + + deps.map(function (service) { + return "'" + service + "', "; + }).join('') + + "function (" + deps.join(', ') + ") {\n" + body + "\n}])"; +} + + +function isFacadeModule(modulePath) { + return modulePath.indexOf('facade') > -1 || + modulePath === 'angular2/src/reflection/reflection'; +} + +module.exports = function () { + var dist = __dirname + '/../../dist'; + if (!fs.existsSync(dist)) { + fs.mkdirSync(dist); + } + fs.writeFileSync(dist + '/angular_1_router.js', main(files)); +}; diff --git a/modules/angular1_router/index.html b/modules/angular1_router/index.html new file mode 100644 index 000000000000..63dfbe65c390 --- /dev/null +++ b/modules/angular1_router/index.html @@ -0,0 +1,22 @@ + + + + + + + +
+ +
+ + + + + diff --git a/modules/angular1_router/karma-router.conf.js b/modules/angular1_router/karma-router.conf.js new file mode 100644 index 000000000000..d7d014f2659b --- /dev/null +++ b/modules/angular1_router/karma-router.conf.js @@ -0,0 +1,28 @@ +'use strict'; + +var sauceConf = require('../../sauce.conf'); + +// This runs the tests for the router in Angular 1.x + +module.exports = function (config) { + var options = { + frameworks: ['jasmine'], + + files: [ + '../../node_modules/angular/angular.js', + '../../node_modules/angular-animate/angular-animate.js', + '../../node_modules/angular-mocks/angular-mocks.js', + + '../../dist/angular_1_router.js', + + 'test/*.es5.js', + 'test/*_spec.js' + ], + + customLaunchers: sauceConf.customLaunchers, + + browsers: ['ChromeCanary'] + }; + + config.set(options); +}; diff --git a/modules/angular1_router/lib/facades.es5 b/modules/angular1_router/lib/facades.es5 new file mode 100644 index 000000000000..e0d6024e6f50 --- /dev/null +++ b/modules/angular1_router/lib/facades.es5 @@ -0,0 +1,305 @@ +function CONST() { + return (function(target) { + return target; + }); +} + +function IMPLEMENTS(_) { + return (function(t) { + return t; + }); +} + +function CONST_EXPR(expr) { + return expr; +} + +function isPresent (x) { + return !!x; +} + +function isBlank (x) { + return !x; +} + +function isString(obj) { + return typeof obj === 'string'; +} + +function isType (x) { + return typeof x === 'function'; +} + +function isStringMap(obj) { + return typeof obj === 'object' && obj !== null; +} + +function isArray(obj) { + return Array.isArray(obj); +} + +var PromiseWrapper = { + resolve: function (reason) { + return $q.when(reason); + }, + + reject: function (reason) { + return $q.reject(reason); + }, + + catchError: function (promise, fn) { + return promise.then(null, fn); + }, + all: function (promises) { + return $q.all(promises); + } +}; + +var RegExpWrapper = { + create: function(regExpStr, flags) { + flags = flags ? flags.replace(/g/g, '') : ''; + return new RegExp(regExpStr, flags + 'g'); + }, + firstMatch: function(regExp, input) { + regExp.lastIndex = 0; + return regExp.exec(input); + }, + matcher: function (regExp, input) { + regExp.lastIndex = 0; + return { re: regExp, input: input }; + } +}; + +var reflector = { + annotations: function (fn) { + //TODO: implement me + return fn.annotations || []; + } +}; + +var MapWrapper = { + create: function() { + return new Map(); + }, + + get: function(m, k) { + return m.get(k); + }, + + set: function(m, k, v) { + return m.set(k, v); + }, + + contains: function (m, k) { + return m.has(k); + }, + + forEach: function (m, fn) { + return m.forEach(fn); + } +}; + +var StringMapWrapper = { + create: function () { + return {}; + }, + + set: function (m, k, v) { + return m[k] = v; + }, + + get: function (m, k) { + return m.hasOwnProperty(k) ? m[k] : undefined; + }, + + contains: function (m, k) { + return m.hasOwnProperty(k); + }, + + keys: function(map) { + return Object.keys(map); + }, + + isEmpty: function(map) { + for (var prop in map) { + if (map.hasOwnProperty(prop)) { + return false; + } + } + return true; + }, + + delete: function(map, key) { + delete map[key]; + }, + + forEach: function (m, fn) { + for (prop in m) { + if (m.hasOwnProperty(prop)) { + fn(m[prop], prop); + } + } + }, + + equals: function (m1, m2) { + var k1 = Object.keys(m1); + var k2 = Object.keys(m2); + if (k1.length != k2.length) { + return false; + } + var key; + for (var i = 0; i < k1.length; i++) { + key = k1[i]; + if (m1[key] !== m2[key]) { + return false; + } + } + return true; + }, + + merge: function(m1, m2) { + var m = {}; + for (var attr in m1) { + if (m1.hasOwnProperty(attr)) { + m[attr] = m1[attr]; + } + } + for (var attr in m2) { + if (m2.hasOwnProperty(attr)) { + m[attr] = m2[attr]; + } + } + return m; + } +}; + +var List = Array; +var ListWrapper = { + create: function () { + return []; + }, + + push: function (l, v) { + return l.push(v); + }, + + forEach: function (l, fn) { + return l.forEach(fn); + }, + + first: function(array) { + if (!array) + return null; + return array[0]; + }, + + map: function (l, fn) { + return l.map(fn); + }, + + join: function (l, str) { + return l.join(str); + }, + + reduce: function(list, fn, init) { + return list.reduce(fn, init); + }, + + filter: function(array, pred) { + return array.filter(pred); + }, + + concat: function(a, b) { + return a.concat(b); + }, + + slice: function(l) { + var from = arguments[1] !== (void 0) ? arguments[1] : 0; + var to = arguments[2] !== (void 0) ? arguments[2] : null; + return l.slice(from, to === null ? undefined : to); + }, + + maximum: function(list, predicate) { + if (list.length == 0) { + return null; + } + var solution = null; + var maxValue = -Infinity; + for (var index = 0; index < list.length; index++) { + var candidate = list[index]; + if (isBlank(candidate)) { + continue; + } + var candidateValue = predicate(candidate); + if (candidateValue > maxValue) { + solution = candidate; + maxValue = candidateValue; + } + } + return solution; + } +}; + +var StringWrapper = { + equals: function (s1, s2) { + return s1 === s2; + }, + + split: function(s, re) { + return s.split(re); + }, + + substring: function(s, start, end) { + return s.substr(start, end); + }, + + replaceAll: function(s, from, replace) { + return s.replace(from, replace); + }, + + startsWith: function(s, start) { + return s.startsWith(start); + }, + + replaceAllMapped: function(s, from, cb) { + return s.replace(from, function(matches) { + // Remove offset & string from the result array + matches.splice(-2, 2); + // The callback receives match, p1, ..., pn + return cb.apply(null, matches); + }); + }, + + contains: function(s, substr) { + return s.indexOf(substr) != -1; + } + +}; + +//TODO: implement? +// I think it's too heavy to ask 1.x users to bring in Rx for the router... +function EventEmitter() { + +} + +var BaseException = Error; + +var ObservableWrapper = { + callNext: function(){} +}; + +// TODO: https://github.com/angular/angular.js/blob/master/src/ng/browser.js#L227-L265 +var $__router_47_location__ = { + Location: Location +}; + +function Location(){} +Location.prototype.subscribe = function () { + //TODO: implement +}; +Location.prototype.path = function () { + return $location.path(); +}; +Location.prototype.go = function (url) { + return $location.path(url); +}; diff --git a/modules/angular1_router/src/ng_outlet.js b/modules/angular1_router/src/ng_outlet.js new file mode 100644 index 000000000000..7598c8a9ee84 --- /dev/null +++ b/modules/angular1_router/src/ng_outlet.js @@ -0,0 +1,432 @@ +'use strict'; + +/* + * A module for adding new a routing system Angular 1. + */ +angular.module('ngComponentRouter', []) + .factory('$componentMapper', $componentMapperFactory) + .directive('ngOutlet', ngOutletDirective) + .directive('ngOutlet', ngOutletFillContentDirective) + .directive('ngLink', ngLinkDirective) + .directive('a', anchorLinkDirective); // TODO: make the anchor link feature configurable + +/* + * A module for inspecting controller constructors + */ +angular.module('ng') + .provider('$$controllerIntrospector', $$controllerIntrospectorProvider) + .config(controllerProviderDecorator); + +/* + * decorates with routing info + */ +function controllerProviderDecorator($controllerProvider, $$controllerIntrospectorProvider) { + var register = $controllerProvider.register; + $controllerProvider.register = function (name, ctrl) { + $$controllerIntrospectorProvider.register(name, ctrl); + return register.apply(this, arguments); + }; +} + +// TODO: decorate $controller ? +/* + * private service that holds route mappings for each controller + */ +function $$controllerIntrospectorProvider() { + var controllers = []; + var controllersByName = {}; + var onControllerRegistered = null; + return { + register: function (name, constructor) { + if (angular.isArray(constructor)) { + constructor = constructor[constructor.length - 1]; + } + controllersByName[name] = constructor; + constructor.$$controllerName = name; + if (onControllerRegistered) { + onControllerRegistered(name, constructor); + } else { + controllers.push({name: name, constructor: constructor}); + } + }, + $get: ['$componentMapper', function ($componentMapper) { + var fn = function (newOnControllerRegistered) { + onControllerRegistered = function (name, constructor) { + name = $componentMapper.component(name); + return newOnControllerRegistered(name, constructor); + }; + while (controllers.length > 0) { + var rule = controllers.pop(); + onControllerRegistered(rule.name, rule.constructor); + } + }; + + fn.getTypeByName = function (name) { + return controllersByName[name]; + }; + + return fn; + }] + }; +} + + +/** + * @name ngOutlet + * + * @description + * An ngOutlet is where resolved content goes. + * + * ## Use + * + * ```html + *
+ * ``` + * + * The value for the `ngOutlet` attribute is optional. + */ +function ngOutletDirective($animate, $injector, $q, $router, $componentMapper, $controller, + $$controllerIntrospector, $templateRequest) { + var rootRouter = $router; + + return { + restrict: 'AE', + transclude: 'element', + terminal: true, + priority: 400, + require: ['?^^ngOutlet', 'ngOutlet'], + link: outletLink, + controller: function () {}, + controllerAs: '$$ngOutlet' + }; + + function outletLink(scope, $element, attrs, ctrls, $transclude) { + var outletName = attrs.ngOutlet || 'default', + parentCtrl = ctrls[0], + myCtrl = ctrls[1], + router = (parentCtrl && parentCtrl.$$router) || rootRouter; + + var childRouter, + currentInstruction, + currentScope, + currentController, + currentElement, + previousLeaveAnimation; + + function cleanupLastView() { + if (previousLeaveAnimation) { + $animate.cancel(previousLeaveAnimation); + previousLeaveAnimation = null; + } + + if (currentScope) { + currentScope.$destroy(); + currentScope = null; + } + if (currentElement) { + previousLeaveAnimation = $animate.leave(currentElement); + previousLeaveAnimation.then(function () { + previousLeaveAnimation = null; + }); + currentElement = null; + } + } + + router.registerOutlet({ + commit: function (instruction) { + var next; + var componentInstruction = instruction.component; + if (componentInstruction.reuse) { + // todo(shahata): lifecycle - onReuse + next = $q.when(true); + } else { + var self = this; + next = this.deactivate(instruction).then(function () { + return self.activate(componentInstruction); + }); + } + return next.then(function () { + if (childRouter) { + return childRouter.commit(instruction.child); + } else { + return $q.when(true); + } + }); + }, + canReuse: function (nextInstruction) { + var result; + var componentInstruction = nextInstruction.component; + if (!currentInstruction || + currentInstruction.componentType !== componentInstruction.componentType) { + result = false; + } else { + // todo(shahata): lifecycle - canReuse + result = componentInstruction === currentInstruction || + angular.equals(componentInstruction.params, currentInstruction.params); + } + return $q.when(result).then(function (result) { + // TODO: this is a hack + componentInstruction.reuse = result; + return result; + }); + }, + canDeactivate: function (instruction) { + if (currentInstruction && currentController && currentController.canDeactivate) { + return $q.when(currentController.canDeactivate(instruction && instruction.component, currentInstruction)); + } + return $q.when(true); + }, + deactivate: function (instruction) { + // todo(shahata): childRouter.dectivate, dispose component? + var result = $q.when(); + return result.then(function () { + if (currentController && currentController.onDeactivate) { + return currentController.onDeactivate(instruction && instruction.component, currentInstruction); + } + }); + }, + activate: function (instruction) { + var previousInstruction = currentInstruction; + currentInstruction = instruction; + childRouter = router.childRouter(instruction.componentType); + + var controllerConstructor, componentName; + controllerConstructor = instruction.componentType; + componentName = $componentMapper.component(controllerConstructor.$$controllerName); + + var componentTemplateUrl = $componentMapper.template(componentName); + return $templateRequest(componentTemplateUrl).then(function (templateHtml) { + myCtrl.$$router = childRouter; + myCtrl.$$template = templateHtml; + }).then(function () { + var newScope = scope.$new(); + var locals = { + $scope: newScope, + $router: childRouter, + $routeParams: (instruction.params || {}) + }; + + // todo(shahata): controllerConstructor is not minify friendly + currentController = $controller(controllerConstructor, locals); + + var clone = $transclude(newScope, function (clone) { + $animate.enter(clone, null, currentElement || $element); + cleanupLastView(); + }); + + var controllerAs = $componentMapper.controllerAs(componentName) || componentName; + newScope[controllerAs] = currentController; + currentElement = clone; + currentScope = newScope; + + if (currentController.onActivate) { + return currentController.onActivate(instruction, previousInstruction); + } + }); + } + }, outletName); + } +} + +function ngOutletFillContentDirective($compile) { + return { + restrict: 'EA', + priority: -400, + require: 'ngOutlet', + link: function (scope, $element, attrs, ctrl) { + var template = ctrl.$$template; + $element.html(template); + var link = $compile($element.contents()); + link(scope); + } + }; +} + + +/** + * @name ngLink + * @description + * Lets you link to different parts of the app, and automatically generates hrefs. + * + * ## Use + * The directive uses a simple syntax: `ng-link="componentName({ param: paramValue })"` + * + * ## Example + * + * ```js + * angular.module('myApp', ['ngFuturisticRouter']) + * .controller('AppController', ['$router', function($router) { + * $router.config({ path: '/user/:id' component: 'user' }); + * this.user = { name: 'Brian', id: 123 }; + * }); + * ``` + * + * ```html + * + * ``` + */ +function ngLinkDirective($router, $location, $parse) { + var rootRouter = $router; + + return { + require: '?^^ngOutlet', + restrict: 'A', + link: ngLinkDirectiveLinkFn + }; + + function ngLinkDirectiveLinkFn(scope, elt, attrs, ctrl) { + var router = (ctrl && ctrl.$$router) || rootRouter; + if (!router) { + return; + } + + var link = attrs.ngLink || ''; + + function getLink(params) { + return './' + angular.stringifyInstruction(router.generate(params)); + } + + var routeParamsGetter = $parse(link); + // we can avoid adding a watcher if it's a literal + if (routeParamsGetter.constant) { + var params = routeParamsGetter(); + elt.attr('href', getLink(params)); + } else { + scope.$watch(function () { + return routeParamsGetter(scope); + }, function (params) { + elt.attr('href', getLink(params)); + }, true); + } + } +} + + +function anchorLinkDirective($router) { + return { + restrict: 'E', + link: function (scope, element) { + // If the linked element is not an anchor tag anymore, do nothing + if (element[0].nodeName.toLowerCase() !== 'a') { + return; + } + + // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. + var hrefAttrName = Object.prototype.toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? + 'xlink:href' : 'href'; + + element.on('click', function (event) { + if (event.which !== 1) { + return; + } + + var href = element.attr(hrefAttrName); + if (href && $router.recognize(href)) { + $router.navigate(href); + event.preventDefault(); + } + }); + } + }; +} + +/** + * @name $componentMapperFactory + * @description + * + * This lets you configure conventions for what controllers are named and where to load templates from. + * + * The default behavior is to dasherize and serve from `./components`. A component called `myWidget` + * uses a controller named `MyWidgetController` and a template loaded from `./components/my-widget/my-widget.html`. + * + * A component is: + * - a controller + * - a template + * - an optional router + * + * This service makes it easy to group all of them into a single concept. + */ +function $componentMapperFactory() { + + var DEFAULT_SUFFIX = 'Controller'; + + var componentToCtrl = function componentToCtrlDefault(name) { + return name[0].toUpperCase() + name.substr(1) + DEFAULT_SUFFIX; + }; + + var componentToTemplate = function componentToTemplateDefault(name) { + var dashName = dashCase(name); + return './components/' + dashName + '/' + dashName + '.html'; + }; + + var ctrlToComponent = function ctrlToComponentDefault(name) { + return name[0].toLowerCase() + name.substr(1, name.length - DEFAULT_SUFFIX.length - 1); + }; + + var componentToControllerAs = function componentToControllerAsDefault(name) { + return name; + }; + + return { + controllerName: function (name) { + return componentToCtrl(name); + }, + + controllerAs: function (name) { + return componentToControllerAs(name); + }, + + template: function (name) { + return componentToTemplate(name); + }, + + component: function (name) { + return ctrlToComponent(name); + }, + + /** + * @name $componentMapper#setCtrlNameMapping + * @description takes a function for mapping component names to component controller names + */ + setCtrlNameMapping: function (newFn) { + componentToCtrl = newFn; + return this; + }, + + /** + * @name $componentMapper#setCtrlAsMapping + * @description takes a function for mapping component names to controllerAs name in the template + */ + setCtrlAsMapping: function (newFn) { + componentToControllerAs = newFn; + return this; + }, + + /** + * @name $componentMapper#setComponentFromCtrlMapping + * @description takes a function for mapping component controller names to component names + */ + setComponentFromCtrlMapping: function (newFn) { + ctrlToComponent = newFn; + return this; + }, + + /** + * @name $componentMapper#setTemplateMapping + * @description takes a function for mapping component names to component template URLs + */ + setTemplateMapping: function (newFn) { + componentToTemplate = newFn; + return this; + } + }; +} + + +function dashCase(str) { + return str.replace(/([A-Z])/g, function ($1) { + return '-' + $1.toLowerCase(); + }); +} diff --git a/modules/angular1_router/test/component_mapper_spec.js b/modules/angular1_router/test/component_mapper_spec.js new file mode 100644 index 000000000000..bfe9343b0f03 --- /dev/null +++ b/modules/angular1_router/test/component_mapper_spec.js @@ -0,0 +1,77 @@ +'use strict'; + +describe('$componentMapper', function () { + var elt, + $compile, + $rootScope, + $router, + $templateCache; + + function Ctrl() { + this.message = 'howdy'; + } + + beforeEach(function() { + module('ng'); + module('ngComponentRouter'); + module(function ($controllerProvider) { + $controllerProvider.register('myComponentController', Ctrl); + }); + }); + + it('should convert a component name to a controller name', inject(function ($componentMapper) { + expect($componentMapper.controllerName('foo')).toBe('FooController'); + })); + + it('should convert a controller name to a component name', inject(function ($componentMapper) { + expect($componentMapper.component('FooController')).toBe('foo'); + })); + + it('should convert a component name to a template URL', inject(function ($componentMapper) { + expect($componentMapper.template('foo')).toBe('./components/foo/foo.html'); + })); + + it('should work with a controller constructor fn and a template url', inject(function ($componentMapper) { + var routes = {}; + $componentMapper.setCtrlNameMapping(function (name) { + return routes[name].controller; + }); + $componentMapper.setTemplateMapping(function (name) { + return routes[name].templateUrl; + }); + $componentMapper.setCtrlAsMapping(function (name) { + return 'ctrl'; + }); + + routes.myComponent = { + controller: Ctrl, + templateUrl: '/foo' + }; + + inject(function(_$compile_, _$rootScope_, _$router_, _$templateCache_) { + $compile = _$compile_; + $rootScope = _$rootScope_; + $router = _$router_; + $templateCache = _$templateCache_; + }); + + $templateCache.put('/foo', [200, '{{ctrl.message}}', {}]); + + compile(''); + + $router.config([ + { path: '/', component: Ctrl } + ]); + + $router.navigate('/'); + $rootScope.$digest(); + + expect(elt.text()).toBe('howdy'); + })); + + function compile(template) { + elt = $compile('
' + template + '
')($rootScope); + $rootScope.$digest(); + return elt; + } +}); diff --git a/modules/angular1_router/test/controller_introspector_spec.js b/modules/angular1_router/test/controller_introspector_spec.js new file mode 100644 index 000000000000..fa3d8e34da4b --- /dev/null +++ b/modules/angular1_router/test/controller_introspector_spec.js @@ -0,0 +1,38 @@ +'use strict'; + +describe('$$controllerIntrospector', function () { + + var $controllerProvider; + + beforeEach(function() { + module('ng'); + module('ngComponentRouter'); + module(function(_$controllerProvider_) { + $controllerProvider = _$controllerProvider_; + }); + }); + + it('should call the introspector function whenever a controller is registered', inject(function ($$controllerIntrospector) { + var spy = jasmine.createSpy(); + $$controllerIntrospector(spy); + function Ctrl(){} + $controllerProvider.register('SomeController', Ctrl); + + expect(spy).toHaveBeenCalledWith('some', Ctrl); + })); + + it('should call the introspector function whenever a controller is registered with array annotations', inject(function ($$controllerIntrospector) { + var spy = jasmine.createSpy(); + $$controllerIntrospector(spy); + function Ctrl(foo){} + $controllerProvider.register('SomeController', ['foo', Ctrl]); + + expect(spy).toHaveBeenCalledWith('some', Ctrl); + })); + + it('should retrieve a constructor', inject(function ($$controllerIntrospector) { + function Ctrl(foo){} + $controllerProvider.register('SomeController', ['foo', Ctrl]); + expect($$controllerIntrospector.getTypeByName('SomeController')).toBe(Ctrl); + })); +}); diff --git a/modules/angular1_router/test/ng_outlet_spec.js b/modules/angular1_router/test/ng_outlet_spec.js new file mode 100644 index 000000000000..cd89ffc005cb --- /dev/null +++ b/modules/angular1_router/test/ng_outlet_spec.js @@ -0,0 +1,800 @@ +'use strict'; + +describe('ngOutlet', function () { + + var elt, + $compile, + $rootScope, + $router, + $templateCache, + $controllerProvider, + $componentMapperProvider; + + var OneController, TwoController, UserController; + + function instructionFor(componentType) { + return jasmine.objectContaining({componentType: componentType}); + } + + + beforeEach(function () { + module('ng'); + module('ngComponentRouter'); + module(function (_$controllerProvider_, _$componentMapperProvider_) { + $controllerProvider = _$controllerProvider_; + $componentMapperProvider = _$componentMapperProvider_; + }); + + inject(function (_$compile_, _$rootScope_, _$router_, _$templateCache_) { + $compile = _$compile_; + $rootScope = _$rootScope_; + $router = _$router_; + $templateCache = _$templateCache_; + }); + + UserController = registerComponent('user', '
hello {{user.name}}
', function ($routeParams) { + this.name = $routeParams.name; + }); + OneController = registerComponent('one', '
{{one.number}}
', boringController('number', 'one')); + TwoController = registerComponent('two', '
{{two.number}}
', boringController('number', 'two')); + }); + + + it('should work in a simple case', function () { + compile(''); + + $router.config([ + { path: '/', component: OneController } + ]); + + $router.navigate('/'); + $rootScope.$digest(); + + expect(elt.text()).toBe('one'); + }); + + + // See https://github.com/angular/router/issues/105 + xit('should warn when instantiating a component with no controller', function () { + put('noController', '
{{ 2 + 2 }}
'); + $router.config([ + { path: '/', component: 'noController' } + ]); + + spyOn(console, 'warn'); + compile(''); + $router.navigate('/'); + + expect(console.warn).toHaveBeenCalledWith('Could not find controller for', 'NoControllerController'); + expect(elt.text()).toBe('4'); + }); + + + it('should navigate between components with different parameters', function () { + $router.config([ + { path: '/user/:name', component: UserController } + ]); + compile(''); + + $router.navigate('/user/brian'); + $rootScope.$digest(); + expect(elt.text()).toBe('hello brian'); + + $router.navigate('/user/igor'); + $rootScope.$digest(); + expect(elt.text()).toBe('hello igor'); + }); + + + it('should not reactivate a parent when navigating between child components with different parameters', function () { + var spy = jasmine.createSpy('onActivate'); + function ParentController() {} + ParentController.$routeConfig = [ + { path: '/user/:name', component: UserController } + ]; + ParentController.prototype.onActivate = spy; + + registerComponent('parent', 'parent { }', ParentController); + + $router.config([ + { path: '/parent/...', component: ParentController } + ]); + compile(''); + + $router.navigate('/parent/user/brian'); + $rootScope.$digest(); + expect(spy).toHaveBeenCalled(); + expect(elt.text()).toBe('parent { hello brian }'); + + spy.calls.reset(); + + $router.navigate('/parent/user/igor'); + $rootScope.$digest(); + expect(spy).not.toHaveBeenCalled(); + expect(elt.text()).toBe('parent { hello igor }'); + }); + + + it('should work with nested outlets', function () { + var childComponent = registerComponent('childComponent', '
inner {
}
', [ + { path: '/b', component: OneController } + ]); + + $router.config([ + { path: '/a/...', component: childComponent } + ]); + compile('
outer {
}
'); + + $router.navigate('/a/b'); + $rootScope.$digest(); + + expect(elt.text()).toBe('outer { inner { one } }'); + }); + + + it('should work with recursive nested outlets', function () { + put('two', '
recur {
}
'); + $router.config([ + { path: '/recur', component: TwoController }, + { path: '/', component: OneController } + ]); + + compile('
root {
}
'); + $router.navigate('/'); + $rootScope.$digest(); + expect(elt.text()).toBe('root { one }'); + }); + + + it('should allow linking from the parent to the child', function () { + put('one', '
{{number}}
'); + + $router.config([ + { path: '/a', component: OneController }, + { path: '/b', component: TwoController, as: 'two' } + ]); + compile('link | outer {
}'); + + $router.navigate('/a'); + $rootScope.$digest(); + + expect(elt.find('a').attr('href')).toBe('./b'); + }); + + it('should allow linking from the child and the parent', function () { + put('one', ''); + + $router.config([ + { path: '/a', component: OneController }, + { path: '/b', component: TwoController, as: 'two' } + ]); + compile('outer {
}'); + + $router.navigate('/a'); + $rootScope.$digest(); + + expect(elt.find('a').attr('href')).toBe('./b'); + }); + + + it('should allow params in routerLink directive', function () { + put('router', '
outer {
}
'); + put('one', ''); + + $router.config([ + { path: '/a', component: OneController }, + { path: '/b/:param', component: TwoController, as: 'two' } + ]); + compile('
'); + + $router.navigate('/a'); + $rootScope.$digest(); + + expect(elt.find('a').attr('href')).toBe('./b/lol'); + }); + + // TODO: test dynamic links + it('should update the href of links with bound params', function () { + put('router', '
outer {
}
'); + put('one', ''); + + $router.config([ + { path: '/a', component: OneController }, + { path: '/b/:param', component: TwoController, as: 'two' } + ]); + compile('
'); + + $router.navigate('/a'); + $rootScope.$digest(); + + expect(elt.find('a').attr('href')).toBe('./b/one'); + }); + + + it('should run the activate hook of controllers', function () { + var spy = jasmine.createSpy('activate'); + var activate = registerComponent('activate', '', { + onActivate: spy + }); + + $router.config([ + { path: '/a', component: activate } + ]); + compile('
outer {
}
'); + + $router.navigate('/a'); + $rootScope.$digest(); + + expect(spy).toHaveBeenCalled(); + }); + + + it('should pass instruction into the activate hook of a controller', function () { + var spy = jasmine.createSpy('activate'); + var UserController = registerComponent('user', '', { + onActivate: spy + }); + + $router.config([ + { path: '/user/:name', component: UserController } + ]); + compile('
'); + + $router.navigate('/user/brian'); + $rootScope.$digest(); + + expect(spy).toHaveBeenCalledWith(instructionFor(UserController), undefined); + }); + + + it('should pass previous instruction into the activate hook of a controller', function () { + var spy = jasmine.createSpy('activate'); + var activate = registerComponent('activate', '', { + onActivate: spy + }); + + $router.config([ + { path: '/user/:name', component: OneController }, + { path: '/post/:id', component: activate } + ]); + compile('
'); + + $router.navigate('/user/brian'); + $rootScope.$digest(); + $router.navigate('/post/123'); + $rootScope.$digest(); + expect(spy).toHaveBeenCalledWith(instructionFor(activate), + instructionFor(OneController)); + }); + + + it('should inject $scope into the controller constructor', function () { + + var injectedScope; + var UserController = registerComponent('user', '', function ($scope) { + injectedScope = $scope; + }); + + $router.config([ + { path: '/user', component: UserController } + ]); + compile('
'); + + $router.navigate('/user'); + $rootScope.$digest(); + + expect(injectedScope).toBeDefined(); + }); + + + it('should run the deactivate hook of controllers', function () { + var spy = jasmine.createSpy('deactivate'); + var deactivate = registerComponent('deactivate', '', { + onDeactivate: spy + }); + + $router.config([ + { path: '/a', component: deactivate }, + { path: '/b', component: OneController } + ]); + compile('
'); + + $router.navigate('/a'); + $rootScope.$digest(); + $router.navigate('/b'); + $rootScope.$digest(); + expect(spy).toHaveBeenCalled(); + }); + + + it('should pass instructions into the deactivate hook of controllers', function () { + var spy = jasmine.createSpy('deactivate'); + var deactivate = registerComponent('deactivate', '', { + onDeactivate: spy + }); + + $router.config([ + { path: '/user/:name', component: deactivate }, + { path: '/post/:id', component: OneController } + ]); + compile('
'); + + $router.navigate('/user/brian'); + $rootScope.$digest(); + $router.navigate('/post/123'); + $rootScope.$digest(); + expect(spy).toHaveBeenCalledWith(instructionFor(OneController), + instructionFor(deactivate)); + }); + + + it('should run the deactivate hook before the activate hook', function () { + var log = []; + + var activate = registerComponent('activate', '', { + onActivate: function () { + log.push('activate'); + } + }); + + var deactivate = registerComponent('deactivate', '', { + onDeactivate: function () { + log.push('deactivate'); + } + }); + + $router.config([ + { path: '/a', component: deactivate }, + { path: '/b', component: activate } + ]); + compile('outer {
}'); + + $router.navigate('/a'); + $rootScope.$digest(); + $router.navigate('/b'); + $rootScope.$digest(); + + expect(log).toEqual(['deactivate', 'activate']); + }); + + + it('should not activate a component when canActivate returns false', function () { + var spy = jasmine.createSpy('activate'); + var activate = registerComponent('activate', '', { + canActivate: function () { + return false; + }, + onActivate: spy + }); + + $router.config([ + { path: '/a', component: activate } + ]); + compile('outer {
}'); + + $router.navigate('/a'); + $rootScope.$digest(); + + expect(spy).not.toHaveBeenCalled(); + expect(elt.text()).toBe('outer { }'); + }); + + + it('should activate a component when canActivate returns true', function () { + var spy = jasmine.createSpy('activate'); + var activate = registerComponent('activate', 'hi', { + canActivate: function () { + return true; + }, + onActivate: spy + }); + + $router.config([ + { path: '/a', component: activate } + ]); + compile('
'); + + $router.navigate('/a'); + $rootScope.$digest(); + + expect(spy).toHaveBeenCalled(); + expect(elt.text()).toBe('hi'); + }); + + + it('should activate a component when canActivate returns a resolved promise', inject(function ($q) { + var spy = jasmine.createSpy('activate'); + var activate = registerComponent('activate', 'hi', { + canActivate: function () { + return $q.when(true); + }, + onActivate: spy + }); + + $router.config([ + { path: '/a', component: activate } + ]); + compile('
'); + + $router.navigate('/a'); + $rootScope.$digest(); + + expect(spy).toHaveBeenCalled(); + expect(elt.text()).toBe('hi'); + })); + + + it('should inject into the canActivate hook of controllers', inject(function ($http) { + var spy = jasmine.createSpy('canActivate').and.returnValue(true); + var activate = registerComponent('activate', '', { + canActivate: spy + }); + + spy.$inject = ['$routeParams', '$http']; + + $router.config([ + { path: '/user/:name', component: activate } + ]); + compile('
'); + + $router.navigate('/user/brian'); + $rootScope.$digest(); + expect(spy).toHaveBeenCalledWith({name: 'brian'}, $http); + })); + + + it('should not navigate when canDeactivate returns false', function () { + var activate = registerComponent('activate', 'hi', { + canDeactivate: function () { + return false; + } + }); + + $router.config([ + { path: '/a', component: activate }, + { path: '/b', component: OneController } + ]); + compile('outer {
}'); + + $router.navigate('/a'); + $rootScope.$digest(); + expect(elt.text()).toBe('outer { hi }'); + + $router.navigate('/b'); + $rootScope.$digest(); + expect(elt.text()).toBe('outer { hi }'); + }); + + + it('should navigate when canDeactivate returns true', function () { + var activate = registerComponent('activate', 'hi', { + canDeactivate: function () { + return true; + } + }); + + $router.config([ + { path: '/a', component: activate }, + { path: '/b', component: OneController } + ]); + compile('outer {
}'); + + $router.navigate('/a'); + $rootScope.$digest(); + expect(elt.text()).toBe('outer { hi }'); + + $router.navigate('/b'); + $rootScope.$digest(); + expect(elt.text()).toBe('outer { one }'); + }); + + + it('should activate a component when canActivate returns true', function () { + var spy = jasmine.createSpy('activate'); + var activate = registerComponent('activate', 'hi', { + canActivate: function () { + return true; + }, + onActivate: spy + }); + + $router.config([ + { path: '/a', component: activate } + ]); + compile('
'); + + $router.navigate('/a'); + $rootScope.$digest(); + + expect(spy).toHaveBeenCalled(); + expect(elt.text()).toBe('hi'); + }); + + + it('should pass instructions into the canDeactivate hook of controllers', function () { + var spy = jasmine.createSpy('canDeactivate').and.returnValue(true); + var deactivate = registerComponent('deactivate', '', { + canDeactivate: spy + }); + + $router.config([ + { path: '/user/:name', component: deactivate }, + { path: '/post/:id', component: OneController } + ]); + compile('
'); + + $router.navigate('/user/brian'); + $rootScope.$digest(); + $router.navigate('/post/123'); + $rootScope.$digest(); + expect(spy).toHaveBeenCalledWith(instructionFor(OneController), + instructionFor(deactivate)); + }); + + + it('should change location path', inject(function ($location) { + $router.config([ + { path: '/user', component: UserController } + ]); + + compile('
'); + + $router.navigate('/user'); + $rootScope.$digest(); + + expect($location.path()).toBe('/user'); + })); + + // TODO: test injecting $scope + + it('should navigate on left-mouse click when a link url matches a route', function () { + $router.config([ + { path: '/', component: OneController }, + { path: '/two', component: TwoController } + ]); + + compile('link |
'); + $rootScope.$digest(); + expect(elt.text()).toBe('link | one'); + elt.find('a')[0].click(); + + $rootScope.$digest(); + expect(elt.text()).toBe('link | two'); + }); + + + it('should not navigate on non-left mouse click when a link url matches a route', inject(function ($router) { + $router.config([ + { path: '/', component: OneController }, + { path: '/two', component: TwoController } + ]); + + compile('link |
'); + $rootScope.$digest(); + expect(elt.text()).toBe('link | one'); + elt.find('a').triggerHandler({ type: 'click', which: 3 }); + + $rootScope.$digest(); + expect(elt.text()).toBe('link | one'); + })); + + + // See https://github.com/angular/router/issues/206 + it('should not navigate a link without an href', function () { + $router.config([ + { path: '/', component: OneController }, + { path: '/two', component: TwoController } + ]); + expect(function () { + compile('link'); + $rootScope.$digest(); + expect(elt.text()).toBe('link'); + elt.find('a')[0].click(); + $rootScope.$digest(); + }).not.toThrow(); + }); + + + it('should change location to the canonical route', inject(function ($location) { + compile('
'); + + $router.config([ + { path: '/', redirectTo: '/user' }, + { path: '/user', component: UserController } + ]); + + $router.navigate('/'); + $rootScope.$digest(); + + expect($location.path()).toBe('/user'); + })); + + + it('should change location to the canonical route with nested components', inject(function ($location) { + var childRouter = registerComponent('childRouter', '
inner {
}
', [ + { path: '/old-child', redirectTo: '/new-child' }, + { path: '/new-child', component: OneController}, + { path: '/old-child-two', redirectTo: '/new-child-two' }, + { path: '/new-child-two', component: TwoController} + ]); + + $router.config([ + { path: '/old-parent', redirectTo: '/new-parent' }, + { path: '/new-parent/...', component: childRouter } + ]); + + compile('
'); + + $router.navigate('/old-parent/old-child'); + $rootScope.$digest(); + + expect($location.path()).toBe('/new-parent/new-child'); + expect(elt.text()).toBe('inner { one }'); + + $router.navigate('/old-parent/old-child-two'); + $rootScope.$digest(); + + expect($location.path()).toBe('/new-parent/new-child-two'); + expect(elt.text()).toBe('inner { two }'); + })); + + + it('should navigate when the location path changes', inject(function ($location) { + $router.config([ + { path: '/one', component: OneController } + ]); + compile('
'); + + $location.path('/one'); + $rootScope.$digest(); + + expect(elt.text()).toBe('one'); + })); + + + it('should expose a "navigating" property on $router', inject(function ($q) { + var defer; + var pendingActivate = registerComponent('pendingActivate', '', { + onActivate: function () { + defer = $q.defer(); + return defer.promise; + } + }); + $router.config([ + { path: '/pendingActivate', component: pendingActivate } + ]); + compile('
'); + + $router.navigate('/pendingActivate'); + $rootScope.$digest(); + expect($router.navigating).toBe(true); + defer.resolve(); + $rootScope.$digest(); + expect($router.navigating).toBe(false); + })); + + + function registerComponent(name, template, config) { + var Ctrl; + if (!template) { + template = ''; + } + if (!config) { + Ctrl = function () {}; + } else if (angular.isArray(config)) { + Ctrl = function () {}; + Ctrl.annotations = [new angular.annotations.RouteConfig(config)]; + } else if (typeof config === 'function') { + Ctrl = config; + } else { + Ctrl = function () {}; + if (config.canActivate) { + Ctrl.$canActivate = config.canActivate; + delete config.canActivate; + } + Ctrl.prototype = config; + } + $controllerProvider.register(componentControllerName(name), Ctrl); + put(name, template); + return Ctrl; + } + + function boringController(model, value) { + return function () { + this[model] = value; + }; + } + + function put(name, template) { + $templateCache.put(componentTemplatePath(name), [200, template, {}]); + } + + function compile(template) { + elt = $compile('
' + template + '
')($rootScope); + $rootScope.$digest(); + return elt; + } +}); + + +describe('ngOutlet animations', function () { + + var elt, + $animate, + $compile, + $rootScope, + $router, + $templateCache, + $controllerProvider; + + function UserController($routeParams) { + this.name = $routeParams.name; + } + + beforeEach(function () { + module('ngAnimate'); + module('ngAnimateMock'); + module('ngComponentRouter'); + module(function (_$controllerProvider_) { + $controllerProvider = _$controllerProvider_; + }); + + inject(function (_$animate_, _$compile_, _$rootScope_, _$router_, _$templateCache_) { + $animate = _$animate_; + $compile = _$compile_; + $rootScope = _$rootScope_; + $router = _$router_; + $templateCache = _$templateCache_; + }); + + put('user', '
hello {{user.name}}
'); + $controllerProvider.register('UserController', UserController); + }); + + afterEach(function () { + expect($animate.queue).toEqual([]); + }); + + it('should work in a simple case', function () { + var item; + + compile('
'); + + $router.config([ + { path: '/user/:name', component: UserController } + ]); + + $router.navigate('/user/brian'); + $rootScope.$digest(); + expect(elt.text()).toBe('hello brian'); + + // "user" component enters + item = $animate.queue.shift(); + expect(item.event).toBe('enter'); + + // navigate to pete + $router.navigate('/user/pete'); + $rootScope.$digest(); + expect(elt.text()).toBe('hello pete'); + + // "user pete" component enters + item = $animate.queue.shift(); + expect(item.event).toBe('enter'); + expect(item.element.text()).toBe('hello pete'); + + // "user brian" component leaves + item = $animate.queue.shift(); + expect(item.event).toBe('leave'); + expect(item.element.text()).toBe('hello brian'); + }); + + function put(name, template) { + $templateCache.put(componentTemplatePath(name), [200, template, {}]); + } + + function compile(template) { + elt = $compile('
' + template + '
')($rootScope); + $rootScope.$digest(); + return elt; + } +}); diff --git a/modules/angular1_router/test/util.es5.js b/modules/angular1_router/test/util.es5.js new file mode 100644 index 000000000000..c12ed8d6324d --- /dev/null +++ b/modules/angular1_router/test/util.es5.js @@ -0,0 +1,85 @@ +/* + * Helpers to keep tests DRY + */ + +function componentTemplatePath(name) { + return './components/' + dashCase(name) + '/' + dashCase(name) + '.html'; +} + +function componentControllerName(name) { + return name[0].toUpperCase() + name.substr(1) + 'Controller'; +} + +function dashCase(str) { + return str.replace(/([A-Z])/g, function ($1) { + return '-' + $1.toLowerCase(); + }); +} + +function boringController (model, value) { + return function () { + this[model] = value; + }; +} + +function provideHelpers(fn, preInject) { + return function () { + var elt, + $compile, + $rootScope, + $router, + $templateCache, + $controllerProvider; + + module('ng'); + module('ngNewRouter'); + module(function(_$controllerProvider_) { + $controllerProvider = _$controllerProvider_; + }); + + inject(function(_$compile_, _$rootScope_, _$router_, _$templateCache_) { + $compile = _$compile_; + $rootScope = _$rootScope_; + $router = _$router_; + $templateCache = _$templateCache_; + }); + + function registerComponent(name, template, config) { + if (!template) { + template = ''; + } + var ctrl; + if (!config) { + ctrl = function () {}; + } else if (angular.isArray(config)) { + ctrl = function () {}; + ctrl.$routeConfig = config; + } else if (typeof config === 'function') { + ctrl = config; + } else { + ctrl = function () {}; + ctrl.prototype = config; + } + $controllerProvider.register(componentControllerName(name), ctrl); + put(name, template); + } + + + function put (name, template) { + $templateCache.put(componentTemplatePath(name), [200, template, {}]); + } + + function compile(template) { + var elt = $compile('
' + template + '
')($rootScope); + $rootScope.$digest(); + return elt; + } + + fn({ + registerComponent: registerComponent, + $router: $router, + put: put, + compile: compile + }) + } +} diff --git a/package.json b/package.json index 66ca07182f76..eea424363f02 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,8 @@ }, "devDependencies": { "angular": "1.3.5", + "angular-animate": "1.3.5", + "angular-mocks": "1.3.5", "base64-js": "^0.0.8", "bower": "^1.3.12", "broccoli": "^0.15.3", diff --git a/scripts/ci/build_router.sh b/scripts/ci/build_router.sh new file mode 100755 index 000000000000..32247675ca5a --- /dev/null +++ b/scripts/ci/build_router.sh @@ -0,0 +1,11 @@ +#!/bin/bash +set -e + +echo ============================================================================= +# go to project dir +SCRIPT_DIR=$(dirname $0) +# this is needed because we're running JS tests in Dartium too +source $SCRIPT_DIR/env_dart.sh +cd $SCRIPT_DIR/../.. + +./node_modules/.bin/gulp buildRouter.dev diff --git a/scripts/ci/test_router.sh b/scripts/ci/test_router.sh new file mode 100755 index 000000000000..ced7f9f2ca1f --- /dev/null +++ b/scripts/ci/test_router.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -e + +echo ============================================================================= +# go to project dir +SCRIPT_DIR=$(dirname $0) +source $SCRIPT_DIR/env_dart.sh +cd $SCRIPT_DIR/../.. + +./node_modules/.bin/gulp test.unit.router/ci --browsers=${KARMA_BROWSERS:-ChromeCanary} diff --git a/tools/broccoli/trees/browser_tree.ts b/tools/broccoli/trees/browser_tree.ts index 669ee354ba8d..3d26ebc5c81a 100644 --- a/tools/broccoli/trees/browser_tree.ts +++ b/tools/broccoli/trees/browser_tree.ts @@ -76,6 +76,7 @@ module.exports = function makeBrowserTree(options, destinationPath) { exclude: [ '**/*.cjs', 'benchmarks/e2e_test/**', + 'angular1_router/**', // Exclude ES6 polyfill typings when tsc target=ES6 'angular2/traceur-runtime.d.ts', 'angular2/typings/es6-promise/**' diff --git a/tools/broccoli/trees/dart_tree.ts b/tools/broccoli/trees/dart_tree.ts index 3464e50229cc..b482b8255929 100644 --- a/tools/broccoli/trees/dart_tree.ts +++ b/tools/broccoli/trees/dart_tree.ts @@ -56,7 +56,7 @@ function stripModulePrefix(relativePath: string): string { function getSourceTree() { // Transpile everything in 'modules' except for rtts_assertions. - var tsInputTree = modulesFunnel(['**/*.js', '**/*.ts', '**/*.dart']); + var tsInputTree = modulesFunnel(['**/*.js', '**/*.ts', '**/*.dart'], ['angular1_router/**/*']); var transpiled = ts2dart(tsInputTree, { generateLibraryName: true, generateSourceMap: false, @@ -147,7 +147,7 @@ function getDocsTree() { var licenses = new MultiCopy('', { srcPath: 'LICENSE', targetPatterns: ['modules/*'], - exclude: ['*/rtts_assert', '*/http', '*/upgrade'], // Not in dart. + exclude: ['*/rtts_assert', '*/http', '*/upgrade', '*/angular1_router'] // Not in dart. }); licenses = stew.rename(licenses, stripModulePrefix); @@ -157,7 +157,7 @@ function getDocsTree() { relativePath => relativePath.replace(/\.dart\.md$/, '.md')); // Copy all assets, ignore .js. and .dart. (handled above). var docs = modulesFunnel(['**/*.md', '**/*.png', '**/*.html', '**/*.css', '**/*.scss'], - ['**/*.js.md', '**/*.dart.md']); + ['**/*.js.md', '**/*.dart.md', 'angular1_router/**/*']); var assets = modulesFunnel(['examples/**/*.json']); diff --git a/tools/broccoli/trees/node_tree.ts b/tools/broccoli/trees/node_tree.ts index 99b27bb99e69..4d7fcad5a265 100644 --- a/tools/broccoli/trees/node_tree.ts +++ b/tools/broccoli/trees/node_tree.ts @@ -23,7 +23,8 @@ module.exports = function makeNodeTree(destinationPath) { 'angular2/test/core/zone/**', 'angular2/test/test_lib/fake_async_spec.ts', 'angular2/test/render/xhr_impl_spec.ts', - 'angular2/test/forms/**' + 'angular2/test/forms/**', + 'angular1_router/**' ] }); From bde6416b40c322660b60dd319e4bbe165a5d9c45 Mon Sep 17 00:00:00 2001 From: Brian Ford Date: Thu, 20 Aug 2015 13:19:58 -0700 Subject: [PATCH 0005/1265] Revert "Revert "feat(router): add reuse support for angular 1.x router"" This reverts commit cef51a7e0d5e542954857ba05d734940ccd9cccf. --- modules/angular1_router/build.js | 12 +++- modules/angular1_router/src/ng_outlet.js | 12 ++-- .../angular1_router/test/ng_outlet_spec.js | 70 +++++++++++++++++++ 3 files changed, 89 insertions(+), 5 deletions(-) diff --git a/modules/angular1_router/build.js b/modules/angular1_router/build.js index 62c790ec7cf9..cacaf863203f 100644 --- a/modules/angular1_router/build.js +++ b/modules/angular1_router/build.js @@ -1,3 +1,5 @@ +'use strict'; + var fs = require('fs'); var ts = require('typescript'); @@ -58,6 +60,10 @@ function main() { "});", "}));", "}", + "if (constructor.$routeConfig) {", + "constructor.annotations = constructor.annotations || [];", + "constructor.annotations.push(new angular.annotations.RouteConfig(constructor.$routeConfig));", + "}", "if (constructor.annotations) {", "constructor.annotations.forEach(function(annotation) {", "if (annotation instanceof RouteConfig) {", @@ -70,7 +76,11 @@ function main() { "});", "var router = new RootRouter(registry, undefined, location, new Object());", - "$rootScope.$watch(function () { return $location.path(); }, function (path) { router.navigate(path); });", + "$rootScope.$watch(function () { return $location.path(); }, function (path) {", + "if (router.lastNavigationAttempt !== path) {", + "router.navigate(path);", + "}", + "});", "return router;" ].join('\n')); diff --git a/modules/angular1_router/src/ng_outlet.js b/modules/angular1_router/src/ng_outlet.js index 7598c8a9ee84..3d170d5952d8 100644 --- a/modules/angular1_router/src/ng_outlet.js +++ b/modules/angular1_router/src/ng_outlet.js @@ -134,11 +134,14 @@ function ngOutletDirective($animate, $injector, $q, $router, $componentMapper, $ router.registerOutlet({ commit: function (instruction) { - var next; + var next = $q.when(true); var componentInstruction = instruction.component; if (componentInstruction.reuse) { - // todo(shahata): lifecycle - onReuse - next = $q.when(true); + var previousInstruction = currentInstruction; + currentInstruction = componentInstruction; + if (currentController.onReuse) { + next = $q.when(currentController.onReuse(currentInstruction, previousInstruction)); + } } else { var self = this; next = this.deactivate(instruction).then(function () { @@ -159,8 +162,9 @@ function ngOutletDirective($animate, $injector, $q, $router, $componentMapper, $ if (!currentInstruction || currentInstruction.componentType !== componentInstruction.componentType) { result = false; + } else if (currentController.canReuse) { + result = currentController.canReuse(componentInstruction, currentInstruction); } else { - // todo(shahata): lifecycle - canReuse result = componentInstruction === currentInstruction || angular.equals(componentInstruction.params, currentInstruction.params); } diff --git a/modules/angular1_router/test/ng_outlet_spec.js b/modules/angular1_router/test/ng_outlet_spec.js index cd89ffc005cb..ac9c97768396 100644 --- a/modules/angular1_router/test/ng_outlet_spec.js +++ b/modules/angular1_router/test/ng_outlet_spec.js @@ -358,6 +358,76 @@ describe('ngOutlet', function () { }); + it('should reuse a component when the canReuse hook returns true', function () { + var log = []; + var cmpInstanceCount = 0; + + function ReuseCmp() { + cmpInstanceCount++; + this.canReuse = function () { + return true; + }; + this.onReuse = function (next, prev) { + log.push('reuse: ' + prev.urlPath + ' -> ' + next.urlPath); + }; + } + ReuseCmp.$routeConfig = [{path: '/a', component: OneController}, {path: '/b', component: TwoController}]; + registerComponent('reuse', 'reuse {}', ReuseCmp); + + $router.config([ + { path: '/on-reuse/:number/...', component: ReuseCmp } + ]); + compile('outer {
}'); + + $router.navigate('/on-reuse/1/a'); + $rootScope.$digest(); + expect(log).toEqual([]); + expect(cmpInstanceCount).toBe(1); + expect(elt.text()).toBe('outer { reuse {one} }'); + + $router.navigate('/on-reuse/2/b'); + $rootScope.$digest(); + expect(log).toEqual(['reuse: on-reuse/1 -> on-reuse/2']); + expect(cmpInstanceCount).toBe(1); + expect(elt.text()).toBe('outer { reuse {two} }'); + }); + + + it('should not reuse a component when the canReuse hook returns false', function () { + var log = []; + var cmpInstanceCount = 0; + + function NeverReuseCmp() { + cmpInstanceCount++; + this.canReuse = function () { + return false; + }; + this.onReuse = function (next, prev) { + log.push('reuse: ' + prev.urlPath + ' -> ' + next.urlPath); + }; + } + NeverReuseCmp.$routeConfig = [{path: '/a', component: OneController}, {path: '/b', component: TwoController}]; + registerComponent('reuse', 'reuse {}', NeverReuseCmp); + + $router.config([ + { path: '/never-reuse/:number/...', component: NeverReuseCmp } + ]); + compile('outer {
}'); + + $router.navigate('/never-reuse/1/a'); + $rootScope.$digest(); + expect(log).toEqual([]); + expect(cmpInstanceCount).toBe(1); + expect(elt.text()).toBe('outer { reuse {one} }'); + + $router.navigate('/never-reuse/2/b'); + $rootScope.$digest(); + expect(log).toEqual([]); + expect(cmpInstanceCount).toBe(2); + expect(elt.text()).toBe('outer { reuse {two} }'); + }); + + it('should not activate a component when canActivate returns false', function () { var spy = jasmine.createSpy('activate'); var activate = registerComponent('activate', '', { From 9afcb002162ce38907dc0f260e5f9392a1995b16 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Wed, 19 Aug 2015 15:33:59 -0700 Subject: [PATCH 0006/1265] fix: wtf paramater passing on scope Closes #3726 --- modules/angular2/src/profile/wtf_impl.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/angular2/src/profile/wtf_impl.dart b/modules/angular2/src/profile/wtf_impl.dart index 19e11a4c53b6..1650c64d79b9 100644 --- a/modules/angular2/src/profile/wtf_impl.dart +++ b/modules/angular2/src/profile/wtf_impl.dart @@ -70,7 +70,7 @@ dynamic createScope(String signature, [flags]) { return ([arg0, arg1]) { _arg2[0] = arg0; _arg2[1] = arg1; - return jsScope.apply(_arg1); + return jsScope.apply(_arg2); }; default: throw "Max 2 arguments are supported."; From a0b240884bd31a31e5142d654342ac14e8d29ba9 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Wed, 19 Aug 2015 20:36:57 -0700 Subject: [PATCH 0007/1265] =?UTF-8?q?fix(.d.ts):=20show=20unknown=20fields?= =?UTF-8?q?=20as=20=E2=80=98any=E2=80=99=20not=20=E2=80=98void=E2=80=99.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #3637 --- .../templates/type-definition.template.html | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/typescript-definition-package/templates/type-definition.template.html b/docs/typescript-definition-package/templates/type-definition.template.html index b1667ee54a95..020dfe149d23 100644 --- a/docs/typescript-definition-package/templates/type-definition.template.html +++ b/docs/typescript-definition-package/templates/type-definition.template.html @@ -13,7 +13,12 @@ {$ member.name $}{% if member.optional %}?{% endif -%} {% if member.typeParameters %}<{% for typeParam in member.typeParameters %}{$ typeParam $}{% if not loop.last %}, {% endif %}{% endfor %}>{% endif -%} {%- if member.parameters -%}({% for param in member.parameters %}{$ param $}{% if not loop.last %}, {% endif %}{% endfor %}){%- endif -%} -{%- if member.returnType == 'Directive' %}: DirectiveAnnotation{%- elif member.returnType -%}: {$ member.returnType $}{%- else -%}: void +{%- if member.returnType -%} + : {$ member.returnType $} +{%- elif member.parameters -%} + : void +{%- else -%} + : any {%- endif -%}; {%- endmacro -%} From 195c5c21d434e8ab7c668dbb75dc2745f7be48ee Mon Sep 17 00:00:00 2001 From: vsavkin Date: Wed, 19 Aug 2015 10:48:53 -0700 Subject: [PATCH 0008/1265] fix(change_detection): update the right change detector when using ON_PUSH mode Previously, in a case where you have a mix of ON_PUSH and DEFAULT detectors, Angular would update the status of a wrong detector. --- .../abstract_change_detector.ts | 4 ++ .../change_detection_jit_generator.ts | 12 +----- .../change_detection/codegen_logic_util.ts | 13 +++++++ .../src/change_detection/codegen_name_util.ts | 6 --- .../change_detector_codegen.dart | 12 +----- .../change_detector_config.ts | 23 ++++++------ .../change_detection/change_detector_spec.ts | 37 +++++++++++-------- 7 files changed, 53 insertions(+), 54 deletions(-) diff --git a/modules/angular2/src/change_detection/abstract_change_detector.ts b/modules/angular2/src/change_detection/abstract_change_detector.ts index aac85baf5379..82efc68b469e 100644 --- a/modules/angular2/src/change_detection/abstract_change_detector.ts +++ b/modules/angular2/src/change_detection/abstract_change_detector.ts @@ -210,6 +210,10 @@ export class AbstractChangeDetector implements ChangeDetector { return value; } + protected getDetectorFor(directives: any, index: number): ChangeDetector { + return directives.getDetectorFor(this.directiveRecords[index].directiveIndex); + } + protected notifyDispatcher(value: any): void { this.dispatcher.notifyOnBinding(this._currentBinding(), value); } 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 ba2601812394..78609c008198 100644 --- a/modules/angular2/src/change_detection/change_detection_jit_generator.ts +++ b/modules/angular2/src/change_detection/change_detection_jit_generator.ts @@ -143,7 +143,7 @@ export class ChangeDetectorJITGenerator { _maybeGenHydrateDirectives(): string { var hydrateDirectivesCode = this._genHydrateDirectives(); - var hydrateDetectorsCode = this._genHydrateDetectors(); + var hydrateDetectorsCode = this._logic.genHydrateDetectors(this.directiveRecords); if (!hydrateDirectivesCode && !hydrateDetectorsCode) return ''; return `${this._typeName}.prototype.hydrateDirectives = function(directives) { ${hydrateDirectivesCode} @@ -161,16 +161,6 @@ export class ChangeDetectorJITGenerator { return lines.join('\n'); } - _genHydrateDetectors(): string { - var detectorFieldNames = this._names.getAllDetectorNames(); - var lines = ListWrapper.createFixedSize(detectorFieldNames.length); - for (var i = 0, iLen = detectorFieldNames.length; i < iLen; ++i) { - lines[i] = `${detectorFieldNames[i]} = directives.getDetectorFor( - ${this._names.getDirectivesAccessorName()}[${i}].directiveIndex);`; - } - return lines.join('\n'); - } - _maybeGenCallOnAllChangesDone(): string { var notifications = []; var dirs = this.directiveRecords; diff --git a/modules/angular2/src/change_detection/codegen_logic_util.ts b/modules/angular2/src/change_detection/codegen_logic_util.ts index 13842ee596c5..1181c8243273 100644 --- a/modules/angular2/src/change_detection/codegen_logic_util.ts +++ b/modules/angular2/src/change_detection/codegen_logic_util.ts @@ -3,6 +3,7 @@ import {BaseException, Json, StringWrapper} from 'angular2/src/facade/lang'; import {CodegenNameUtil} from './codegen_name_util'; import {codify, combineGeneratedStrings, rawString} from './codegen_facade'; import {ProtoRecord, RecordType} from './proto_record'; +import {DirectiveRecord} from './directive_record'; /** * This is an experimental feature. Works only in Dart. @@ -131,4 +132,16 @@ export class CodegenLogicUtil { iVals.push(codify(protoRec.fixedArgs[protoRec.args.length])); return combineGeneratedStrings(iVals); } + + genHydrateDetectors(directiveRecords: DirectiveRecord[]): string { + var res = []; + for (var i = 0; i < directiveRecords.length; ++i) { + var r = directiveRecords[i]; + if (!r.isDefaultChangeDetection()) { + res.push( + `${this._names.getDetectorName(r.directiveIndex)} = this.getDetectorFor(directives, ${i});`); + } + } + return res.join("\n"); + } } diff --git a/modules/angular2/src/change_detection/codegen_name_util.ts b/modules/angular2/src/change_detection/codegen_name_util.ts index 8e99ec5d3f2d..4300c17ac9b7 100644 --- a/modules/angular2/src/change_detection/codegen_name_util.ts +++ b/modules/angular2/src/change_detection/codegen_name_util.ts @@ -200,11 +200,5 @@ export class CodegenNameUtil { return this._addFieldPrefix(`directive_${d.name}`); } - getAllDetectorNames(): List { - return ListWrapper.map( - ListWrapper.filter(this.directiveRecords, r => !r.isDefaultChangeDetection()), - (d) => this.getDetectorName(d.directiveIndex)); - } - getDetectorName(d: DirectiveIndex): string { return this._addFieldPrefix(`detector_${d.name}`); } } diff --git a/modules/angular2/src/transform/template_compiler/change_detector_codegen.dart b/modules/angular2/src/transform/template_compiler/change_detector_codegen.dart index d1a2b1ba6ba6..81a6e19e1bf9 100644 --- a/modules/angular2/src/transform/template_compiler/change_detector_codegen.dart +++ b/modules/angular2/src/transform/template_compiler/change_detector_codegen.dart @@ -226,7 +226,7 @@ class _CodegenState { String _maybeGenHydrateDirectives() { var hydrateDirectivesCode = _genHydrateDirectives(); - var hydrateDetectorsCode = _genHydrateDetectors(); + var hydrateDetectorsCode = _logic.genHydrateDetectors(_directiveRecords); if (hydrateDirectivesCode.isEmpty && hydrateDetectorsCode.isEmpty) { return ''; } @@ -244,16 +244,6 @@ class _CodegenState { return '$buf'; } - String _genHydrateDetectors() { - var buf = new StringBuffer(); - var detectorFieldNames = _names.getAllDetectorNames(); - for (var i = 0; i < detectorFieldNames.length; ++i) { - buf.writeln('${detectorFieldNames[i]} = directives.getDetectorFor(' - '${_names.getDirectivesAccessorName()}[$i].directiveIndex);'); - } - return '$buf'; - } - /// Generates calls to `onAllChangesDone` for all `Directive`s that request /// them. String _maybeGenCallOnAllChangesDone() { diff --git a/modules/angular2/test/change_detection/change_detector_config.ts b/modules/angular2/test/change_detection/change_detector_config.ts index baf8d81a1cdf..3442a1fef276 100644 --- a/modules/angular2/test/change_detection/change_detector_config.ts +++ b/modules/angular2/test/change_detection/change_detector_config.ts @@ -183,24 +183,25 @@ class _ExpressionWithMode { var directiveRecords = []; var eventRecords = []; + var dirRecordWithDefault = + new DirectiveRecord({directiveIndex: new DirectiveIndex(0, 0), changeDetection: DEFAULT}); + var dirRecordWithOnPush = + new DirectiveRecord({directiveIndex: new DirectiveIndex(0, 1), changeDetection: ON_PUSH}); + if (this._withRecords) { - var dirRecordWithOnPush = - new DirectiveRecord({directiveIndex: new DirectiveIndex(0, 0), changeDetection: ON_PUSH}); + var updateDirWithOnDefaultRecord = + BindingRecord.createForDirective(_getParser().parseBinding('42', 'location'), 'a', + (o, v) => (o).a = v, dirRecordWithDefault); var updateDirWithOnPushRecord = BindingRecord.createForDirective(_getParser().parseBinding('42', 'location'), 'a', (o, v) => (o).a = v, dirRecordWithOnPush); - bindingRecords = [updateDirWithOnPushRecord]; - directiveRecords = [dirRecordWithOnPush]; - } else { - bindingRecords = []; - directiveRecords = []; + + directiveRecords = [dirRecordWithDefault, dirRecordWithOnPush]; + bindingRecords = [updateDirWithOnDefaultRecord, updateDirWithOnPushRecord]; } if (this._withEvents) { - var dirRecordWithOnPush = - new DirectiveRecord({directiveIndex: new DirectiveIndex(0, 0), changeDetection: ON_PUSH}); - directiveRecords = [dirRecordWithOnPush]; - + directiveRecords = [dirRecordWithDefault, dirRecordWithOnPush]; eventRecords = ListWrapper.concat(_createEventRecords("(event)='false'"), _createHostEventRecords("(host-event)='false'", dirRecordWithOnPush)) diff --git a/modules/angular2/test/change_detection/change_detector_spec.ts b/modules/angular2/test/change_detection/change_detector_spec.ts index 34a8ff264c35..e814617cd928 100644 --- a/modules/angular2/test/change_detection/change_detector_spec.ts +++ b/modules/angular2/test/change_detection/change_detector_spec.ts @@ -704,29 +704,36 @@ export function main() { }); describe('marking ON_PUSH detectors as CHECK_ONCE after an update', () => { - var checkedDetector; + var childDirectiveDetectorRegular; + var childDirectiveDetectorOnPush; var directives; beforeEach(() => { - checkedDetector = _createWithoutHydrate('emptyUsingOnPushStrategy').changeDetector; - checkedDetector.hydrate(_DEFAULT_CONTEXT, null, null, null); - checkedDetector.mode = CHECKED; - - var targetDirective = new TestData(null); - directives = new FakeDirectives([targetDirective], [checkedDetector]); + childDirectiveDetectorRegular = _createWithoutHydrate('10').changeDetector; + childDirectiveDetectorRegular.hydrate(_DEFAULT_CONTEXT, null, null, null); + childDirectiveDetectorRegular.mode = CHECK_ALWAYS; + + childDirectiveDetectorOnPush = + _createWithoutHydrate('emptyUsingOnPushStrategy').changeDetector; + childDirectiveDetectorOnPush.hydrate(_DEFAULT_CONTEXT, null, null, null); + childDirectiveDetectorOnPush.mode = CHECKED; + + directives = + new FakeDirectives([new TestData(null), new TestData(null)], + [childDirectiveDetectorRegular, childDirectiveDetectorOnPush]); }); it('should set the mode to CHECK_ONCE when a binding is updated', () => { - var cd = _createWithoutHydrate('onPushRecordsUsingDefaultStrategy').changeDetector; - cd.hydrate(_DEFAULT_CONTEXT, null, directives, null); + var parentDetector = + _createWithoutHydrate('onPushRecordsUsingDefaultStrategy').changeDetector; + parentDetector.hydrate(_DEFAULT_CONTEXT, null, directives, null); - expect(checkedDetector.mode).toEqual(CHECKED); + parentDetector.detectChanges(); - // evaluate the record, update the targetDirective, and mark its detector as - // CHECK_ONCE - cd.detectChanges(); + // making sure that we only change the status of ON_PUSH components + expect(childDirectiveDetectorRegular.mode).toEqual(CHECK_ALWAYS); - expect(checkedDetector.mode).toEqual(CHECK_ONCE); + expect(childDirectiveDetectorOnPush.mode).toEqual(CHECK_ONCE); }); it('should mark ON_PUSH detectors as CHECK_ONCE after an event', () => { @@ -745,7 +752,7 @@ export function main() { cd.handleEvent("host-event", 0, null); - expect(checkedDetector.mode).toEqual(CHECK_ONCE); + expect(childDirectiveDetectorOnPush.mode).toEqual(CHECK_ONCE); }); if (IS_DART) { From 49997ca9324cb5383c759beaf6776bba80e426a0 Mon Sep 17 00:00:00 2001 From: Marc Laval Date: Wed, 19 Aug 2015 11:01:00 +0200 Subject: [PATCH 0009/1265] chore(build): add iOS8 to CI --- sauce.conf.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sauce.conf.js b/sauce.conf.js index 08904c7b7d0f..5535da6d18c9 100644 --- a/sauce.conf.js +++ b/sauce.conf.js @@ -125,7 +125,7 @@ var aliases = { 'SAFARI': ['SL_SAFARI7', 'SL_SAFARI8'], 'BETA': ['SL_CHROMEBETA', 'SL_FIREFOXBETA'], 'DEV': ['SL_CHROMEDEV', 'SL_FIREFOXDEV'], - 'CI': ['SL_CHROME', 'SL_ANDROID5.1', 'SL_SAFARI8'] + 'CI': ['SL_CHROME', 'SL_ANDROID5.1', 'SL_SAFARI8', 'SL_IOS8'] }; module.exports = { From 1c9be9b5aabcf27a45f1528c7e47d028eff0a85e Mon Sep 17 00:00:00 2001 From: Marc Laval Date: Wed, 19 Aug 2015 16:47:18 +0200 Subject: [PATCH 0010/1265] chore(build): add Firefox to CI --- modules/angular2/src/test_lib/utils.ts | 5 +++++ modules/angular2/test/forms/integration_spec.ts | 10 +++++++++- sauce.conf.js | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/modules/angular2/src/test_lib/utils.ts b/modules/angular2/src/test_lib/utils.ts index 76a4cf6c9cb8..d7af22177172 100644 --- a/modules/angular2/src/test_lib/utils.ts +++ b/modules/angular2/src/test_lib/utils.ts @@ -92,3 +92,8 @@ export function stringifyElement(el): string { export function supportsIntlApi(): boolean { return DOM.getUserAgent().indexOf('Chrome') > -1 && DOM.getUserAgent().indexOf('Edge') == -1; } + +// TODO(mlaval): extract all browser detection checks from all tests +export function isFirefox(): boolean { + return DOM.getUserAgent().indexOf("Firefox") > -1; +} diff --git a/modules/angular2/test/forms/integration_spec.ts b/modules/angular2/test/forms/integration_spec.ts index 15b254a7fbf0..0f13fc2e806c 100644 --- a/modules/angular2/test/forms/integration_spec.ts +++ b/modules/angular2/test/forms/integration_spec.ts @@ -14,7 +14,8 @@ import { it, inject, iit, - xit + xit, + isFirefox } from 'angular2/test_lib'; import {DOM} from 'angular2/src/dom/dom_adapter'; @@ -737,6 +738,13 @@ export function main() { rootTC.componentInstance.form = form; rootTC.detectChanges(); + // In Firefox, effective text selection in the real DOM requires an actual focus + // of the field. This is not an issue in a new HTML document. + if (isFirefox()) { + var fakeDoc = DOM.createHtmlDocument(); + DOM.appendChild(fakeDoc.body, rootTC.nativeElement); + } + var input = rootTC.query(By.css("input")).nativeElement; input.value = "aa"; input.selectionStart = 1; diff --git a/sauce.conf.js b/sauce.conf.js index 5535da6d18c9..7d7203d91198 100644 --- a/sauce.conf.js +++ b/sauce.conf.js @@ -125,7 +125,7 @@ var aliases = { 'SAFARI': ['SL_SAFARI7', 'SL_SAFARI8'], 'BETA': ['SL_CHROMEBETA', 'SL_FIREFOXBETA'], 'DEV': ['SL_CHROMEDEV', 'SL_FIREFOXDEV'], - 'CI': ['SL_CHROME', 'SL_ANDROID5.1', 'SL_SAFARI8', 'SL_IOS8'] + 'CI': ['SL_CHROME', 'SL_ANDROID5.1', 'SL_SAFARI8', 'SL_IOS8', 'SL_FIREFOX'] }; module.exports = { From 9ba2ab5ceaab60b48a8eb830601abf2cef05ecea Mon Sep 17 00:00:00 2001 From: Marc Laval Date: Thu, 20 Aug 2015 10:02:42 +0200 Subject: [PATCH 0011/1265] chore(build): add IE11 to CI --- karma-js.conf.js | 4 +- .../angular2/src/test_lib/shims_for_IE.dart | 1 - modules/angular2/src/test_lib/shims_for_IE.js | 87 +++++++++++++++++++ modules/angular2/src/test_lib/shims_for_IE.ts | 13 --- sauce.conf.js | 2 +- 5 files changed, 90 insertions(+), 17 deletions(-) delete mode 100644 modules/angular2/src/test_lib/shims_for_IE.dart create mode 100644 modules/angular2/src/test_lib/shims_for_IE.js delete mode 100644 modules/angular2/src/test_lib/shims_for_IE.ts diff --git a/karma-js.conf.js b/karma-js.conf.js index f607206ba66c..1bbed6cc0eb7 100644 --- a/karma-js.conf.js +++ b/karma-js.conf.js @@ -20,13 +20,13 @@ module.exports = function(config) { 'node_modules/traceur/bin/traceur-runtime.js', 'node_modules/es6-module-loader/dist/es6-module-loader-sans-promises.src.js', // Including systemjs because it defines `__eval`, which produces correct stack traces. + 'modules/angular2/src/test_lib/shims_for_IE.js', 'node_modules/systemjs/dist/system.src.js', {pattern: 'node_modules/rx/dist/rx.js', included: false, watched: false, served: true}, 'node_modules/reflect-metadata/Reflect.js', 'tools/build/file2modulename.js', 'test-main.js', - {pattern: 'modules/**/test/**/static_assets/**', included: false, watched: false}, - 'modules/angular2/src/test_lib/shims_for_IE.ts' + {pattern: 'modules/**/test/**/static_assets/**', included: false, watched: false} ], exclude: [ diff --git a/modules/angular2/src/test_lib/shims_for_IE.dart b/modules/angular2/src/test_lib/shims_for_IE.dart deleted file mode 100644 index a70b4cf71845..000000000000 --- a/modules/angular2/src/test_lib/shims_for_IE.dart +++ /dev/null @@ -1 +0,0 @@ -library angular2.test_lib.shims_for_ie; diff --git a/modules/angular2/src/test_lib/shims_for_IE.js b/modules/angular2/src/test_lib/shims_for_IE.js new file mode 100644 index 000000000000..9bb828bdb188 --- /dev/null +++ b/modules/angular2/src/test_lib/shims_for_IE.js @@ -0,0 +1,87 @@ +// function.name (all IE) +/*! @source http://stackoverflow.com/questions/6903762/function-name-not-supported-in-ie*/ +if (!Object.hasOwnProperty('name')) { + Object.defineProperty(Function.prototype, 'name', { + get: function() { + var name = this.toString().match(/^\s*function\s*(\S*)\s*\(/)[1]; + // For better performance only parse once, and then cache the + // result through a new accessor for repeated access. + Object.defineProperty(this, 'name', {value: name}); + return name; + } + }); +} + +// URL polyfill for SystemJS (all IE) +/*! @source https://github.com/ModuleLoader/es6-module-loader/blob/master/src/url-polyfill.js*/ +// from https://gist.github.com/Yaffle/1088850 +(function(global) { + function URLPolyfill(url, baseURL) { + if (typeof url != 'string') { + throw new TypeError('URL must be a string'); + } + var m = String(url).replace(/^\s+|\s+$/g, "").match(/^([^:\/?#]+:)?(?:\/\/(?:([^:@\/?#]*)(?::([^:@\/?#]*))?@)?(([^:\/?#]*)(?::(\d*))?))?([^?#]*)(\?[^#]*)?(#[\s\S]*)?/); + if (!m) { + throw new RangeError(); + } + var protocol = m[1] || ""; + var username = m[2] || ""; + var password = m[3] || ""; + var host = m[4] || ""; + var hostname = m[5] || ""; + var port = m[6] || ""; + var pathname = m[7] || ""; + var search = m[8] || ""; + var hash = m[9] || ""; + if (baseURL !== undefined) { + var base = baseURL instanceof URLPolyfill ? baseURL : new URLPolyfill(baseURL); + var flag = protocol === "" && host === "" && username === ""; + if (flag && pathname === "" && search === "") { + search = base.search; + } + if (flag && pathname.charAt(0) !== "/") { + pathname = (pathname !== "" ? (((base.host !== "" || base.username !== "") && base.pathname === "" ? "/" : "") + base.pathname.slice(0, base.pathname.lastIndexOf("/") + 1) + pathname) : base.pathname); + } + // dot segments removal + var output = []; + pathname.replace(/^(\.\.?(\/|$))+/, "") + .replace(/\/(\.(\/|$))+/g, "/") + .replace(/\/\.\.$/, "/../") + .replace(/\/?[^\/]*/g, function (p) { + if (p === "/..") { + output.pop(); + } else { + output.push(p); + } + }); + pathname = output.join("").replace(/^\//, pathname.charAt(0) === "/" ? "/" : ""); + if (flag) { + port = base.port; + hostname = base.hostname; + host = base.host; + password = base.password; + username = base.username; + } + if (protocol === "") { + protocol = base.protocol; + } + } + + // convert windows file URLs to use / + if (protocol == 'file:') + pathname = pathname.replace(/\\/g, '/'); + + this.origin = protocol + (protocol !== "" || host !== "" ? "//" : "") + host; + this.href = protocol + (protocol !== "" || host !== "" ? "//" : "") + (username !== "" ? username + (password !== "" ? ":" + password : "") + "@" : "") + host + pathname + search + hash; + this.protocol = protocol; + this.username = username; + this.password = password; + this.host = host; + this.hostname = hostname; + this.port = port; + this.pathname = pathname; + this.search = search; + this.hash = hash; + } +global.URLPolyfill = URLPolyfill; +})(typeof self != 'undefined' ? self : global); \ No newline at end of file diff --git a/modules/angular2/src/test_lib/shims_for_IE.ts b/modules/angular2/src/test_lib/shims_for_IE.ts deleted file mode 100644 index adb0720ecab3..000000000000 --- a/modules/angular2/src/test_lib/shims_for_IE.ts +++ /dev/null @@ -1,13 +0,0 @@ -// function.name -/*! @source http://stackoverflow.com/questions/6903762/function-name-not-supported-in-ie*/ -if (!Object.hasOwnProperty('name')) { - Object.defineProperty(Function.prototype, 'name', { - get: function() { - var name = this.toString().match(/^\s*function\s*(\S*)\s*\(/)[1]; - // For better performance only parse once, and then cache the - // result through a new accessor for repeated access. - Object.defineProperty(this, 'name', {value: name}); - return name; - } - }); -} diff --git a/sauce.conf.js b/sauce.conf.js index 7d7203d91198..4dbb84d6b30c 100644 --- a/sauce.conf.js +++ b/sauce.conf.js @@ -125,7 +125,7 @@ var aliases = { 'SAFARI': ['SL_SAFARI7', 'SL_SAFARI8'], 'BETA': ['SL_CHROMEBETA', 'SL_FIREFOXBETA'], 'DEV': ['SL_CHROMEDEV', 'SL_FIREFOXDEV'], - 'CI': ['SL_CHROME', 'SL_ANDROID5.1', 'SL_SAFARI8', 'SL_IOS8', 'SL_FIREFOX'] + 'CI': ['SL_CHROME', 'SL_ANDROID5.1', 'SL_SAFARI8', 'SL_IOS8', 'SL_FIREFOX', 'SL_IE11'] }; module.exports = { From b0d27ee896cfeaa4a290bd4fed26086956522cd1 Mon Sep 17 00:00:00 2001 From: Marc Laval Date: Thu, 20 Aug 2015 10:12:46 +0200 Subject: [PATCH 0012/1265] chore(build): add IE10 to CI --- modules/angular2/src/dom/browser_adapter.ts | 6 ++++-- modules/angular2/test/core/compiler/integration_spec.ts | 9 +++++---- sauce.conf.js | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/modules/angular2/src/dom/browser_adapter.ts b/modules/angular2/src/dom/browser_adapter.ts index ddd52cb2f184..d0548f1fe2d7 100644 --- a/modules/angular2/src/dom/browser_adapter.ts +++ b/modules/angular2/src/dom/browser_adapter.ts @@ -299,8 +299,10 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter { } resetBaseElement(): void { baseElement = null; } getUserAgent(): string { return window.navigator.userAgent; } - setData(element, name: string, value: string) { element.dataset[name] = value; } - getData(element, name: string): string { return element.dataset[name]; } + setData(element, name: string, value: string) { + this.setAttribute(element, 'data-' + name, value); + } + getData(element, name: string): string { return this.getAttribute(element, 'data-' + name); } // TODO(tbosch): move this into a separate environment class once we have it setGlobalVar(name: string, value: any) { global[name] = value; } } diff --git a/modules/angular2/test/core/compiler/integration_spec.ts b/modules/angular2/test/core/compiler/integration_spec.ts index 42e2b77baf1a..aba8405e6851 100644 --- a/modules/angular2/test/core/compiler/integration_spec.ts +++ b/modules/angular2/test/core/compiler/integration_spec.ts @@ -1060,7 +1060,7 @@ export function main() { var tc = rootTC.componentViewChildren[0]; var needsAttribute = tc.inject(NeedsAttribute); expect(needsAttribute.typeAttribute).toEqual('text'); - expect(needsAttribute.titleAttribute).toEqual(''); + expect(needsAttribute.staticAttribute).toEqual(''); expect(needsAttribute.fooAttribute).toEqual(null); async.done(); @@ -1886,12 +1886,13 @@ class IdDir { @Injectable() class NeedsAttribute { typeAttribute; - titleAttribute; + staticAttribute; fooAttribute; - constructor(@Attribute('type') typeAttribute: String, @Attribute('title') titleAttribute: String, + constructor(@Attribute('type') typeAttribute: String, + @Attribute('static') staticAttribute: String, @Attribute('foo') fooAttribute: String) { this.typeAttribute = typeAttribute; - this.titleAttribute = titleAttribute; + this.staticAttribute = staticAttribute; this.fooAttribute = fooAttribute; } } diff --git a/sauce.conf.js b/sauce.conf.js index 4dbb84d6b30c..84a9e088e110 100644 --- a/sauce.conf.js +++ b/sauce.conf.js @@ -125,7 +125,7 @@ var aliases = { 'SAFARI': ['SL_SAFARI7', 'SL_SAFARI8'], 'BETA': ['SL_CHROMEBETA', 'SL_FIREFOXBETA'], 'DEV': ['SL_CHROMEDEV', 'SL_FIREFOXDEV'], - 'CI': ['SL_CHROME', 'SL_ANDROID5.1', 'SL_SAFARI8', 'SL_IOS8', 'SL_FIREFOX', 'SL_IE11'] + 'CI': ['SL_CHROME', 'SL_ANDROID5.1', 'SL_SAFARI8', 'SL_IOS8', 'SL_FIREFOX', 'SL_IE11', 'SL_IE10'] }; module.exports = { From 8336881a852fb16afaa48fd67ae4f8f48cec9769 Mon Sep 17 00:00:00 2001 From: yjbanov Date: Wed, 19 Aug 2015 14:07:30 -0700 Subject: [PATCH 0013/1265] feat: track unused reflection data --- modules/angular2/src/reflection/reflector.ts | 38 +++++++++++++++++-- .../test/reflection/reflector_spec.ts | 23 +++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/modules/angular2/src/reflection/reflector.ts b/modules/angular2/src/reflection/reflector.ts index eb95e9298bcf..3fc12036acf6 100644 --- a/modules/angular2/src/reflection/reflector.ts +++ b/modules/angular2/src/reflection/reflector.ts @@ -4,6 +4,8 @@ import { ListWrapper, Map, MapWrapper, + Set, + SetWrapper, StringMap, StringMapWrapper } from 'angular2/src/facade/collection'; @@ -32,6 +34,7 @@ export class Reflector { _getters: Map; _setters: Map; _methods: Map; + _usedKeys: Set; reflectionCapabilities: PlatformReflectionCapabilities; constructor(reflectionCapabilities: PlatformReflectionCapabilities) { @@ -39,11 +42,31 @@ export class Reflector { this._getters = new Map(); this._setters = new Map(); this._methods = new Map(); + this._usedKeys = null; this.reflectionCapabilities = reflectionCapabilities; } isReflectionEnabled(): boolean { return this.reflectionCapabilities.isReflectionEnabled(); } + /** + * Causes `this` reflector to track keys used to access + * {@link ReflectionInfo} objects. + */ + trackUsage(): void { this._usedKeys = new Set(); } + + /** + * Lists types for which reflection information was not requested since + * {@link #trackUsage} was called. This list could later be audited as + * potential dead code. + */ + listUnusedKeys(): List { + if (this._usedKeys == null) { + throw new BaseException('Usage tracking is disabled'); + } + var allTypes = MapWrapper.keys(this._injectableInfo); + return ListWrapper.filter(allTypes, (key) => { return !SetWrapper.has(this._usedKeys, key); }); + } + registerFunction(func: Function, funcInfo: ReflectionInfo): void { this._injectableInfo.set(func, funcInfo); } @@ -66,7 +89,7 @@ export class Reflector { factory(type: Type): Function { if (this._containsReflectionInfo(type)) { - var res = this._injectableInfo.get(type)._factory; + var res = this._getReflectionInfo(type)._factory; return isPresent(res) ? res : null; } else { return this.reflectionCapabilities.factory(type); @@ -75,7 +98,7 @@ export class Reflector { parameters(typeOrFunc: /*Type*/ any): List { if (this._injectableInfo.has(typeOrFunc)) { - var res = this._injectableInfo.get(typeOrFunc)._parameters; + var res = this._getReflectionInfo(typeOrFunc)._parameters; return isPresent(res) ? res : []; } else { return this.reflectionCapabilities.parameters(typeOrFunc); @@ -84,7 +107,7 @@ export class Reflector { annotations(typeOrFunc: /*Type*/ any): List { if (this._injectableInfo.has(typeOrFunc)) { - var res = this._injectableInfo.get(typeOrFunc)._annotations; + var res = this._getReflectionInfo(typeOrFunc)._annotations; return isPresent(res) ? res : []; } else { return this.reflectionCapabilities.annotations(typeOrFunc); @@ -93,7 +116,7 @@ export class Reflector { interfaces(type: Type): List { if (this._injectableInfo.has(type)) { - var res = this._injectableInfo.get(type)._interfaces; + var res = this._getReflectionInfo(type)._interfaces; return isPresent(res) ? res : []; } else { return this.reflectionCapabilities.interfaces(type); @@ -124,6 +147,13 @@ export class Reflector { } } + _getReflectionInfo(typeOrFunc) { + if (isPresent(this._usedKeys)) { + this._usedKeys.add(typeOrFunc); + } + return this._injectableInfo.get(typeOrFunc); + } + _containsReflectionInfo(typeOrFunc) { return this._injectableInfo.has(typeOrFunc); } } diff --git a/modules/angular2/test/reflection/reflector_spec.ts b/modules/angular2/test/reflection/reflector_spec.ts index c2839088f1ff..4649987f9b33 100644 --- a/modules/angular2/test/reflection/reflector_spec.ts +++ b/modules/angular2/test/reflection/reflector_spec.ts @@ -48,6 +48,29 @@ export function main() { beforeEach(() => { reflector = new Reflector(new ReflectionCapabilities()); }); + describe("usage tracking", () => { + beforeEach(() => { reflector = new Reflector(null); }); + + it("should be disabled by default", () => { + expect(() => reflector.listUnusedKeys()).toThrowError('Usage tracking is disabled'); + }); + + it("should report unused keys", () => { + reflector.trackUsage(); + expect(reflector.listUnusedKeys()).toEqual([]); + + reflector.registerType(AType, new ReflectionInfo(null, null, () => "AType")); + reflector.registerType(TestObj, new ReflectionInfo(null, null, () => "TestObj")); + expect(reflector.listUnusedKeys()).toEqual([AType, TestObj]); + + reflector.factory(AType); + expect(reflector.listUnusedKeys()).toEqual([TestObj]); + + reflector.factory(TestObj); + expect(reflector.listUnusedKeys()).toEqual([]); + }); + }); + describe("factory", () => { it("should create a factory for the given type", () => { var obj = reflector.factory(TestObj)(1, 2); From b986c54079e1082303027676fa2e596d75dfcd67 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Thu, 20 Aug 2015 16:25:34 -0700 Subject: [PATCH 0014/1265] chore: remove int in favor for number Closes #3511 --- modules/angular2/globals.d.ts | 1 - .../src/change_detection/codegen_name_util.ts | 12 +++---- .../differs/default_iterable_differ.ts | 28 ++++++++-------- .../src/change_detection/parser/parser.ts | 14 ++++---- .../src/core/compiler/element_binder.ts | 2 +- .../src/core/compiler/element_injector.ts | 5 +-- modules/angular2/src/core/compiler/view.ts | 6 ++-- modules/angular2/src/facade/async.ts | 10 +++--- modules/angular2/src/facade/intl.ts | 10 +++--- modules/angular2/src/facade/lang.dart | 2 +- modules/angular2/src/facade/lang.ts | 32 +++++++++---------- modules/angular2/src/pipes/limit_to_pipe.ts | 2 +- .../src/web-workers/shared/serializer.ts | 10 +++--- .../test/change_detection/iterable.ts | 2 +- .../change_detection/parser/lexer_spec.ts | 10 +++--- .../angular2/test/facade/collection_spec.ts | 2 +- .../src/components/grid_list/grid_list.ts | 6 ++-- .../src/naive_infinite_scroll/app.ts | 8 ++--- .../src/naive_infinite_scroll/cells.ts | 4 +-- .../src/naive_infinite_scroll/random_data.ts | 16 +++++----- .../benchpress/src/metric/perflog_metric.ts | 4 +-- modules/http/src/enums.ts | 2 +- 22 files changed, 95 insertions(+), 93 deletions(-) diff --git a/modules/angular2/globals.d.ts b/modules/angular2/globals.d.ts index 4e45e219f906..a0f11f922302 100644 --- a/modules/angular2/globals.d.ts +++ b/modules/angular2/globals.d.ts @@ -4,7 +4,6 @@ /// declare var assert: any; -declare type int = number; interface List extends Array {} diff --git a/modules/angular2/src/change_detection/codegen_name_util.ts b/modules/angular2/src/change_detection/codegen_name_util.ts index 4300c17ac9b7..eb5aaced3a15 100644 --- a/modules/angular2/src/change_detection/codegen_name_util.ts +++ b/modules/angular2/src/change_detection/codegen_name_util.ts @@ -83,13 +83,13 @@ export class CodegenNameUtil { return this._addFieldPrefix(_FIRST_PROTO_IN_CURRENT_BINDING); } - getLocalName(idx: int): string { return `l_${this._sanitizedNames[idx]}`; } + getLocalName(idx: number): string { return `l_${this._sanitizedNames[idx]}`; } - getEventLocalName(eb: EventBinding, idx: int): string { + getEventLocalName(eb: EventBinding, idx: number): string { return `l_${MapWrapper.get(this._sanitizedEventNames, eb)[idx]}`; } - getChangeName(idx: int): string { return `c_${this._sanitizedNames[idx]}`; } + getChangeName(idx: number): string { return `c_${this._sanitizedNames[idx]}`; } /** * Generate a statement initializing local variables used when detecting changes. @@ -133,9 +133,9 @@ export class CodegenNameUtil { getPreventDefaultAccesor(): string { return "preventDefault"; } - getFieldCount(): int { return this._sanitizedNames.length; } + getFieldCount(): number { return this._sanitizedNames.length; } - getFieldName(idx: int): string { return this._addFieldPrefix(this._sanitizedNames[idx]); } + getFieldName(idx: number): string { return this._addFieldPrefix(this._sanitizedNames[idx]); } getAllFieldNames(): List { var fieldList = []; @@ -188,7 +188,7 @@ export class CodegenNameUtil { '\n'); } - getPipeName(idx: int): string { + getPipeName(idx: number): string { return this._addFieldPrefix(`${this._sanitizedNames[idx]}_pipe`); } diff --git a/modules/angular2/src/change_detection/differs/default_iterable_differ.ts b/modules/angular2/src/change_detection/differs/default_iterable_differ.ts index 248532876149..f592cd07410b 100644 --- a/modules/angular2/src/change_detection/differs/default_iterable_differ.ts +++ b/modules/angular2/src/change_detection/differs/default_iterable_differ.ts @@ -26,7 +26,7 @@ export class DefaultIterableDifferFactory implements IterableDifferFactory { export class DefaultIterableDiffer implements IterableDiffer { private _collection = null; - private _length: int = null; + private _length: number = null; // Keeps track of the used records at any point in time (during & across `_check()` calls) private _linkedRecords: _DuplicateMap = null; // Keeps track of the removed records at any point in time during `_check()` calls. @@ -43,7 +43,7 @@ export class DefaultIterableDiffer implements IterableDiffer { get collection() { return this._collection; } - get length(): int { return this._length; } + get length(): number { return this._length; } forEachItem(fn: Function) { var record: CollectionChangeRecord; @@ -101,7 +101,7 @@ export class DefaultIterableDiffer implements IterableDiffer { var record: CollectionChangeRecord = this._itHead; var mayBeDirty: boolean = false; - var index: int; + var index: number; var item; if (isArray(collection)) { @@ -185,7 +185,7 @@ export class DefaultIterableDiffer implements IterableDiffer { * - `item` is the current item in the collection * - `index` is the position of the item in the collection */ - _mismatch(record: CollectionChangeRecord, item, index: int): CollectionChangeRecord { + _mismatch(record: CollectionChangeRecord, item, index: number): CollectionChangeRecord { // The previous record after which we will append the current one. var previousRecord: CollectionChangeRecord; @@ -241,7 +241,7 @@ export class DefaultIterableDiffer implements IterableDiffer { * better way to think of it is as insert of 'b' rather then switch 'a' with 'b' and then add 'a' * at the end. */ - _verifyReinsertion(record: CollectionChangeRecord, item, index: int): CollectionChangeRecord { + _verifyReinsertion(record: CollectionChangeRecord, item, index: number): CollectionChangeRecord { var reinsertRecord: CollectionChangeRecord = this._unlinkedRecords === null ? null : this._unlinkedRecords.get(item); if (reinsertRecord !== null) { @@ -284,7 +284,7 @@ export class DefaultIterableDiffer implements IterableDiffer { } _reinsertAfter(record: CollectionChangeRecord, prevRecord: CollectionChangeRecord, - index: int): CollectionChangeRecord { + index: number): CollectionChangeRecord { if (this._unlinkedRecords !== null) { this._unlinkedRecords.remove(record); } @@ -308,7 +308,7 @@ export class DefaultIterableDiffer implements IterableDiffer { } _moveAfter(record: CollectionChangeRecord, prevRecord: CollectionChangeRecord, - index: int): CollectionChangeRecord { + index: number): CollectionChangeRecord { this._unlink(record); this._insertAfter(record, prevRecord, index); this._addToMoves(record, index); @@ -316,7 +316,7 @@ export class DefaultIterableDiffer implements IterableDiffer { } _addAfter(record: CollectionChangeRecord, prevRecord: CollectionChangeRecord, - index: int): CollectionChangeRecord { + index: number): CollectionChangeRecord { this._insertAfter(record, prevRecord, index); if (this._additionsTail === null) { @@ -333,7 +333,7 @@ export class DefaultIterableDiffer implements IterableDiffer { } _insertAfter(record: CollectionChangeRecord, prevRecord: CollectionChangeRecord, - index: int): CollectionChangeRecord { + index: number): CollectionChangeRecord { // todo(vicb) // assert(record != prevRecord); // assert(record._next === null); @@ -395,7 +395,7 @@ export class DefaultIterableDiffer implements IterableDiffer { return record; } - _addToMoves(record: CollectionChangeRecord, toIndex: int): CollectionChangeRecord { + _addToMoves(record: CollectionChangeRecord, toIndex: number): CollectionChangeRecord { // todo(vicb) // assert(record._nextMoved === null); @@ -473,8 +473,8 @@ export class DefaultIterableDiffer implements IterableDiffer { } export class CollectionChangeRecord { - currentIndex: int = null; - previousIndex: int = null; + currentIndex: number = null; + previousIndex: number = null; _nextPrevious: CollectionChangeRecord = null; _prev: CollectionChangeRecord = null; @@ -524,7 +524,7 @@ class _DuplicateItemRecordList { // Returns a CollectionChangeRecord having CollectionChangeRecord.item == item and // CollectionChangeRecord.currentIndex >= afterIndex - get(item: any, afterIndex: int): CollectionChangeRecord { + get(item: any, afterIndex: number): CollectionChangeRecord { var record: CollectionChangeRecord; for (record = this._head; record !== null; record = record._nextDup) { if ((afterIndex === null || afterIndex < record.currentIndex) && @@ -588,7 +588,7 @@ class _DuplicateMap { * Use case: `[a, b, c, a, a]` if we are at index `3` which is the second `a` then asking if we * have any more `a`s needs to return the last `a` not the first or second. */ - get(value: any, afterIndex: int = null): CollectionChangeRecord { + get(value: any, afterIndex: number = null): CollectionChangeRecord { var key = getMapKey(value); var recordList = this.map.get(key); diff --git a/modules/angular2/src/change_detection/parser/parser.ts b/modules/angular2/src/change_detection/parser/parser.ts index 296e4f35f040..7b1519df3b8c 100644 --- a/modules/angular2/src/change_detection/parser/parser.ts +++ b/modules/angular2/src/change_detection/parser/parser.ts @@ -122,24 +122,24 @@ export class Parser { } export class _ParseAST { - index: int = 0; + index: number = 0; constructor(public input: string, public location: any, public tokens: List, public reflector: Reflector, public parseAction: boolean) {} - peek(offset: int): Token { + peek(offset: number): Token { var i = this.index + offset; return i < this.tokens.length ? this.tokens[i] : EOF; } get next(): Token { return this.peek(0); } - get inputIndex(): int { + get inputIndex(): number { return (this.index < this.tokens.length) ? this.next.index : this.input.length; } advance() { this.index++; } - optionalCharacter(code: int): boolean { + optionalCharacter(code: number): boolean { if (this.next.isCharacter(code)) { this.advance(); return true; @@ -159,7 +159,7 @@ export class _ParseAST { peekKeywordVar(): boolean { return this.next.isKeywordVar() || this.next.isOperator('#'); } - expectCharacter(code: int) { + expectCharacter(code: number) { if (this.optionalCharacter(code)) return; this.error(`Missing expected ${StringWrapper.fromCharCode(code)}`); } @@ -453,7 +453,7 @@ export class _ParseAST { throw new BaseException("Fell through all cases in parsePrimary"); } - parseExpressionList(terminator: int): List { + parseExpressionList(terminator: number): List { var result = []; if (!this.next.isCharacter(terminator)) { do { @@ -606,7 +606,7 @@ export class _ParseAST { return bindings; } - error(message: string, index: int = null) { + error(message: string, index: number = null) { if (isBlank(index)) index = this.index; var location = (index < this.tokens.length) ? `at column ${this.tokens[index].index + 1} in` : diff --git a/modules/angular2/src/core/compiler/element_binder.ts b/modules/angular2/src/core/compiler/element_binder.ts index 7a2676c008df..6f93f5908d31 100644 --- a/modules/angular2/src/core/compiler/element_binder.ts +++ b/modules/angular2/src/core/compiler/element_binder.ts @@ -7,7 +7,7 @@ export class ElementBinder { // updated later, so we are able to resolve cycles nestedProtoView: viewModule.AppProtoView = null; - constructor(public index: int, public parent: ElementBinder, public distanceToParent: int, + constructor(public index: number, public parent: ElementBinder, public distanceToParent: number, public protoElementInjector: eiModule.ProtoElementInjector, public componentDirective: DirectiveBinding) { if (isBlank(index)) { diff --git a/modules/angular2/src/core/compiler/element_injector.ts b/modules/angular2/src/core/compiler/element_injector.ts index 488644f19779..17950813699b 100644 --- a/modules/angular2/src/core/compiler/element_injector.ts +++ b/modules/angular2/src/core/compiler/element_injector.ts @@ -378,8 +378,9 @@ export class ProtoElementInjector { - constructor(public parent: ProtoElementInjector, public index: int, bwv: BindingWithVisibility[], - public distanceToParent: number, public _firstBindingIsComponent: boolean, + constructor(public parent: ProtoElementInjector, public index: number, + bwv: BindingWithVisibility[], public distanceToParent: number, + public _firstBindingIsComponent: boolean, public directiveVariableBindings: Map) { var length = bwv.length; diff --git a/modules/angular2/src/core/compiler/view.ts b/modules/angular2/src/core/compiler/view.ts index dbcb8777eca2..5c2a7c04cff1 100644 --- a/modules/angular2/src/core/compiler/view.ts +++ b/modules/angular2/src/core/compiler/view.ts @@ -162,9 +162,9 @@ export class AppView implements ChangeDispatcher, RenderEventDispatcher { * * @param {string} eventName * @param {*} eventObj - * @param {int} boundElementIndex + * @param {number} boundElementIndex */ - triggerEventHandlers(eventName: string, eventObj: Event, boundElementIndex: int): void { + triggerEventHandlers(eventName: string, eventObj: Event, boundElementIndex: number): void { var locals = new Map(); locals.set('$event', eventObj); this.dispatchEvent(boundElementIndex, eventName, locals); @@ -328,7 +328,7 @@ export class AppProtoView { } } - bindElement(parent: ElementBinder, distanceToParent: int, + bindElement(parent: ElementBinder, distanceToParent: number, protoElementInjector: ProtoElementInjector, componentDirective: DirectiveBinding = null): ElementBinder { var elBinder = new ElementBinder(this.elementBinders.length, parent, distanceToParent, diff --git a/modules/angular2/src/facade/async.ts b/modules/angular2/src/facade/async.ts index 0c582bce4509..67e32b5741b0 100644 --- a/modules/angular2/src/facade/async.ts +++ b/modules/angular2/src/facade/async.ts @@ -58,11 +58,13 @@ export class PromiseWrapper { } export class TimerWrapper { - static setTimeout(fn: Function, millis: int): int { return global.setTimeout(fn, millis); } - static clearTimeout(id: int): void { global.clearTimeout(id); } + static setTimeout(fn: Function, millis: number): number { return global.setTimeout(fn, millis); } + static clearTimeout(id: number): void { global.clearTimeout(id); } - static setInterval(fn: Function, millis: int): int { return global.setInterval(fn, millis); } - static clearInterval(id: int): void { global.clearInterval(id); } + static setInterval(fn: Function, millis: number): number { + return global.setInterval(fn, millis); + } + static clearInterval(id: number): void { global.clearInterval(id); } } export class ObservableWrapper { diff --git a/modules/angular2/src/facade/intl.ts b/modules/angular2/src/facade/intl.ts index 27ed8d1dc2fc..3a8c983a5f24 100644 --- a/modules/angular2/src/facade/intl.ts +++ b/modules/angular2/src/facade/intl.ts @@ -51,9 +51,9 @@ export class NumberFormatter { static format(number: number, locale: string, style: NumberFormatStyle, {minimumIntegerDigits = 1, minimumFractionDigits = 0, maximumFractionDigits = 3, currency, currencyAsSymbol = false}: { - minimumIntegerDigits?: int, - minimumFractionDigits?: int, - maximumFractionDigits?: int, + minimumIntegerDigits?: number, + minimumFractionDigits?: number, + maximumFractionDigits?: number, currency?: string, currencyAsSymbol?: boolean } = {}): string { @@ -71,10 +71,10 @@ export class NumberFormatter { } } -function digitCondition(len: int): string { +function digitCondition(len: number): string { return len == 2 ? '2-digit' : 'numeric'; } -function nameCondition(len: int): string { +function nameCondition(len: number): string { return len < 4 ? 'short' : 'long'; } function extractComponents(pattern: string): Intl.DateTimeFormatOptions { diff --git a/modules/angular2/src/facade/lang.dart b/modules/angular2/src/facade/lang.dart index c5612854373d..fa071bcda00d 100644 --- a/modules/angular2/src/facade/lang.dart +++ b/modules/angular2/src/facade/lang.dart @@ -50,7 +50,7 @@ int serializeEnum(val) { * val should be the indexed value of the enum (sa returned from @Link{serializeEnum}) * values should be a map from indexes to values for the enum that you want to deserialize. */ -dynamic deserializeEnum(int val, Map values) { +dynamic deserializeEnum(num val, Map values) { return values[val]; } diff --git a/modules/angular2/src/facade/lang.ts b/modules/angular2/src/facade/lang.ts index 7714db62fdaf..abed29658930 100644 --- a/modules/angular2/src/facade/lang.ts +++ b/modules/angular2/src/facade/lang.ts @@ -53,7 +53,7 @@ _global.assert = function assert(condition) { } }; -export function ENUM_INDEX(value: int): int { +export function ENUM_INDEX(value: number): number { return value; } @@ -139,18 +139,18 @@ export function stringify(token): string { // serialize / deserialize enum exist only for consistency with dart API // enums in typescript don't need to be serialized -export function serializeEnum(val): int { +export function serializeEnum(val): number { return val; } -export function deserializeEnum(val, values: Map): any { +export function deserializeEnum(val, values: Map): any { return val; } export class StringWrapper { - static fromCharCode(code: int): string { return String.fromCharCode(code); } + static fromCharCode(code: number): string { return String.fromCharCode(code); } - static charCodeAt(s: string, index: int): number { return s.charCodeAt(index); } + static charCodeAt(s: string, index: number): number { return s.charCodeAt(index); } static split(s: string, regExp: RegExp): List { return s.split(regExp); } @@ -170,7 +170,7 @@ export class StringWrapper { static startsWith(s: string, start: string): boolean { return s.startsWith(start); } - static substring(s: string, start: int, end: int = null): string { + static substring(s: string, start: number, end: number = null): string { return s.substring(start, end === null ? undefined : end); } @@ -185,7 +185,7 @@ export class StringWrapper { static contains(s: string, substr: string): boolean { return s.indexOf(substr) != -1; } - static compare(a: string, b: string): int { + static compare(a: string, b: string): number { if (a < b) { return -1; } else if (a > b) { @@ -214,19 +214,19 @@ export class NumberParseError extends BaseException { export class NumberWrapper { - static toFixed(n: number, fractionDigits: int): string { return n.toFixed(fractionDigits); } + static toFixed(n: number, fractionDigits: number): string { return n.toFixed(fractionDigits); } static equal(a: number, b: number): boolean { return a === b; } - static parseIntAutoRadix(text: string): int { - var result: int = parseInt(text); + static parseIntAutoRadix(text: string): number { + var result: number = parseInt(text); if (isNaN(result)) { throw new NumberParseError("Invalid integer literal when parsing " + text); } return result; } - static parseInt(text: string, radix: int): int { + static parseInt(text: string, radix: number): number { if (radix == 10) { if (/^(\-|\+)?[0-9]+$/.test(text)) { return parseInt(text, radix); @@ -236,7 +236,7 @@ export class NumberWrapper { return parseInt(text, radix); } } else { - var result: int = parseInt(text, radix); + var result: number = parseInt(text, radix); if (!isNaN(result)) { return result; } @@ -338,12 +338,12 @@ export class Json { } export class DateWrapper { - static create(year: int, month: int = 1, day: int = 1, hour: int = 0, minutes: int = 0, - seconds: int = 0, milliseconds: int = 0): Date { + static create(year: number, month: number = 1, day: number = 1, hour: number = 0, + minutes: number = 0, seconds: number = 0, milliseconds: number = 0): Date { return new Date(year, month - 1, day, hour, minutes, seconds, milliseconds); } - static fromMillis(ms: int): Date { return new Date(ms); } - static toMillis(date: Date): int { return date.getTime(); } + static fromMillis(ms: number): Date { return new Date(ms); } + static toMillis(date: Date): number { return date.getTime(); } static now(): Date { return new Date(); } static toJson(date: Date): string { return date.toJSON(); } } diff --git a/modules/angular2/src/pipes/limit_to_pipe.ts b/modules/angular2/src/pipes/limit_to_pipe.ts index 7cd2bccdebbf..c2645884d0d5 100644 --- a/modules/angular2/src/pipes/limit_to_pipe.ts +++ b/modules/angular2/src/pipes/limit_to_pipe.ts @@ -67,7 +67,7 @@ export class LimitToPipe implements PipeTransform { throw new InvalidPipeArgumentException(LimitToPipe, value); } if (isBlank(value)) return value; - var limit: int = args[0]; + var limit: number = args[0]; var left = 0, right = Math.min(limit, value.length); if (limit < 0) { left = Math.max(0, value.length + limit); diff --git a/modules/angular2/src/web-workers/shared/serializer.ts b/modules/angular2/src/web-workers/shared/serializer.ts index 2ed922ed83b9..9d8940accf6b 100644 --- a/modules/angular2/src/web-workers/shared/serializer.ts +++ b/modules/angular2/src/web-workers/shared/serializer.ts @@ -42,24 +42,24 @@ import { @Injectable() export class Serializer { - private _enumRegistry: Map>; + private _enumRegistry: Map>; constructor(private _parser: Parser, private _protoViewStore: RenderProtoViewRefStore, private _renderViewStore: RenderViewWithFragmentsStore) { - this._enumRegistry = new Map>(); + this._enumRegistry = new Map>(); - var viewTypeMap = new Map(); + var viewTypeMap = new Map(); viewTypeMap[0] = ViewType.HOST; viewTypeMap[1] = ViewType.COMPONENT; viewTypeMap[2] = ViewType.EMBEDDED; this._enumRegistry.set(ViewType, viewTypeMap); - var viewEncapsulationMap = new Map(); + var viewEncapsulationMap = new Map(); viewEncapsulationMap[0] = ViewEncapsulation.EMULATED; viewEncapsulationMap[1] = ViewEncapsulation.NATIVE; viewEncapsulationMap[2] = ViewEncapsulation.NONE; this._enumRegistry.set(ViewEncapsulation, viewEncapsulationMap); - var propertyBindingTypeMap = new Map(); + var propertyBindingTypeMap = new Map(); propertyBindingTypeMap[0] = PropertyBindingType.PROPERTY; propertyBindingTypeMap[1] = PropertyBindingType.ATTRIBUTE; propertyBindingTypeMap[2] = PropertyBindingType.CLASS; diff --git a/modules/angular2/test/change_detection/iterable.ts b/modules/angular2/test/change_detection/iterable.ts index a322db6e66a7..17539b257f49 100644 --- a/modules/angular2/test/change_detection/iterable.ts +++ b/modules/angular2/test/change_detection/iterable.ts @@ -1,7 +1,7 @@ import {List} from 'angular2/src/facade/collection'; export class TestIterable { - list: List; + list: List; constructor() { this.list = []; } [Symbol.iterator]() { return this.list[Symbol.iterator](); } diff --git a/modules/angular2/test/change_detection/parser/lexer_spec.ts b/modules/angular2/test/change_detection/parser/lexer_spec.ts index c852d40861ce..c97602e2a11a 100644 --- a/modules/angular2/test/change_detection/parser/lexer_spec.ts +++ b/modules/angular2/test/change_detection/parser/lexer_spec.ts @@ -53,13 +53,13 @@ export function main() { describe('lexer', function() { describe('token', function() { it('should tokenize a simple identifier', function() { - var tokens: List = lex("j"); + var tokens: List = lex("j"); expect(tokens.length).toEqual(1); expectIdentifierToken(tokens[0], 0, 'j'); }); it('should tokenize a dotted identifier', function() { - var tokens: List = lex("j.k"); + var tokens: List = lex("j.k"); expect(tokens.length).toEqual(3); expectIdentifierToken(tokens[0], 0, 'j'); expectCharacterToken(tokens[1], 1, '.'); @@ -67,20 +67,20 @@ export function main() { }); it('should tokenize an operator', function() { - var tokens: List = lex("j-k"); + var tokens: List = lex("j-k"); expect(tokens.length).toEqual(3); expectOperatorToken(tokens[1], 1, '-'); }); it('should tokenize an indexed operator', function() { - var tokens: List = lex("j[k]"); + var tokens: List = lex("j[k]"); expect(tokens.length).toEqual(4); expectCharacterToken(tokens[1], 1, "["); expectCharacterToken(tokens[3], 3, "]"); }); it('should tokenize numbers', function() { - var tokens: List = lex("88"); + var tokens: List = lex("88"); expect(tokens.length).toEqual(1); expectNumberToken(tokens[0], 0, 88); }); diff --git a/modules/angular2/test/facade/collection_spec.ts b/modules/angular2/test/facade/collection_spec.ts index 2d0a5ed5786f..161a1259cf6b 100644 --- a/modules/angular2/test/facade/collection_spec.ts +++ b/modules/angular2/test/facade/collection_spec.ts @@ -10,7 +10,7 @@ import { export function main() { describe('ListWrapper', () => { - var l: List; + var l: List; describe('splice', () => { it('should remove sublist of given length and return it', () => { diff --git a/modules/angular2_material/src/components/grid_list/grid_list.ts b/modules/angular2_material/src/components/grid_list/grid_list.ts index 0058ccde66ed..f1291a21e8d8 100644 --- a/modules/angular2_material/src/components/grid_list/grid_list.ts +++ b/modules/angular2_material/src/components/grid_list/grid_list.ts @@ -302,13 +302,13 @@ export class MdGridTile { */ class TileCoordinator { // Tracking array (see class description). - tracker: List; + tracker: List; // Index at which the search for the next gap will start. - columnIndex: int; + columnIndex: number; // The current row index. - rowIndex: int; + rowIndex: number; // The computed (row, col) position of each tile (the output). positions: List; diff --git a/modules/benchmarks/src/naive_infinite_scroll/app.ts b/modules/benchmarks/src/naive_infinite_scroll/app.ts index dbe2e17d059f..c8af2b18c417 100644 --- a/modules/benchmarks/src/naive_infinite_scroll/app.ts +++ b/modules/benchmarks/src/naive_infinite_scroll/app.ts @@ -25,9 +25,9 @@ import {Component, Directive, View} from 'angular2/angular2'; ` }) export class App { - scrollAreas: List; - iterationCount: int; - scrollIncrement: int; + scrollAreas: List; + iterationCount: number; + scrollIncrement: number; constructor() { var appSize = getIntParameter('appSize'); @@ -50,7 +50,7 @@ export class App { runBenchmark() { var scrollDiv = this._getScrollDiv(); - var n: int = this.iterationCount; + var n: number = this.iterationCount; var scheduleScroll; scheduleScroll = () => { TimerWrapper.setTimeout(() => { diff --git a/modules/benchmarks/src/naive_infinite_scroll/cells.ts b/modules/benchmarks/src/naive_infinite_scroll/cells.ts index af0097bd1256..ee0b36160bf1 100644 --- a/modules/benchmarks/src/naive_infinite_scroll/cells.ts +++ b/modules/benchmarks/src/naive_infinite_scroll/cells.ts @@ -5,11 +5,11 @@ import {NgFor} from 'angular2/directives'; import {Component, Directive, View} from 'angular2/angular2'; export class HasStyle { - cellWidth: int; + cellWidth: number; constructor() {} - set width(w: int) { this.cellWidth = w; } + set width(w: number) { this.cellWidth = w; } } @Component({selector: 'company-name', properties: ['width: cell-width', 'company']}) diff --git a/modules/benchmarks/src/naive_infinite_scroll/random_data.ts b/modules/benchmarks/src/naive_infinite_scroll/random_data.ts index 80c859b7e0c0..1795df9655c4 100644 --- a/modules/benchmarks/src/naive_infinite_scroll/random_data.ts +++ b/modules/benchmarks/src/naive_infinite_scroll/random_data.ts @@ -10,7 +10,7 @@ import { AAT_STATUS_LIST } from './common'; -export function generateOfferings(count: int): List { +export function generateOfferings(count: number): List { var res = []; for (var i = 0; i < count; i++) { res.push(generateOffering(i)); @@ -18,7 +18,7 @@ export function generateOfferings(count: int): List { return res; } -export function generateOffering(seed: int): Offering { +export function generateOffering(seed: number): Offering { var res = new Offering(); res.name = generateName(seed++); res.company = generateCompany(seed++); @@ -34,19 +34,19 @@ export function generateOffering(seed: int): Offering { return res; } -export function generateCompany(seed: int): Company { +export function generateCompany(seed: number): Company { var res = new Company(); res.name = generateName(seed); return res; } -export function generateOpportunity(seed: int): Opportunity { +export function generateOpportunity(seed: number): Opportunity { var res = new Opportunity(); res.name = generateName(seed); return res; } -export function generateAccount(seed: int): Account { +export function generateAccount(seed: number): Account { var res = new Account(); res.accountId = seed; return res; @@ -68,13 +68,13 @@ var names = [ 'Stuff' ]; -function generateName(seed: int): string { +function generateName(seed: number): string { return names[seed % names.length]; } var offsets = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; -function randomDate(seed: int, minDate: CustomDate = null): CustomDate { +function randomDate(seed: number, minDate: CustomDate = null): CustomDate { if (minDate == null) { minDate = CustomDate.now(); } @@ -85,7 +85,7 @@ function randomDate(seed: int, minDate: CustomDate = null): CustomDate { var stringLengths = [5, 7, 9, 11, 13]; var charCodeOffsets = [0, 1, 2, 3, 4, 5, 6, 7, 8]; -function randomString(seed: int): string { +function randomString(seed: number): string { var len = stringLengths[seed % 5]; var str = ''; for (var i = 0; i < len; i++) { diff --git a/modules/benchpress/src/metric/perflog_metric.ts b/modules/benchpress/src/metric/perflog_metric.ts index 1fdf577b388f..b2dfb47f7703 100644 --- a/modules/benchpress/src/metric/perflog_metric.ts +++ b/modules/benchpress/src/metric/perflog_metric.ts @@ -25,7 +25,7 @@ export class PerflogMetric extends Metric { static get SET_TIMEOUT(): OpaqueToken { return _SET_TIMEOUT; } private _remainingEvents: List>; - private _measureCount: int; + private _measureCount: number; _perfLogFeatures: PerfLogFeatures; @@ -124,7 +124,7 @@ export class PerflogMetric extends Metric { .then((_) => this._readUntilEndMark(markName)); } - _readUntilEndMark(markName: string, loopCount: int = 0, startEvent = null) { + _readUntilEndMark(markName: string, loopCount: number = 0, startEvent = null) { if (loopCount > _MAX_RETRY_COUNT) { throw new BaseException(`Tried too often to get the ending mark: ${loopCount}`); } diff --git a/modules/http/src/enums.ts b/modules/http/src/enums.ts index e2da8affba9f..81e4afe7ee62 100644 --- a/modules/http/src/enums.ts +++ b/modules/http/src/enums.ts @@ -51,7 +51,7 @@ export enum RequestMethods { export class RequestMethodsMap { private _methods: List; constructor() { this._methods = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD', 'PATCH']; } - getMethod(method: int): string { return this._methods[method]; } + getMethod(method: number): string { return this._methods[method]; } } /** * All possible states in which a connection can be, based on From d2d0715568421b169dd0ecc4ffa5876849d4da9e Mon Sep 17 00:00:00 2001 From: vsavkin Date: Wed, 19 Aug 2015 11:26:45 -0700 Subject: [PATCH 0015/1265] feat(change_detection): do not reparse AST when using generated detectors --- .../abstract_change_detector.ts | 60 ++++------ .../src/change_detection/binding_record.ts | 113 +++++++++++------- .../src/change_detection/change_detection.ts | 17 ++- .../change_detection_jit_generator.ts | 49 +++++--- .../change_detection/change_detection_util.ts | 11 ++ .../angular2/src/change_detection/coalesce.ts | 10 +- .../change_detection/codegen_logic_util.ts | 20 +++- .../src/change_detection/codegen_name_util.ts | 8 +- .../dynamic_change_detector.ts | 25 ++-- .../src/change_detection/exceptions.ts | 14 +-- .../src/change_detection/interfaces.ts | 10 +- .../jit_proto_change_detector.ts | 7 +- .../pregen_proto_change_detector.dart | 26 +--- .../change_detection/proto_change_detector.ts | 49 ++++---- .../src/change_detection/proto_record.ts | 6 +- .../src/core/compiler/proto_view_factory.ts | 53 ++++++-- modules/angular2/src/core/compiler/view.ts | 14 +-- modules/angular2/src/facade/collection.dart | 6 + modules/angular2/src/facade/collection.ts | 5 + .../change_detector_codegen.dart | 49 ++++++-- .../reflection/reflection_capabilities.dart | 2 +- .../change_detection/change_detection_spec.ts | 4 +- .../change_detection/change_detector_spec.ts | 4 +- .../test/change_detection/coalesce_spec.ts | 5 +- .../proto_record_builder_spec.ts | 4 +- .../change_detection/proto_record_spec.ts | 4 +- .../core/compiler/element_injector_spec.ts | 4 +- .../core/compiler/proto_view_factory_spec.ts | 75 ++++++------ .../angular2/test/facade/collection_spec.ts | 12 ++ .../expected/bar.ng_deps.dart | 35 ++++-- .../change_detection_benchmark.ts | 7 +- 31 files changed, 426 insertions(+), 282 deletions(-) diff --git a/modules/angular2/src/change_detection/abstract_change_detector.ts b/modules/angular2/src/change_detection/abstract_change_detector.ts index 82efc68b469e..98abbbe6e92f 100644 --- a/modules/angular2/src/change_detection/abstract_change_detector.ts +++ b/modules/angular2/src/change_detection/abstract_change_detector.ts @@ -2,7 +2,7 @@ import {isPresent, isBlank, BaseException, StringWrapper} from 'angular2/src/fac import {List, ListWrapper} from 'angular2/src/facade/collection'; import {ChangeDetectionUtil} from './change_detection_util'; import {ChangeDetectorRef} from './change_detector_ref'; -import {DirectiveRecord} from './directive_record'; +import {DirectiveIndex} from './directive_record'; import {ChangeDetector, ChangeDispatcher} from './interfaces'; import {Pipes} from './pipes'; import { @@ -10,8 +10,7 @@ import { ExpressionChangedAfterItHasBeenCheckedException, DehydratedException } from './exceptions'; -import {ProtoRecord} from './proto_record'; -import {BindingRecord} from './binding_record'; +import {BindingTarget} from './binding_record'; import {Locals} from './parser/locals'; import {CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED} from './constants'; import {wtfCreateScope, wtfLeave, WtfScopeFn} from '../profile/profile'; @@ -20,9 +19,8 @@ import {isObservable} from './observable_facade'; var _scope_check: WtfScopeFn = wtfCreateScope(`ChangeDetector#check(ascii id, bool throwOnChange)`); class _Context { - constructor(public element: any, public componentElement: any, public instance: any, - public context: any, public locals: any, public injector: any, - public expression: any) {} + constructor(public element: any, public componentElement: any, public context: any, + public locals: any, public injector: any, public expression: any) {} } export class AbstractChangeDetector implements ChangeDetector { @@ -35,24 +33,19 @@ export class AbstractChangeDetector implements ChangeDetector { // change detection will fail. alreadyChecked: any = false; context: T; - directiveRecords: List; - dispatcher: ChangeDispatcher; locals: Locals = null; mode: string = null; pipes: Pipes = null; - firstProtoInCurrentBinding: number; - protos: List; + propertyBindingIndex: number; // This is an experimental feature. Works only in Dart. subscriptions: any[]; streams: any[]; - constructor(public id: string, dispatcher: ChangeDispatcher, protos: List, - directiveRecords: List, public modeOnHydrate: string) { + constructor(public id: string, public dispatcher: ChangeDispatcher, + public numberOfPropertyProtoRecords: number, public bindingTargets: BindingTarget[], + public directiveIndices: DirectiveIndex[], public modeOnHydrate: string) { this.ref = new ChangeDetectorRef(this); - this.directiveRecords = directiveRecords; - this.dispatcher = dispatcher; - this.protos = protos; } addChild(cd: ChangeDetector): void { @@ -99,7 +92,7 @@ export class AbstractChangeDetector implements ChangeDetector { // implementation of `detectChangesInRecordsInternal` which does the work of detecting changes // and which this method will call. // This method expects that `detectChangesInRecordsInternal` will set the property - // `this.firstProtoInCurrentBinding` to the selfIndex of the first proto record. This is to + // `this.propertyBindingIndex` to the propertyBindingIndex of the first proto record. This is to // facilitate error reporting. detectChangesInRecords(throwOnChange: boolean): void { if (!this.hydrated()) { @@ -115,9 +108,9 @@ export class AbstractChangeDetector implements ChangeDetector { // Subclasses should override this method to perform any work necessary to detect and report // changes. For example, changes should be reported via `ChangeDetectionUtil.addChange`, lifecycle // methods should be called, etc. - // This implementation should also set `this.firstProtoInCurrentBinding` to the selfIndex of the - // first proto record - // to facilitate error reporting. See {@link #detectChangesInRecords}. + // This implementation should also set `this.propertyBindingIndex` to the propertyBindingIndex of + // the + // first proto record to facilitate error reporting. See {@link #detectChangesInRecords}. detectChangesInRecordsInternal(throwOnChange: boolean): void {} // This method is not intended to be overridden. Subclasses should instead provide an @@ -195,8 +188,8 @@ export class AbstractChangeDetector implements ChangeDetector { protected observe(value: any, index: number): any { if (isObservable(value)) { if (isBlank(this.subscriptions)) { - this.subscriptions = ListWrapper.createFixedSize(this.protos.length + 1); - this.streams = ListWrapper.createFixedSize(this.protos.length + 1); + this.subscriptions = ListWrapper.createFixedSize(this.numberOfPropertyProtoRecords + 1); + this.streams = ListWrapper.createFixedSize(this.numberOfPropertyProtoRecords + 1); } if (isBlank(this.subscriptions[index])) { this.streams[index] = value.changes; @@ -211,7 +204,7 @@ export class AbstractChangeDetector implements ChangeDetector { } protected getDetectorFor(directives: any, index: number): ChangeDetector { - return directives.getDetectorFor(this.directiveRecords[index].directiveIndex); + return directives.getDetectorFor(this.directiveIndices[index]); } protected notifyDispatcher(value: any): void { @@ -223,31 +216,26 @@ export class AbstractChangeDetector implements ChangeDetector { if (isBlank(changes)) { changes = {}; } - changes[this._currentBinding().propertyName] = - ChangeDetectionUtil.simpleChange(oldValue, newValue); + changes[this._currentBinding().name] = ChangeDetectionUtil.simpleChange(oldValue, newValue); return changes; } private _throwError(exception: any, stack: any): void { - var proto = this._currentBindingProto(); - var c = this.dispatcher.getDebugContext(proto.bindingRecord.elementIndex, proto.directiveIndex); - var context = isPresent(c) ? new _Context(c.element, c.componentElement, c.directive, c.context, - c.locals, c.injector, proto.expressionAsString) : + var c = this.dispatcher.getDebugContext(this._currentBinding().elementIndex, null); + var context = isPresent(c) ? new _Context(c.element, c.componentElement, c.context, c.locals, + c.injector, this._currentBinding().debug) : null; - throw new ChangeDetectionError(proto, exception, stack, context); + throw new ChangeDetectionError(this._currentBinding().debug, exception, stack, context); } protected throwOnChangeError(oldValue: any, newValue: any): void { - var change = ChangeDetectionUtil.simpleChange(oldValue, newValue); - throw new ExpressionChangedAfterItHasBeenCheckedException(this._currentBindingProto(), change, - null); + throw new ExpressionChangedAfterItHasBeenCheckedException(this._currentBinding().debug, + oldValue, newValue, null); } protected throwDehydratedError(): void { throw new DehydratedException(); } - private _currentBinding(): BindingRecord { return this._currentBindingProto().bindingRecord; } - - private _currentBindingProto(): ProtoRecord { - return ChangeDetectionUtil.protoByIndex(this.protos, this.firstProtoInCurrentBinding); + private _currentBinding(): BindingTarget { + return this.bindingTargets[this.propertyBindingIndex]; } } diff --git a/modules/angular2/src/change_detection/binding_record.ts b/modules/angular2/src/change_detection/binding_record.ts index b92fd4312e7b..272ab345b09c 100644 --- a/modules/angular2/src/change_detection/binding_record.ts +++ b/modules/angular2/src/change_detection/binding_record.ts @@ -3,8 +3,10 @@ import {SetterFn} from 'angular2/src/reflection/types'; import {AST} from './parser/ast'; import {DirectiveIndex, DirectiveRecord} from './directive_record'; -const DIRECTIVE = "directive"; const DIRECTIVE_LIFECYCLE = "directiveLifecycle"; +const BINDING = "native"; + +const DIRECTIVE = "directive"; const ELEMENT_PROPERTY = "elementProperty"; const ELEMENT_ATTRIBUTE = "elementAttribute"; const ELEMENT_CLASS = "elementClass"; @@ -13,24 +15,12 @@ const TEXT_NODE = "textNode"; const EVENT = "event"; const HOST_EVENT = "hostEvent"; -export class BindingRecord { - constructor(public mode: string, public implicitReceiver: any, public ast: AST, - public elementIndex: number, public propertyName: string, public propertyUnit: string, - public eventName: string, public setter: SetterFn, public lifecycleEvent: string, - public directiveRecord: DirectiveRecord) {} - - callOnChange(): boolean { - return isPresent(this.directiveRecord) && this.directiveRecord.callOnChange; - } - - isDefaultChangeDetection(): boolean { - return isBlank(this.directiveRecord) || this.directiveRecord.isDefaultChangeDetection(); - } +export class BindingTarget { + constructor(public mode: string, public elementIndex: number, public name: string, + public unit: string, public debug: string) {} isDirective(): boolean { return this.mode === DIRECTIVE; } - isDirectiveLifecycle(): boolean { return this.mode === DIRECTIVE_LIFECYCLE; } - isElementProperty(): boolean { return this.mode === ELEMENT_PROPERTY; } isElementAttribute(): boolean { return this.mode === ELEMENT_ATTRIBUTE; } @@ -40,87 +30,118 @@ export class BindingRecord { isElementStyle(): boolean { return this.mode === ELEMENT_STYLE; } isTextNode(): boolean { return this.mode === TEXT_NODE; } +} - static createForDirective(ast: AST, propertyName: string, setter: SetterFn, - directiveRecord: DirectiveRecord): BindingRecord { - return new BindingRecord(DIRECTIVE, 0, ast, 0, propertyName, null, null, setter, null, - directiveRecord); +export class BindingRecord { + constructor(public mode: string, public target: BindingTarget, public implicitReceiver: any, + public ast: AST, public setter: SetterFn, public lifecycleEvent: string, + public directiveRecord: DirectiveRecord) {} + + isDirectiveLifecycle(): boolean { return this.mode === DIRECTIVE_LIFECYCLE; } + + callOnChange(): boolean { + return isPresent(this.directiveRecord) && this.directiveRecord.callOnChange; } + isDefaultChangeDetection(): boolean { + return isBlank(this.directiveRecord) || this.directiveRecord.isDefaultChangeDetection(); + } + + static createDirectiveOnCheck(directiveRecord: DirectiveRecord): BindingRecord { - return new BindingRecord(DIRECTIVE_LIFECYCLE, 0, null, 0, null, null, null, null, "onCheck", - directiveRecord); + return new BindingRecord(DIRECTIVE_LIFECYCLE, null, 0, null, null, "onCheck", directiveRecord); } static createDirectiveOnInit(directiveRecord: DirectiveRecord): BindingRecord { - return new BindingRecord(DIRECTIVE_LIFECYCLE, 0, null, 0, null, null, null, null, "onInit", - directiveRecord); + return new BindingRecord(DIRECTIVE_LIFECYCLE, null, 0, null, null, "onInit", directiveRecord); } static createDirectiveOnChange(directiveRecord: DirectiveRecord): BindingRecord { - return new BindingRecord(DIRECTIVE_LIFECYCLE, 0, null, 0, null, null, null, null, "onChange", - directiveRecord); + return new BindingRecord(DIRECTIVE_LIFECYCLE, null, 0, null, null, "onChange", directiveRecord); + } + + + + static createForDirective(ast: AST, propertyName: string, setter: SetterFn, + directiveRecord: DirectiveRecord): BindingRecord { + var t = new BindingTarget(DIRECTIVE, null, propertyName, null, ast.toString()); + return new BindingRecord(DIRECTIVE, t, 0, ast, setter, null, directiveRecord); } + + static createForElementProperty(ast: AST, elementIndex: number, propertyName: string): BindingRecord { - return new BindingRecord(ELEMENT_PROPERTY, 0, ast, elementIndex, propertyName, null, null, null, - null, null); + var t = new BindingTarget(ELEMENT_PROPERTY, elementIndex, propertyName, null, ast.toString()); + return new BindingRecord(BINDING, t, 0, ast, null, null, null); } static createForElementAttribute(ast: AST, elementIndex: number, attributeName: string): BindingRecord { - return new BindingRecord(ELEMENT_ATTRIBUTE, 0, ast, elementIndex, attributeName, null, null, - null, null, null); + var t = new BindingTarget(ELEMENT_ATTRIBUTE, elementIndex, attributeName, null, ast.toString()); + return new BindingRecord(BINDING, t, 0, ast, null, null, null); } static createForElementClass(ast: AST, elementIndex: number, className: string): BindingRecord { - return new BindingRecord(ELEMENT_CLASS, 0, ast, elementIndex, className, null, null, null, null, - null); + var t = new BindingTarget(ELEMENT_CLASS, elementIndex, className, null, ast.toString()); + return new BindingRecord(BINDING, t, 0, ast, null, null, null); } static createForElementStyle(ast: AST, elementIndex: number, styleName: string, unit: string): BindingRecord { - return new BindingRecord(ELEMENT_STYLE, 0, ast, elementIndex, styleName, unit, null, null, null, - null); + var t = new BindingTarget(ELEMENT_STYLE, elementIndex, styleName, unit, ast.toString()); + return new BindingRecord(BINDING, t, 0, ast, null, null, null); } + + static createForHostProperty(directiveIndex: DirectiveIndex, ast: AST, propertyName: string): BindingRecord { - return new BindingRecord(ELEMENT_PROPERTY, directiveIndex, ast, directiveIndex.elementIndex, - propertyName, null, null, null, null, null); + var t = new BindingTarget(ELEMENT_PROPERTY, directiveIndex.elementIndex, propertyName, null, + ast.toString()); + return new BindingRecord(BINDING, t, directiveIndex, ast, null, null, null); } static createForHostAttribute(directiveIndex: DirectiveIndex, ast: AST, attributeName: string): BindingRecord { - return new BindingRecord(ELEMENT_ATTRIBUTE, directiveIndex, ast, directiveIndex.elementIndex, - attributeName, null, null, null, null, null); + var t = new BindingTarget(ELEMENT_ATTRIBUTE, directiveIndex.elementIndex, attributeName, null, + ast.toString()); + return new BindingRecord(BINDING, t, directiveIndex, ast, null, null, null); } static createForHostClass(directiveIndex: DirectiveIndex, ast: AST, className: string): BindingRecord { - return new BindingRecord(ELEMENT_CLASS, directiveIndex, ast, directiveIndex.elementIndex, - className, null, null, null, null, null); + var t = new BindingTarget(ELEMENT_CLASS, directiveIndex.elementIndex, className, null, + ast.toString()); + return new BindingRecord(BINDING, t, directiveIndex, ast, null, null, null); } static createForHostStyle(directiveIndex: DirectiveIndex, ast: AST, styleName: string, unit: string): BindingRecord { - return new BindingRecord(ELEMENT_STYLE, directiveIndex, ast, directiveIndex.elementIndex, - styleName, unit, null, null, null, null); + var t = new BindingTarget(ELEMENT_STYLE, directiveIndex.elementIndex, styleName, unit, + ast.toString()); + return new BindingRecord(BINDING, t, directiveIndex, ast, null, null, null); } + + static createForTextNode(ast: AST, elementIndex: number): BindingRecord { - return new BindingRecord(TEXT_NODE, 0, ast, elementIndex, null, null, null, null, null, null); + var t = new BindingTarget(TEXT_NODE, elementIndex, null, null, ast.toString()); + return new BindingRecord(BINDING, t, 0, ast, null, null, null); } + + static createForEvent(ast: AST, eventName: string, elementIndex: number): BindingRecord { - return new BindingRecord(EVENT, 0, ast, elementIndex, null, null, eventName, null, null, null); + var t = new BindingTarget(EVENT, elementIndex, eventName, null, ast.toString()); + return new BindingRecord(EVENT, t, 0, ast, null, null, null); } static createForHostEvent(ast: AST, eventName: string, directiveRecord: DirectiveRecord): BindingRecord { var directiveIndex = directiveRecord.directiveIndex; - return new BindingRecord(EVENT, directiveIndex, ast, directiveIndex.elementIndex, null, null, - eventName, null, null, directiveRecord); + var t = + new BindingTarget(HOST_EVENT, directiveIndex.elementIndex, eventName, null, ast.toString()); + return new BindingRecord(HOST_EVENT, t, directiveIndex, ast, null, null, directiveRecord); } } diff --git a/modules/angular2/src/change_detection/change_detection.ts b/modules/angular2/src/change_detection/change_detection.ts index e189844ba0df..68bd95d9f42d 100644 --- a/modules/angular2/src/change_detection/change_detection.ts +++ b/modules/angular2/src/change_detection/change_detection.ts @@ -38,7 +38,7 @@ export { } from './interfaces'; export {CHECK_ONCE, CHECK_ALWAYS, DETACHED, CHECKED, ON_PUSH, DEFAULT} from './constants'; export {DynamicProtoChangeDetector} from './proto_change_detector'; -export {BindingRecord} from './binding_record'; +export {BindingRecord, BindingTarget} from './binding_record'; export {DirectiveIndex, DirectiveRecord} from './directive_record'; export {DynamicChangeDetector} from './dynamic_change_detector'; export {ChangeDetectorRef} from './change_detector_ref'; @@ -93,13 +93,14 @@ export class PreGeneratedChangeDetection extends ChangeDetection { static isSupported(): boolean { return PregenProtoChangeDetector.isSupported(); } - createProtoChangeDetector(definition: ChangeDetectorDefinition): ProtoChangeDetector { - var id = definition.id; + getProtoChangeDetector(id: string, definition: ChangeDetectorDefinition): ProtoChangeDetector { if (StringMapWrapper.contains(this._protoChangeDetectorFactories, id)) { return StringMapWrapper.get(this._protoChangeDetectorFactories, id)(definition); } - return this._dynamicChangeDetection.createProtoChangeDetector(definition); + return this._dynamicChangeDetection.getProtoChangeDetector(id, definition); } + + get generateDetectors(): boolean { return true; } } @@ -110,9 +111,11 @@ export class PreGeneratedChangeDetection extends ChangeDetection { */ @Injectable() export class DynamicChangeDetection extends ChangeDetection { - createProtoChangeDetector(definition: ChangeDetectorDefinition): ProtoChangeDetector { + getProtoChangeDetector(id: string, definition: ChangeDetectorDefinition): ProtoChangeDetector { return new DynamicProtoChangeDetector(definition); } + + get generateDetectors(): boolean { return true; } } /** @@ -126,7 +129,9 @@ export class DynamicChangeDetection extends ChangeDetection { export class JitChangeDetection extends ChangeDetection { static isSupported(): boolean { return JitProtoChangeDetector.isSupported(); } - createProtoChangeDetector(definition: ChangeDetectorDefinition): ProtoChangeDetector { + getProtoChangeDetector(id: string, definition: ChangeDetectorDefinition): ProtoChangeDetector { return new JitProtoChangeDetector(definition); } + + get generateDetectors(): boolean { return true; } } 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 78609c008198..3ba1564d5f1b 100644 --- a/modules/angular2/src/change_detection/change_detection_jit_generator.ts +++ b/modules/angular2/src/change_detection/change_detection_jit_generator.ts @@ -9,6 +9,7 @@ import {ProtoRecord, RecordType} from './proto_record'; import {CodegenNameUtil, sanitizeName} from './codegen_name_util'; import {CodegenLogicUtil} from './codegen_logic_util'; import {EventBinding} from './event_binding'; +import {BindingTarget} from './binding_record'; /** @@ -30,9 +31,10 @@ export class ChangeDetectorJITGenerator { _names: CodegenNameUtil; _typeName: string; - constructor(public id: string, private changeDetectionStrategy: string, - public records: List, public eventBindings: EventBinding[], - public directiveRecords: List, private generateCheckNoChanges: boolean) { + constructor(private id: string, private changeDetectionStrategy: string, + private records: List, private propertyBindingTargets: BindingTarget[], + private eventBindings: EventBinding[], private directiveRecords: List, + private devMode: boolean) { this._names = new CodegenNameUtil(this.records, this.eventBindings, this.directiveRecords, UTIL); this._logic = new CodegenLogicUtil(this._names, UTIL, changeDetectionStrategy); @@ -41,9 +43,10 @@ export class ChangeDetectorJITGenerator { generate(): Function { var classDefinition = ` - var ${this._typeName} = function ${this._typeName}(dispatcher, protos, directiveRecords) { + var ${this._typeName} = function ${this._typeName}(dispatcher) { ${ABSTRACT_CHANGE_DETECTOR}.call( - this, ${JSON.stringify(this.id)}, dispatcher, protos, directiveRecords, + this, ${JSON.stringify(this.id)}, dispatcher, ${this.records.length}, + ${this._typeName}.gen_propertyBindingTargets, ${this._typeName}.gen_directiveIndices, "${ChangeDetectionUtil.changeDetectionMode(this.changeDetectionStrategy)}"); this.dehydrateDirectives(false); } @@ -70,13 +73,27 @@ export class ChangeDetectorJITGenerator { ${this._maybeGenDehydrateDirectives()} + ${this._genPropertyBindingTargets()}; + + ${this._genDirectiveIndices()}; + return function(dispatcher) { - return new ${this._typeName}(dispatcher, protos, directiveRecords); + return new ${this._typeName}(dispatcher); } `; - return new Function('AbstractChangeDetector', 'ChangeDetectionUtil', 'protos', - 'directiveRecords', classDefinition)( - AbstractChangeDetector, ChangeDetectionUtil, this.records, this.directiveRecords); + + return new Function(ABSTRACT_CHANGE_DETECTOR, UTIL, classDefinition)(AbstractChangeDetector, + ChangeDetectionUtil); + } + + _genPropertyBindingTargets(): string { + var targets = this._logic.genPropertyBindingTargets(this.propertyBindingTargets, true); + return `${this._typeName}.gen_propertyBindingTargets = ${targets};`; + } + + _genDirectiveIndices(): string { + var indices = this._logic.genDirectiveIndices(this.directiveRecords); + return `${this._typeName}.gen_directiveIndices = ${indices};`; } _maybeGenHandleEventInternal(): string { @@ -156,7 +173,7 @@ export class ChangeDetectorJITGenerator { var lines = ListWrapper.createFixedSize(directiveFieldNames.length); for (var i = 0, iLen = directiveFieldNames.length; i < iLen; ++i) { lines[i] = `${directiveFieldNames[i]} = directives.getDirectiveFor( - ${this._names.getDirectivesAccessorName()}[${i}].directiveIndex);`; + ${this._names.getDirectivesAccessorName()}[${i}]);`; } return lines.join('\n'); } @@ -284,9 +301,9 @@ export class ChangeDetectorJITGenerator { var oldValue = this._names.getFieldName(r.selfIndex); var br = r.bindingRecord; - if (br.isDirective()) { + if (br.target.isDirective()) { var directiveProperty = - `${this._names.getDirectiveName(br.directiveRecord.directiveIndex)}.${br.propertyName}`; + `${this._names.getDirectiveName(br.directiveRecord.directiveIndex)}.${br.target.name}`; return ` ${this._genThrowOnChangeCheck(oldValue, newValue)} ${directiveProperty} = ${newValue}; @@ -301,7 +318,7 @@ export class ChangeDetectorJITGenerator { } _genThrowOnChangeCheck(oldValue: string, newValue: string): string { - if (this.generateCheckNoChanges) { + if (this.devMode) { return ` if(throwOnChange) { this.throwOnChangeError(${oldValue}, ${newValue}); @@ -313,7 +330,7 @@ export class ChangeDetectorJITGenerator { } _genCheckNoChanges(): string { - if (this.generateCheckNoChanges) { + if (this.devMode) { return `${this._typeName}.prototype.checkNoChanges = function() { this.runDetectChanges(true); }`; } else { return ''; @@ -330,7 +347,9 @@ export class ChangeDetectorJITGenerator { _maybeFirstInBinding(r: ProtoRecord): string { var prev = ChangeDetectionUtil.protoByIndex(this.records, r.selfIndex - 1); var firstInBindng = isBlank(prev) || prev.bindingRecord !== r.bindingRecord; - return firstInBindng ? `${this._names.getFirstProtoInCurrentBinding()} = ${r.selfIndex};` : ''; + return firstInBindng && !r.bindingRecord.isDirectiveLifecycle() ? + `${this._names.getPropertyBindingIndex()} = ${r.propertyBindingIndex};` : + ''; } _maybeGenLastInDirective(r: ProtoRecord): string { diff --git a/modules/angular2/src/change_detection/change_detection_util.ts b/modules/angular2/src/change_detection/change_detection_util.ts index f05faa645783..d8784ecb26c7 100644 --- a/modules/angular2/src/change_detection/change_detection_util.ts +++ b/modules/angular2/src/change_detection/change_detection_util.ts @@ -16,6 +16,8 @@ import { isDefaultChangeDetectionStrategy } from './constants'; import {implementsOnDestroy} from './pipe_lifecycle_reflector'; +import {BindingTarget} from './binding_record'; +import {DirectiveIndex} from './directive_record'; /** @@ -201,4 +203,13 @@ export class ChangeDetectionUtil { pipe.onDestroy(); } } + + static bindingTarget(mode: string, elementIndex: number, name: string, unit: string, + debug: string): BindingTarget { + return new BindingTarget(mode, elementIndex, name, unit, debug); + } + + static directiveIndex(elementIndex: number, directiveIndex: number): DirectiveIndex { + return new DirectiveIndex(elementIndex, directiveIndex); + } } diff --git a/modules/angular2/src/change_detection/coalesce.ts b/modules/angular2/src/change_detection/coalesce.ts index d2d77964378b..d5353a1506b7 100644 --- a/modules/angular2/src/change_detection/coalesce.ts +++ b/modules/angular2/src/change_detection/coalesce.ts @@ -44,8 +44,8 @@ export function coalesce(records: ProtoRecord[]): ProtoRecord[] { function _selfRecord(r: ProtoRecord, contextIndex: number, selfIndex: number): ProtoRecord { return new ProtoRecord(RecordType.SELF, "self", null, [], r.fixedArgs, contextIndex, - r.directiveIndex, selfIndex, r.bindingRecord, r.expressionAsString, - r.lastInBinding, r.lastInDirective, false, false); + r.directiveIndex, selfIndex, r.bindingRecord, r.lastInBinding, + r.lastInDirective, false, false, r.propertyBindingIndex); } function _findMatching(r: ProtoRecord, rs: List) { @@ -70,9 +70,9 @@ function _replaceIndices(r: ProtoRecord, selfIndex: number, indexMap: Map _map(indexMap, a)); var contextIndex = _map(indexMap, r.contextIndex); return new ProtoRecord(r.mode, r.name, r.funcOrValue, args, r.fixedArgs, contextIndex, - r.directiveIndex, selfIndex, r.bindingRecord, r.expressionAsString, - r.lastInBinding, r.lastInDirective, r.argumentToPureFunction, - r.referencedBySelf); + r.directiveIndex, selfIndex, r.bindingRecord, r.lastInBinding, + r.lastInDirective, r.argumentToPureFunction, r.referencedBySelf, + r.propertyBindingIndex); } function _map(indexMap: Map, value: number) { diff --git a/modules/angular2/src/change_detection/codegen_logic_util.ts b/modules/angular2/src/change_detection/codegen_logic_util.ts index 1181c8243273..1dd632ed8476 100644 --- a/modules/angular2/src/change_detection/codegen_logic_util.ts +++ b/modules/angular2/src/change_detection/codegen_logic_util.ts @@ -1,8 +1,9 @@ import {ListWrapper} from 'angular2/src/facade/collection'; -import {BaseException, Json, StringWrapper} from 'angular2/src/facade/lang'; +import {BaseException, Json, StringWrapper, isPresent, isBlank} from 'angular2/src/facade/lang'; import {CodegenNameUtil} from './codegen_name_util'; import {codify, combineGeneratedStrings, rawString} from './codegen_facade'; import {ProtoRecord, RecordType} from './proto_record'; +import {BindingTarget} from './binding_record'; import {DirectiveRecord} from './directive_record'; /** @@ -123,6 +124,23 @@ export class CodegenLogicUtil { } } + genPropertyBindingTargets(propertyBindingTargets: BindingTarget[], devMode: boolean): string { + var bs = propertyBindingTargets.map(b => { + if (isBlank(b)) return "null"; + + var debug = devMode ? codify(b.debug) : "null"; + return `${this._utilName}.bindingTarget(${codify(b.mode)}, ${b.elementIndex}, ${codify(b.name)}, ${codify(b.unit)}, ${debug})`; + }); + return `[${bs.join(", ")}]`; + } + + genDirectiveIndices(directiveRecords: DirectiveRecord[]): string { + var bs = directiveRecords.map( + b => + `${this._utilName}.directiveIndex(${b.directiveIndex.elementIndex}, ${b.directiveIndex.directiveIndex})`); + return `[${bs.join(", ")}]`; + } + _genInterpolation(protoRec: ProtoRecord): string { var iVals = []; for (var i = 0; i < protoRec.args.length; ++i) { diff --git a/modules/angular2/src/change_detection/codegen_name_util.ts b/modules/angular2/src/change_detection/codegen_name_util.ts index eb5aaced3a15..53694a0dc845 100644 --- a/modules/angular2/src/change_detection/codegen_name_util.ts +++ b/modules/angular2/src/change_detection/codegen_name_util.ts @@ -10,8 +10,8 @@ import {EventBinding} from './event_binding'; // detection will fail. const _ALREADY_CHECKED_ACCESSOR = "alreadyChecked"; const _CONTEXT_ACCESSOR = "context"; -const _FIRST_PROTO_IN_CURRENT_BINDING = "firstProtoInCurrentBinding"; -const _DIRECTIVES_ACCESSOR = "directiveRecords"; +const _PROP_BINDING_INDEX = "propertyBindingIndex"; +const _DIRECTIVES_ACCESSOR = "directiveIndices"; const _DISPATCHER_ACCESSOR = "dispatcher"; const _LOCALS_ACCESSOR = "locals"; const _MODE_ACCESSOR = "mode"; @@ -79,9 +79,7 @@ export class CodegenNameUtil { getModeName(): string { return this._addFieldPrefix(_MODE_ACCESSOR); } - getFirstProtoInCurrentBinding(): string { - return this._addFieldPrefix(_FIRST_PROTO_IN_CURRENT_BINDING); - } + getPropertyBindingIndex(): string { return this._addFieldPrefix(_PROP_BINDING_INDEX); } getLocalName(idx: number): string { return `l_${this._sanitizedNames[idx]}`; } diff --git a/modules/angular2/src/change_detection/dynamic_change_detector.ts b/modules/angular2/src/change_detection/dynamic_change_detector.ts index ef6d98d5576f..2d23b2f12678 100644 --- a/modules/angular2/src/change_detection/dynamic_change_detector.ts +++ b/modules/angular2/src/change_detection/dynamic_change_detector.ts @@ -9,11 +9,11 @@ import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/faca import {AbstractChangeDetector} from './abstract_change_detector'; import {EventBinding} from './event_binding'; -import {BindingRecord} from './binding_record'; +import {BindingRecord, BindingTarget} from './binding_record'; +import {DirectiveRecord, DirectiveIndex} from './directive_record'; import {Locals} from './parser/locals'; import {ChangeDetectionUtil, SimpleChange} from './change_detection_util'; - import {ProtoRecord, RecordType} from './proto_record'; export class DynamicChangeDetector extends AbstractChangeDetector { @@ -23,12 +23,13 @@ export class DynamicChangeDetector extends AbstractChangeDetector { prevContexts: List; directives: any = null; - constructor(id: string, changeDetectionStrategy: string, dispatcher: any, - protos: List, public eventBindings: EventBinding[], - directiveRecords: List) { - super(id, dispatcher, protos, directiveRecords, - ChangeDetectionUtil.changeDetectionMode(changeDetectionStrategy)); - var len = protos.length + 1; + constructor(id: string, dispatcher: any, numberOfPropertyProtoRecords: number, + propertyBindingTargets: BindingTarget[], directiveIndices: DirectiveIndex[], + modeOnHydrate: string, private records: ProtoRecord[], + private eventBindings: EventBinding[], private directiveRecords: DirectiveRecord[]) { + super(id, dispatcher, numberOfPropertyProtoRecords, propertyBindingTargets, directiveIndices, + modeOnHydrate); + var len = records.length + 1; this.values = ListWrapper.createFixedSize(len); this.localPipes = ListWrapper.createFixedSize(len); this.prevContexts = ListWrapper.createFixedSize(len); @@ -109,7 +110,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector { checkNoChanges(): void { this.runDetectChanges(true); } detectChangesInRecordsInternal(throwOnChange: boolean) { - var protos = this.protos; + var protos = this.records; var changes = null; var isChanged = false; @@ -119,7 +120,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector { var directiveRecord = bindingRecord.directiveRecord; if (this._firstInBinding(proto)) { - this.firstProtoInCurrentBinding = proto.selfIndex; + this.propertyBindingIndex = proto.propertyBindingIndex; } if (proto.isLifeCycleRecord()) { @@ -154,7 +155,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector { } _firstInBinding(r: ProtoRecord): boolean { - var prev = ChangeDetectionUtil.protoByIndex(this.protos, r.selfIndex - 1); + var prev = ChangeDetectionUtil.protoByIndex(this.records, r.selfIndex - 1); return isBlank(prev) || prev.bindingRecord !== r.bindingRecord; } @@ -171,7 +172,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector { _updateDirectiveOrElement(change, bindingRecord) { if (isBlank(bindingRecord.directiveRecord)) { - this.dispatcher.notifyOnBinding(bindingRecord, change.currentValue); + super.notifyDispatcher(change.currentValue); } else { var directiveIndex = bindingRecord.directiveRecord.directiveIndex; bindingRecord.setter(this._getDirectiveFor(directiveIndex), change.currentValue); diff --git a/modules/angular2/src/change_detection/exceptions.ts b/modules/angular2/src/change_detection/exceptions.ts index 520da217eda3..e235d7371f33 100644 --- a/modules/angular2/src/change_detection/exceptions.ts +++ b/modules/angular2/src/change_detection/exceptions.ts @@ -1,4 +1,3 @@ -import {ProtoRecord} from './proto_record'; import {BaseException} from "angular2/src/facade/lang"; /** @@ -11,9 +10,9 @@ import {BaseException} from "angular2/src/facade/lang"; * This exception is only thrown in dev mode. */ export class ExpressionChangedAfterItHasBeenCheckedException extends BaseException { - constructor(proto: ProtoRecord, change: any, context: any) { - super(`Expression '${proto.expressionAsString}' has changed after it was checked. ` + - `Previous value: '${change.previousValue}'. Current value: '${change.currentValue}'`); + constructor(exp: string, oldValue: any, currValue: any, context: any) { + super(`Expression '${exp}' has changed after it was checked. ` + + `Previous value: '${oldValue}'. Current value: '${currValue}'`); } } @@ -28,10 +27,9 @@ export class ChangeDetectionError extends BaseException { */ location: string; - constructor(proto: ProtoRecord, originalException: any, originalStack: any, context: any) { - super(`${originalException} in [${proto.expressionAsString}]`, originalException, originalStack, - context); - this.location = proto.expressionAsString; + constructor(exp: string, originalException: any, originalStack: any, context: any) { + super(`${originalException} in [${exp}]`, originalException, originalStack, context); + this.location = exp; } } diff --git a/modules/angular2/src/change_detection/interfaces.ts b/modules/angular2/src/change_detection/interfaces.ts index b5d45ebf3e1b..08b9ed5ee716 100644 --- a/modules/angular2/src/change_detection/interfaces.ts +++ b/modules/angular2/src/change_detection/interfaces.ts @@ -1,7 +1,7 @@ import {List} from 'angular2/src/facade/collection'; import {CONST} from 'angular2/src/facade/lang'; import {Locals} from './parser/locals'; -import {BindingRecord} from './binding_record'; +import {BindingTarget, BindingRecord} from './binding_record'; import {DirectiveIndex, DirectiveRecord} from './directive_record'; import {ChangeDetectorRef} from './change_detector_ref'; @@ -32,9 +32,11 @@ import {ChangeDetectorRef} from './change_detector_ref'; */ @CONST() export class ChangeDetection { - createProtoChangeDetector(definition: ChangeDetectorDefinition): ProtoChangeDetector { + getProtoChangeDetector(id: string, definition: ChangeDetectorDefinition): ProtoChangeDetector { return null; } + + get generateDetectors(): boolean { return null; } } export class DebugContext { @@ -44,7 +46,7 @@ export class DebugContext { export interface ChangeDispatcher { getDebugContext(elementIndex: number, directiveIndex: DirectiveIndex): DebugContext; - notifyOnBinding(bindingRecord: BindingRecord, value: any): void; + notifyOnBinding(bindingTarget: BindingTarget, value: any): void; notifyOnAllChangesDone(): void; } @@ -72,5 +74,5 @@ export interface ProtoChangeDetector { instantiate(dispatcher: ChangeDispatcher) export class ChangeDetectorDefinition { constructor(public id: string, public strategy: string, public variableNames: List, public bindingRecords: BindingRecord[], public eventRecords: BindingRecord[], - public directiveRecords: DirectiveRecord[], public generateCheckNoChanges: boolean) {} + public directiveRecords: DirectiveRecord[], public devMode: boolean) {} } diff --git a/modules/angular2/src/change_detection/jit_proto_change_detector.ts b/modules/angular2/src/change_detection/jit_proto_change_detector.ts index df009b5cc430..fe063f4da404 100644 --- a/modules/angular2/src/change_detection/jit_proto_change_detector.ts +++ b/modules/angular2/src/change_detection/jit_proto_change_detector.ts @@ -1,4 +1,5 @@ import {ListWrapper} from 'angular2/src/facade/collection'; +import {isPresent} from 'angular2/src/facade/lang'; import {ProtoChangeDetector, ChangeDetector, ChangeDetectorDefinition} from './interfaces'; import {ChangeDetectorJITGenerator} from './change_detection_jit_generator'; @@ -20,9 +21,11 @@ export class JitProtoChangeDetector implements ProtoChangeDetector { _createFactory(definition: ChangeDetectorDefinition) { var propertyBindingRecords = createPropertyRecords(definition); var eventBindingRecords = createEventRecords(definition); + var propertyBindingTargets = this.definition.bindingRecords.map(b => b.target); + return new ChangeDetectorJITGenerator( - definition.id, definition.strategy, propertyBindingRecords, eventBindingRecords, - this.definition.directiveRecords, this.definition.generateCheckNoChanges) + definition.id, definition.strategy, propertyBindingRecords, propertyBindingTargets, + eventBindingRecords, this.definition.directiveRecords, this.definition.devMode) .generate(); } } 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 bdb9a7546dc9..27ba1d00b0ff 100644 --- a/modules/angular2/src/change_detection/pregen_proto_change_detector.dart +++ b/modules/angular2/src/change_detection/pregen_proto_change_detector.dart @@ -1,10 +1,6 @@ library angular2.src.change_detection.pregen_proto_change_detector; -import 'package:angular2/src/change_detection/coalesce.dart'; -import 'package:angular2/src/change_detection/directive_record.dart'; import 'package:angular2/src/change_detection/interfaces.dart'; -import 'package:angular2/src/change_detection/proto_change_detector.dart'; -import 'package:angular2/src/change_detection/proto_record.dart'; import 'package:angular2/src/facade/lang.dart' show looseIdentical; export 'dart:core' show List; @@ -26,8 +22,7 @@ export 'package:angular2/src/facade/lang.dart' show looseIdentical; typedef ProtoChangeDetector PregenProtoChangeDetectorFactory( ChangeDetectorDefinition definition); -typedef ChangeDetector InstantiateMethod(dynamic dispatcher, - List protoRecords, List directiveRecords); +typedef ChangeDetector InstantiateMethod(dynamic dispatcher); /// Implementation of [ProtoChangeDetector] for use by pre-generated change /// detectors in Angular 2 Dart. @@ -41,31 +36,18 @@ class PregenProtoChangeDetector extends ProtoChangeDetector { /// Closure used to generate an actual [ChangeDetector]. final InstantiateMethod _instantiateMethod; - // [ChangeDetector] dependencies. - final List _protoRecords; - final List _directiveRecords; - /// Internal ctor. - PregenProtoChangeDetector._(this.id, this._instantiateMethod, - this._protoRecords, this._directiveRecords); + PregenProtoChangeDetector._(this.id, this._instantiateMethod); static bool isSupported() => true; factory PregenProtoChangeDetector( InstantiateMethod instantiateMethod, ChangeDetectorDefinition def) { - // TODO(kegluneq): Pre-generate these (#2067). - var recordBuilder = new ProtoRecordBuilder(); - def.bindingRecords.forEach((b) { - recordBuilder.add(b, def.variableNames); - }); - var protoRecords = coalesce(recordBuilder.records); - return new PregenProtoChangeDetector._( - def.id, instantiateMethod, protoRecords, def.directiveRecords); + return new PregenProtoChangeDetector._(def.id, instantiateMethod); } @override - instantiate(dynamic dispatcher) => - _instantiateMethod(dispatcher, _protoRecords, _directiveRecords); + instantiate(dynamic dispatcher) => _instantiateMethod(dispatcher); } /// Provided as an optimization to cut down on '!' characters in generated diff --git a/modules/angular2/src/change_detection/proto_change_detector.ts b/modules/angular2/src/change_detection/proto_change_detector.ts index 28e8c246c3b5..f018c728a68e 100644 --- a/modules/angular2/src/change_detection/proto_change_detector.ts +++ b/modules/angular2/src/change_detection/proto_change_detector.ts @@ -29,7 +29,7 @@ import { import {ChangeDetector, ProtoChangeDetector, ChangeDetectorDefinition} from './interfaces'; import {ChangeDetectionUtil} from './change_detection_util'; import {DynamicChangeDetector} from './dynamic_change_detector'; -import {BindingRecord} from './binding_record'; +import {BindingRecord, BindingTarget} from './binding_record'; import {DirectiveRecord, DirectiveIndex} from './directive_record'; import {EventBinding} from './event_binding'; @@ -38,24 +38,30 @@ import {ProtoRecord, RecordType} from './proto_record'; export class DynamicProtoChangeDetector implements ProtoChangeDetector { _propertyBindingRecords: ProtoRecord[]; + _propertyBindingTargets: BindingTarget[]; _eventBindingRecords: EventBinding[]; + _directiveIndices: DirectiveIndex[]; constructor(private definition: ChangeDetectorDefinition) { this._propertyBindingRecords = createPropertyRecords(definition); this._eventBindingRecords = createEventRecords(definition); + this._propertyBindingTargets = this.definition.bindingRecords.map(b => b.target); + this._directiveIndices = this.definition.directiveRecords.map(d => d.directiveIndex); } instantiate(dispatcher: any): ChangeDetector { - return new DynamicChangeDetector(this.definition.id, this.definition.strategy, dispatcher, - this._propertyBindingRecords, this._eventBindingRecords, - this.definition.directiveRecords); + return new DynamicChangeDetector( + this.definition.id, dispatcher, this._propertyBindingRecords.length, + this._propertyBindingTargets, this._directiveIndices, + ChangeDetectionUtil.changeDetectionMode(this.definition.strategy), + this._propertyBindingRecords, this._eventBindingRecords, this.definition.directiveRecords); } } export function createPropertyRecords(definition: ChangeDetectorDefinition): ProtoRecord[] { var recordBuilder = new ProtoRecordBuilder(); - ListWrapper.forEach(definition.bindingRecords, - (b) => { recordBuilder.add(b, definition.variableNames); }); + ListWrapper.forEachWithIndex(definition.bindingRecords, + (b, index) => recordBuilder.add(b, definition.variableNames, index)); return coalesce(recordBuilder.records); } @@ -65,7 +71,7 @@ export function createEventRecords(definition: ChangeDetectorDefinition): EventB return definition.eventRecords.map(er => { var records = _ConvertAstIntoProtoRecords.create(er, varNames); var dirIndex = er.implicitReceiver instanceof DirectiveIndex ? er.implicitReceiver : null; - return new EventBinding(er.eventName, er.elementIndex, dirIndex, records); + return new EventBinding(er.target.name, er.target.elementIndex, dirIndex, records); }); } @@ -74,13 +80,13 @@ export class ProtoRecordBuilder { constructor() { this.records = []; } - add(b: BindingRecord, variableNames: List = null) { + add(b: BindingRecord, variableNames: string[], bindingIndex: number) { var oldLast = ListWrapper.last(this.records); if (isPresent(oldLast) && oldLast.bindingRecord.directiveRecord == b.directiveRecord) { oldLast.lastInDirective = false; } var numberOfRecordsBefore = this.records.length; - this._appendRecords(b, variableNames); + this._appendRecords(b, variableNames, bindingIndex); var newLast = ListWrapper.last(this.records); if (isPresent(newLast) && newLast !== oldLast) { newLast.lastInBinding = true; @@ -99,29 +105,30 @@ export class ProtoRecordBuilder { } } - _appendRecords(b: BindingRecord, variableNames: List) { + _appendRecords(b: BindingRecord, variableNames: string[], bindingIndex: number) { if (b.isDirectiveLifecycle()) { this.records.push(new ProtoRecord(RecordType.DIRECTIVE_LIFECYCLE, b.lifecycleEvent, null, [], - [], -1, null, this.records.length + 1, b, null, false, - false, false, false)); + [], -1, null, this.records.length + 1, b, false, false, + false, false, null)); } else { - _ConvertAstIntoProtoRecords.append(this.records, b, variableNames); + _ConvertAstIntoProtoRecords.append(this.records, b, variableNames, bindingIndex); } } } class _ConvertAstIntoProtoRecords implements AstVisitor { constructor(private _records: List, private _bindingRecord: BindingRecord, - private _expressionAsString: string, private _variableNames: List) {} + private _variableNames: string[], private _bindingIndex: number) {} - static append(records: List, b: BindingRecord, variableNames: List) { - var c = new _ConvertAstIntoProtoRecords(records, b, b.ast.toString(), variableNames); + static append(records: List, b: BindingRecord, variableNames: string[], + bindingIndex: number) { + var c = new _ConvertAstIntoProtoRecords(records, b, variableNames, bindingIndex); b.ast.visit(c); } static create(b: BindingRecord, variableNames: List): ProtoRecord[] { var rec = []; - _ConvertAstIntoProtoRecords.append(rec, b, variableNames); + _ConvertAstIntoProtoRecords.append(rec, b, variableNames, null); rec[rec.length - 1].lastInBinding = true; return rec; } @@ -261,12 +268,12 @@ class _ConvertAstIntoProtoRecords implements AstVisitor { var selfIndex = this._records.length + 1; if (context instanceof DirectiveIndex) { this._records.push(new ProtoRecord(type, name, funcOrValue, args, fixedArgs, -1, context, - selfIndex, this._bindingRecord, this._expressionAsString, - false, false, false, false)); + selfIndex, this._bindingRecord, false, false, false, false, + this._bindingIndex)); } else { this._records.push(new ProtoRecord(type, name, funcOrValue, args, fixedArgs, context, null, - selfIndex, this._bindingRecord, this._expressionAsString, - false, false, false, false)); + selfIndex, this._bindingRecord, false, false, false, false, + this._bindingIndex)); } return selfIndex; } diff --git a/modules/angular2/src/change_detection/proto_record.ts b/modules/angular2/src/change_detection/proto_record.ts index 4f53cb385227..701d709fdd37 100644 --- a/modules/angular2/src/change_detection/proto_record.ts +++ b/modules/angular2/src/change_detection/proto_record.ts @@ -26,9 +26,9 @@ export class ProtoRecord { constructor(public mode: RecordType, public name: string, public funcOrValue, public args: List, public fixedArgs: List, public contextIndex: number, public directiveIndex: DirectiveIndex, public selfIndex: number, - public bindingRecord: BindingRecord, public expressionAsString: string, - public lastInBinding: boolean, public lastInDirective: boolean, - public argumentToPureFunction: boolean, public referencedBySelf: boolean) {} + public bindingRecord: BindingRecord, public lastInBinding: boolean, + public lastInDirective: boolean, public argumentToPureFunction: boolean, + public referencedBySelf: boolean, public propertyBindingIndex: number) {} isPureFunction(): boolean { return this.mode === RecordType.INTERPOLATE || this.mode === RecordType.COLLECTION_LITERAL; diff --git a/modules/angular2/src/core/compiler/proto_view_factory.ts b/modules/angular2/src/core/compiler/proto_view_factory.ts index fef99ed9eed5..716e79e20d59 100644 --- a/modules/angular2/src/core/compiler/proto_view_factory.ts +++ b/modules/angular2/src/core/compiler/proto_view_factory.ts @@ -211,12 +211,10 @@ export class ProtoViewFactory { var nestedPvVariableBindings = _collectNestedProtoViewsVariableBindings(nestedPvsWithIndex); var nestedPvVariableNames = _collectNestedProtoViewsVariableNames(nestedPvsWithIndex); - var changeDetectorDefs = - _getChangeDetectorDefinitions(hostComponentBinding.metadata, nestedPvsWithIndex, + var protoChangeDetectors = + this._getProtoChangeDetectors(hostComponentBinding, nestedPvsWithIndex, nestedPvVariableNames, allRenderDirectiveMetadata); - var protoChangeDetectors = ListWrapper.map( - changeDetectorDefs, - changeDetectorDef => this._changeDetection.createProtoChangeDetector(changeDetectorDef)); + var appProtoViews = ListWrapper.createFixedSize(nestedPvsWithIndex.length); ListWrapper.forEach(nestedPvsWithIndex, (pvWithIndex: RenderProtoViewWithIndex) => { var appProtoView = @@ -230,6 +228,24 @@ export class ProtoViewFactory { }); return appProtoViews; } + + private _getProtoChangeDetectors(hostComponentBinding: DirectiveBinding, + nestedPvsWithIndex: RenderProtoViewWithIndex[], + nestedPvVariableNames: string[][], + allRenderDirectiveMetadata: any[]): ProtoChangeDetector[] { + if (this._changeDetection.generateDetectors) { + var changeDetectorDefs = + _getChangeDetectorDefinitions(hostComponentBinding.metadata, nestedPvsWithIndex, + nestedPvVariableNames, allRenderDirectiveMetadata); + return changeDetectorDefs.map(changeDetectorDef => + this._changeDetection.getProtoChangeDetector( + changeDetectorDef.id, changeDetectorDef)); + } else { + var changeDetectorIds = + _getChangeDetectorDefinitionIds(hostComponentBinding.metadata, nestedPvsWithIndex); + return changeDetectorIds.map(id => this._changeDetection.getProtoChangeDetector(id, null)); + } + } } /** @@ -280,22 +296,35 @@ function _getChangeDetectorDefinitions( var directiveRecords = bindingRecordsCreator.getDirectiveRecords(elementBinders, allRenderDirectiveMetadata); var strategyName = DEFAULT; - var typeString; if (pvWithIndex.renderProtoView.type === ViewType.COMPONENT) { strategyName = hostComponentMetadata.changeDetection; - typeString = 'comp'; - } else if (pvWithIndex.renderProtoView.type === ViewType.HOST) { - typeString = 'host'; - } else { - typeString = 'embedded'; } - var id = `${hostComponentMetadata.id}_${typeString}_${pvWithIndex.index}`; + var id = _protoViewId(hostComponentMetadata, pvWithIndex); var variableNames = nestedPvVariableNames[pvWithIndex.index]; return new ChangeDetectorDefinition(id, strategyName, variableNames, propBindingRecords, eventBindingRecords, directiveRecords, assertionsEnabled()); }); } +function _getChangeDetectorDefinitionIds(hostComponentMetadata: RenderDirectiveMetadata, + nestedPvsWithIndex: List): + string[] { + return nestedPvsWithIndex.map(pvWithIndex => _protoViewId(hostComponentMetadata, pvWithIndex)); +} + +function _protoViewId(hostComponentMetadata: RenderDirectiveMetadata, + pvWithIndex: RenderProtoViewWithIndex): string { + var typeString; + if (pvWithIndex.renderProtoView.type === ViewType.COMPONENT) { + typeString = 'comp'; + } else if (pvWithIndex.renderProtoView.type === ViewType.HOST) { + typeString = 'host'; + } else { + typeString = 'embedded'; + } + return `${hostComponentMetadata.id}_${typeString}_${pvWithIndex.index}`; +} + function _createAppProtoView( renderProtoView: ProtoViewDto, protoChangeDetector: ProtoChangeDetector, variableBindings: Map, allDirectives: List, diff --git a/modules/angular2/src/core/compiler/view.ts b/modules/angular2/src/core/compiler/view.ts index 5c2a7c04cff1..1efcb35a2ac7 100644 --- a/modules/angular2/src/core/compiler/view.ts +++ b/modules/angular2/src/core/compiler/view.ts @@ -8,12 +8,12 @@ import { } from 'angular2/src/facade/collection'; import { AST, - BindingRecord, ChangeDetector, ChangeDetectorRef, ChangeDispatcher, DirectiveIndex, DirectiveRecord, + BindingTarget, Locals, ProtoChangeDetector } from 'angular2/src/change_detection/change_detection'; @@ -171,7 +171,7 @@ export class AppView implements ChangeDispatcher, RenderEventDispatcher { } // dispatch to element injector or text nodes based on context - notifyOnBinding(b: BindingRecord, currentValue: any): void { + notifyOnBinding(b: BindingTarget, currentValue: any): void { if (b.isTextNode()) { this.renderer.setText( this.render, this.mainMergeMapping.renderTextIndices[b.elementIndex + this.textOffset], @@ -179,14 +179,14 @@ export class AppView implements ChangeDispatcher, RenderEventDispatcher { } else { var elementRef = this.elementRefs[this.elementOffset + b.elementIndex]; if (b.isElementProperty()) { - this.renderer.setElementProperty(elementRef, b.propertyName, currentValue); + this.renderer.setElementProperty(elementRef, b.name, currentValue); } else if (b.isElementAttribute()) { - this.renderer.setElementAttribute(elementRef, b.propertyName, currentValue); + this.renderer.setElementAttribute(elementRef, b.name, currentValue); } else if (b.isElementClass()) { - this.renderer.setElementClass(elementRef, b.propertyName, currentValue); + this.renderer.setElementClass(elementRef, b.name, currentValue); } else if (b.isElementStyle()) { - var unit = isPresent(b.propertyUnit) ? b.propertyUnit : ''; - this.renderer.setElementStyle(elementRef, b.propertyName, `${currentValue}${unit}`); + var unit = isPresent(b.unit) ? b.unit : ''; + this.renderer.setElementStyle(elementRef, b.name, `${currentValue}${unit}`); } else { throw new BaseException('Unsupported directive record'); } diff --git a/modules/angular2/src/facade/collection.dart b/modules/angular2/src/facade/collection.dart index c2766d509b3a..7c4e7d8e8745 100644 --- a/modules/angular2/src/facade/collection.dart +++ b/modules/angular2/src/facade/collection.dart @@ -134,6 +134,12 @@ class ListWrapper { list.forEach(fn); } + static void forEachWithIndex(List list, fn(item, index)) { + for (var i = 0; i < list.length; ++i) { + fn(list[i], i); + } + } + static reduce(List list, fn(a, b), init) { return list.fold(init, fn); } diff --git a/modules/angular2/src/facade/collection.ts b/modules/angular2/src/facade/collection.ts index b09e4f9ecd2a..6602a8cc8731 100644 --- a/modules/angular2/src/facade/collection.ts +++ b/modules/angular2/src/facade/collection.ts @@ -182,6 +182,11 @@ export class ListWrapper { fn(array[i]); } } + static forEachWithIndex(array: List, fn: (T, number) => void) { + for (var i = 0; i < array.length; i++) { + fn(array[i], i); + } + } static first(array: List): T { if (!array) return null; return array[0]; diff --git a/modules/angular2/src/transform/template_compiler/change_detector_codegen.dart b/modules/angular2/src/transform/template_compiler/change_detector_codegen.dart index 81a6e19e1bf9..a7f20a90804c 100644 --- a/modules/angular2/src/transform/template_compiler/change_detector_codegen.dart +++ b/modules/angular2/src/transform/template_compiler/change_detector_codegen.dart @@ -9,6 +9,7 @@ import 'package:angular2/src/change_detection/interfaces.dart'; import 'package:angular2/src/change_detection/proto_change_detector.dart'; import 'package:angular2/src/change_detection/proto_record.dart'; import 'package:angular2/src/change_detection/event_binding.dart'; +import 'package:angular2/src/change_detection/binding_record.dart'; import 'package:angular2/src/facade/lang.dart' show BaseException; /// Responsible for generating change detector classes for Angular 2. @@ -79,7 +80,8 @@ class _CodegenState { final List _eventBindings; final CodegenLogicUtil _logic; final CodegenNameUtil _names; - final bool _generateCheckNoChanges; + final bool _devMode; + final List _propertyBindingTargets; _CodegenState._( this._changeDetectorDefId, @@ -87,11 +89,12 @@ class _CodegenState { this._changeDetectorTypeName, String changeDetectionStrategy, this._records, + this._propertyBindingTargets, this._eventBindings, this._directiveRecords, this._logic, this._names, - this._generateCheckNoChanges) + this._devMode) : _changeDetectionMode = ChangeDetectionUtil.changeDetectionMode(changeDetectionStrategy); @@ -99,6 +102,8 @@ class _CodegenState { ChangeDetectorDefinition def) { var protoRecords = createPropertyRecords(def); var eventBindings = createEventRecords(def); + var propertyBindingTargets = def.bindingRecords.map((b) => b.target).toList(); + var names = new CodegenNameUtil(protoRecords, eventBindings, def.directiveRecords, _UTIL); var logic = new CodegenLogicUtil(names, _UTIL, def.strategy); return new _CodegenState._( @@ -107,11 +112,12 @@ class _CodegenState { changeDetectorTypeName, def.strategy, protoRecords, + propertyBindingTargets, eventBindings, def.directiveRecords, logic, names, - def.generateCheckNoChanges); + def.devMode); } void _writeToBuf(StringBuffer buf) { @@ -119,9 +125,12 @@ class _CodegenState { class $_changeDetectorTypeName extends $_BASE_CLASS<$_contextTypeName> { ${_genDeclareFields()} - $_changeDetectorTypeName(dispatcher, protos, directiveRecords) + $_changeDetectorTypeName(dispatcher) : super(${codify(_changeDetectorDefId)}, - dispatcher, protos, directiveRecords, '$_changeDetectionMode') { + dispatcher, ${_records.length}, + ${_changeDetectorTypeName}.gen_propertyBindingTargets, + ${_changeDetectorTypeName}.gen_directiveIndices, + '$_changeDetectionMode') { dehydrateDirectives(false); } @@ -145,17 +154,31 @@ class _CodegenState { ${_maybeGenDehydrateDirectives()} + ${_genPropertyBindingTargets()}; + + ${_genDirectiveIndices()}; + static $_GEN_PREFIX.ProtoChangeDetector $PROTO_CHANGE_DETECTOR_FACTORY_METHOD( $_GEN_PREFIX.ChangeDetectorDefinition def) { return new $_GEN_PREFIX.PregenProtoChangeDetector( - (a, b, c) => new $_changeDetectorTypeName(a, b, c), + (a) => new $_changeDetectorTypeName(a), def); } } '''); } + String _genPropertyBindingTargets() { + var targets = _logic.genPropertyBindingTargets(_propertyBindingTargets, this._devMode); + return "static var gen_propertyBindingTargets = ${targets}"; + } + + String _genDirectiveIndices() { + var indices = _logic.genDirectiveIndices(_directiveRecords); + return "static var gen_directiveIndices = ${indices}"; + } + String _maybeGenHandleEventInternal() { if (_eventBindings.length > 0) { var handlers = _eventBindings.map((eb) => _genEventBinding(eb)).join("\n"); @@ -239,7 +262,7 @@ class _CodegenState { var directiveFieldNames = _names.getAllDirectiveNames(); for (var i = 0; i < directiveFieldNames.length; ++i) { buf.writeln('${directiveFieldNames[i]} = directives.getDirectiveFor(' - '${_names.getDirectivesAccessorName()}[$i].directiveIndex);'); + '${_names.getDirectivesAccessorName()}[$i]);'); } return '$buf'; } @@ -378,9 +401,9 @@ class _CodegenState { var oldValue = _names.getFieldName(r.selfIndex); var br = r.bindingRecord; - if (br.isDirective()) { + if (br.target.isDirective()) { var directiveProperty = - '${_names.getDirectiveName(br.directiveRecord.directiveIndex)}.${br.propertyName}'; + '${_names.getDirectiveName(br.directiveRecord.directiveIndex)}.${br.target.name}'; return ''' ${_genThrowOnChangeCheck(oldValue, newValue)} $directiveProperty = $newValue; @@ -395,7 +418,7 @@ class _CodegenState { } String _genThrowOnChangeCheck(String oldValue, String newValue) { - if (this._generateCheckNoChanges) { + if (this._devMode) { return ''' if(throwOnChange) { this.throwOnChangeError(${oldValue}, ${newValue}); @@ -407,7 +430,7 @@ class _CodegenState { } String _genCheckNoChanges() { - if (this._generateCheckNoChanges) { + if (this._devMode) { return 'void checkNoChanges() { runDetectChanges(true); }'; } else { return ''; @@ -417,8 +440,8 @@ class _CodegenState { String _maybeFirstInBinding(ProtoRecord r) { var prev = ChangeDetectionUtil.protoByIndex(_records, r.selfIndex - 1); var firstInBindng = prev == null || prev.bindingRecord != r.bindingRecord; - return firstInBindng - ? "${_names.getFirstProtoInCurrentBinding()} = ${r.selfIndex};" + return firstInBindng && !r.bindingRecord.isDirectiveLifecycle() + ? "${_names.getPropertyBindingIndex()} = ${r.propertyBindingIndex};" : ''; } diff --git a/modules/angular2/src/transform/template_compiler/reflection/reflection_capabilities.dart b/modules/angular2/src/transform/template_compiler/reflection/reflection_capabilities.dart index 65734853caf9..50fcc118ba56 100644 --- a/modules/angular2/src/transform/template_compiler/reflection/reflection_capabilities.dart +++ b/modules/angular2/src/transform/template_compiler/reflection/reflection_capabilities.dart @@ -14,7 +14,7 @@ class NullReflectionCapabilities implements ReflectionCapabilities { return false; } - Function factory(Type type) => _notImplemented('factory'); + Function factory(Type type) => _notImplemented("factory"); List parameters(typeOrFunc) => _notImplemented('parameters'); diff --git a/modules/angular2/test/change_detection/change_detection_spec.ts b/modules/angular2/test/change_detection/change_detection_spec.ts index d08803aae63a..24eb8dfdbd71 100644 --- a/modules/angular2/test/change_detection/change_detection_spec.ts +++ b/modules/angular2/test/change_detection/change_detection_spec.ts @@ -30,12 +30,12 @@ export function main() { var map = {'id': (def) => proto}; var cd = new PreGeneratedChangeDetection(map); - expect(cd.createProtoChangeDetector(def)).toBe(proto) + expect(cd.getProtoChangeDetector('id', def)).toBe(proto) }); it("should delegate to dynamic change detection otherwise", () => { var cd = new PreGeneratedChangeDetection({}); - expect(cd.createProtoChangeDetector(def)).toBeAnInstanceOf(DynamicProtoChangeDetector); + expect(cd.getProtoChangeDetector('id', def)).toBeAnInstanceOf(DynamicProtoChangeDetector); }); }); } diff --git a/modules/angular2/test/change_detection/change_detector_spec.ts b/modules/angular2/test/change_detection/change_detector_spec.ts index e814617cd928..1c8893f89ecc 100644 --- a/modules/angular2/test/change_detection/change_detector_spec.ts +++ b/modules/angular2/test/change_detection/change_detector_spec.ts @@ -1135,8 +1135,8 @@ class TestDispatcher implements ChangeDispatcher { this.onAllChangesDoneCalled = true; } - notifyOnBinding(binding, value) { - this.log.push(`${binding.propertyName}=${this._asString(value)}`); + notifyOnBinding(target, value) { + this.log.push(`${target.name}=${this._asString(value)}`); this.loggedValues.push(value); } diff --git a/modules/angular2/test/change_detection/coalesce_spec.ts b/modules/angular2/test/change_detection/coalesce_spec.ts index e147a66f677c..ccb8159083bf 100644 --- a/modules/angular2/test/change_detection/coalesce_spec.ts +++ b/modules/angular2/test/change_detection/coalesce_spec.ts @@ -21,8 +21,7 @@ export function main() { if (isBlank(argumentToPureFunction)) argumentToPureFunction = false; return new ProtoRecord(mode, name, funcOrValue, args, null, contextIndex, directiveIndex, - selfIndex, null, null, lastInBinding, false, argumentToPureFunction, - false); + selfIndex, null, lastInBinding, false, argumentToPureFunction, false, 0); } describe("change detection - coalesce", () => { @@ -64,7 +63,7 @@ export function main() { [r("user", [], 0, 1, {lastInBinding: true}), r("user", [], 0, 2, {lastInBinding: true})]); expect(rs[1]).toEqual(new ProtoRecord(RecordType.SELF, "self", null, [], null, 1, null, 2, - null, null, true, false, false, false)); + null, true, false, false, false, 0)); }); it("should set referencedBySelf", () => { diff --git a/modules/angular2/test/change_detection/proto_record_builder_spec.ts b/modules/angular2/test/change_detection/proto_record_builder_spec.ts index 06c27eadea7e..a6c53fe93ee5 100644 --- a/modules/angular2/test/change_detection/proto_record_builder_spec.ts +++ b/modules/angular2/test/change_detection/proto_record_builder_spec.ts @@ -19,7 +19,7 @@ export function main() { it('should set argumentToPureFunction flag', inject([Parser], (p: Parser) => { var builder = new ProtoRecordBuilder(); var ast = p.parseBinding("[1,2]", "location"); // collection literal is a pure function - builder.add(BindingRecord.createForElementProperty(ast, 0, "property"), []); + builder.add(BindingRecord.createForElementProperty(ast, 0, "property"), [], 0); var isPureFunc = builder.records.map(r => r.argumentToPureFunction); expect(isPureFunc).toEqual([true, true, false]); @@ -29,7 +29,7 @@ export function main() { inject([Parser], (p: Parser) => { var builder = new ProtoRecordBuilder(); var ast = p.parseBinding("f(1,2)", "location"); - builder.add(BindingRecord.createForElementProperty(ast, 0, "property"), []); + builder.add(BindingRecord.createForElementProperty(ast, 0, "property"), [], 0); var isPureFunc = builder.records.map(r => r.argumentToPureFunction); expect(isPureFunc).toEqual([false, false, false]); diff --git a/modules/angular2/test/change_detection/proto_record_spec.ts b/modules/angular2/test/change_detection/proto_record_spec.ts index c7071c09fcee..fb62af7ff71b 100644 --- a/modules/angular2/test/change_detection/proto_record_spec.ts +++ b/modules/angular2/test/change_detection/proto_record_spec.ts @@ -20,8 +20,8 @@ export function main() { if (isBlank(argumentToPureFunction)) argumentToPureFunction = false; if (isBlank(referencedBySelf)) referencedBySelf = false; - return new ProtoRecord(mode, name, null, [], null, 0, directiveIndex, 0, null, null, - lastInBinding, false, argumentToPureFunction, referencedBySelf); + return new ProtoRecord(mode, name, null, [], null, 0, directiveIndex, 0, null, lastInBinding, + false, argumentToPureFunction, referencedBySelf, 0); } describe("ProtoRecord", () => { diff --git a/modules/angular2/test/core/compiler/element_injector_spec.ts b/modules/angular2/test/core/compiler/element_injector_spec.ts index 558e65baeb72..09d051ec5bd5 100644 --- a/modules/angular2/test/core/compiler/element_injector_spec.ts +++ b/modules/angular2/test/core/compiler/element_injector_spec.ts @@ -980,7 +980,7 @@ export function main() { }); it("should inject ChangeDetectorRef of the component's view into the component", () => { - var cd = new DynamicChangeDetector(null, null, null, [], [], []); + var cd = new DynamicChangeDetector(null, null, 0, [], [], null, [], [], []); var view = new DummyView(); var childView = new DummyView(); childView.changeDetector = cd; @@ -993,7 +993,7 @@ export function main() { }); it("should inject ChangeDetectorRef of the containing component into directives", () => { - var cd = new DynamicChangeDetector(null, null, null, [], [], []); + var cd = new DynamicChangeDetector(null, null, 0, [], [], null, [], [], []); var view = new DummyView(); view.changeDetector =cd; var binding = DirectiveBinding.createFromType(DirectiveNeedsChangeDetectorRef, new DirectiveMetadata()); diff --git a/modules/angular2/test/core/compiler/proto_view_factory_spec.ts b/modules/angular2/test/core/compiler/proto_view_factory_spec.ts index b302dfb8b068..a69e57cd3330 100644 --- a/modules/angular2/test/core/compiler/proto_view_factory_spec.ts +++ b/modules/angular2/test/core/compiler/proto_view_factory_spec.ts @@ -20,7 +20,8 @@ import { ChangeDetection, ChangeDetectorDefinition, BindingRecord, - DirectiveIndex + DirectiveIndex, + Parser } from 'angular2/src/change_detection/change_detection'; import { BindingRecordsCreator, @@ -177,39 +178,44 @@ export function main() { beforeEach(() => { creator = new BindingRecordsCreator(); }); describe('getEventBindingRecords', () => { - it("should return template event records", () => { - var rec = creator.getEventBindingRecords( - [ - new RenderElementBinder( - {eventBindings: [new EventBinding("a", null)], directives: []}), - new RenderElementBinder( - {eventBindings: [new EventBinding("b", null)], directives: []}) - ], - []); - - expect(rec).toEqual([ - BindingRecord.createForEvent(null, "a", 0), - BindingRecord.createForEvent(null, "b", 1) - ]); - }); - - it('should return host event records', () => { - var rec = creator.getEventBindingRecords( - [ - new RenderElementBinder({ - eventBindings: [], - directives: [ - new DirectiveBinder( - {directiveIndex: 0, eventBindings: [new EventBinding("a", null)]}) - ] - }) - ], - [RenderDirectiveMetadata.create({id: 'some-id'})]); - - expect(rec.length).toEqual(1); - expect(rec[0].eventName).toEqual("a"); - expect(rec[0].implicitReceiver).toBeAnInstanceOf(DirectiveIndex); - }); + it("should return template event records", inject([Parser], (p: Parser) => { + var ast1 = p.parseAction("1", null); + var ast2 = p.parseAction("2", null); + + var rec = creator.getEventBindingRecords( + [ + new RenderElementBinder( + {eventBindings: [new EventBinding("a", ast1)], directives: []}), + new RenderElementBinder( + {eventBindings: [new EventBinding("b", ast2)], directives: []}) + ], + []); + + expect(rec).toEqual([ + BindingRecord.createForEvent(ast1, "a", 0), + BindingRecord.createForEvent(ast2, "b", 1) + ]); + })); + + it('should return host event records', inject([Parser], (p: Parser) => { + var ast1 = p.parseAction("1", null); + + var rec = creator.getEventBindingRecords( + [ + new RenderElementBinder({ + eventBindings: [], + directives: [ + new DirectiveBinder( + {directiveIndex: 0, eventBindings: [new EventBinding("a", ast1)]}) + ] + }) + ], + [RenderDirectiveMetadata.create({id: 'some-id'})]); + + expect(rec.length).toEqual(1); + expect(rec[0].target.name).toEqual("a"); + expect(rec[0].implicitReceiver).toBeAnInstanceOf(DirectiveIndex); + })); }); }); }); @@ -253,6 +259,7 @@ function createRenderViewportElementBinder(nestedProtoView) { class ChangeDetectionSpy extends SpyObject { constructor() { super(ChangeDetection); } noSuchMethod(m) { return super.noSuchMethod(m) } + get generateDetectors() { return true; } } @Component({selector: 'main-comp'}) diff --git a/modules/angular2/test/facade/collection_spec.ts b/modules/angular2/test/facade/collection_spec.ts index 161a1259cf6b..f8542b7a53e9 100644 --- a/modules/angular2/test/facade/collection_spec.ts +++ b/modules/angular2/test/facade/collection_spec.ts @@ -90,6 +90,18 @@ export function main() { it('should return null for an empty list', () => { expect(ListWrapper.maximum([], x => x)).toEqual(null); }); }); + + describe('forEachWithIndex', () => { + var l; + + beforeEach(() => { l = ["a", "b"]; }); + + it('should iterate over an array passing values and indices', () => { + var record = []; + ListWrapper.forEachWithIndex(l, (value, index) => record.push([value, index])); + expect(record).toEqual([["a", 0], ["b", 1]]); + }); + }); }); describe('StringMapWrapper', () => { diff --git a/modules/angular2/test/transform/integration/two_annotations_files/expected/bar.ng_deps.dart b/modules/angular2/test/transform/integration/two_annotations_files/expected/bar.ng_deps.dart index 09dff325f1c2..68a9b4121112 100644 --- a/modules/angular2/test/transform/integration/two_annotations_files/expected/bar.ng_deps.dart +++ b/modules/angular2/test/transform/integration/two_annotations_files/expected/bar.ng_deps.dart @@ -13,12 +13,10 @@ void initReflector() { if (_visited) return; _visited = true; _ngRef.reflector - ..registerType( - MyComponent, - new _ngRef.ReflectionInfo(const [ - const Component(selector: '[soup]'), - const View(template: 'Salad: {{myNum}} is awesome') - ], const [], () => new MyComponent())) + ..registerType(MyComponent, new _ngRef.ReflectionInfo(const [ + const Component(selector: '[soup]'), + const View(template: 'Salad: {{myNum}} is awesome') + ], const [], () => new MyComponent())) ..registerGetters({'myNum': (o) => o.myNum}); _gen.preGeneratedProtoDetectors['MyComponent_comp_0'] = _MyComponent_ChangeDetector0.newProtoChangeDetector; @@ -28,19 +26,23 @@ class _MyComponent_ChangeDetector0 extends _gen.AbstractChangeDetector { var myNum0, interpolate1; - _MyComponent_ChangeDetector0(dispatcher, protos, directiveRecords) - : super("MyComponent_comp_0", dispatcher, protos, directiveRecords, - 'ALWAYS_CHECK') { + _MyComponent_ChangeDetector0(dispatcher) : super( + "MyComponent_comp_0", dispatcher, 2, + _MyComponent_ChangeDetector0.gen_propertyBindingTargets, + _MyComponent_ChangeDetector0.gen_directiveIndices, 'ALWAYS_CHECK') { dehydrateDirectives(false); } void detectChangesInRecordsInternal(throwOnChange) { - var l_context = this.context, l_myNum0, c_myNum0, l_interpolate1; + var l_context = this.context, + l_myNum0, + c_myNum0, + l_interpolate1; c_myNum0 = false; var isChanged = false; var changes = null; - this.firstProtoInCurrentBinding = 1; + this.propertyBindingIndex = 0; l_myNum0 = l_context.myNum; if (_gen.looseNotIdentical(l_myNum0, this.myNum0)) { c_myNum0 = true; @@ -75,9 +77,16 @@ class _MyComponent_ChangeDetector0 this.myNum0 = this.interpolate1 = _gen.ChangeDetectionUtil.uninitialized; } + static var gen_propertyBindingTargets = [ + _gen.ChangeDetectionUtil.bindingTarget("textNode", 0, null, null, + "Salad: {{myNum}} is awesome in MyComponent: