From c3a41ff9fefe894663c4d4f40a83794521deb14f Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Wed, 6 Jun 2012 13:58:10 -0700 Subject: feat($compile): simplify isolate scope bindings Changed the isolate scope binding options to: - @attr - attribute binding (including interpolation) - =model - by-directional model binding - &expr - expression execution binding This change simplifies the terminology as well as number of choices available to the developer. It also supports local name aliasing from the parent. BREAKING CHANGE: isolate scope bindings definition has changed and the inject option for the directive controller injection was removed. To migrate the code follow the example below: Before: scope: { myAttr: 'attribute', myBind: 'bind', myExpression: 'expression', myEval: 'evaluate', myAccessor: 'accessor' } After: scope: { myAttr: '@', myBind: '@', myExpression: '&', // myEval - usually not useful, but in cases where the expression is assignable, you can use '=' myAccessor: '=' // in directive's template change myAccessor() to myAccessor } The removed `inject` wasn't generaly useful for directives so there should be no code using it. --- src/ng/compile.js | 121 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 68 insertions(+), 53 deletions(-) (limited to 'src/ng/compile.js') diff --git a/src/ng/compile.js b/src/ng/compile.js index ee120263..e1aba35b 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -18,6 +18,9 @@ */ +var NON_ASSIGNABLE_MODEL_EXPRESSION = 'Non-assignable model expression: '; + + /** * @ngdoc function * @name angular.module.ng.$compile @@ -225,47 +228,6 @@ function $CompileProvider($provide) { function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse, $controller, $rootScope) { - var LOCAL_MODE = { - attribute: function(localName, mode, parentScope, scope, attr) { - scope[localName] = attr[localName]; - }, - - evaluate: function(localName, mode, parentScope, scope, attr) { - scope[localName] = parentScope.$eval(attr[localName]); - }, - - bind: function(localName, mode, parentScope, scope, attr) { - var getter = $interpolate(attr[localName]); - scope.$watch( - function() { return getter(parentScope); }, - function(v) { scope[localName] = v; } - ); - }, - - accessor: function(localName, mode, parentScope, scope, attr) { - var getter = noop, - setter = noop, - exp = attr[localName]; - - if (exp) { - getter = $parse(exp); - setter = getter.assign || function() { - throw Error("Expression '" + exp + "' not assignable."); - }; - } - - scope[localName] = function(value) { - return arguments.length ? setter(parentScope, value) : getter(parentScope); - }; - }, - - expression: function(localName, mode, parentScope, scope, attr) { - scope[localName] = function(locals) { - $parse(attr[localName])(parentScope, locals); - }; - } - }; - var Attributes = function(element, attr) { this.$$element = element; this.$attr = attr || {}; @@ -746,9 +708,67 @@ function $CompileProvider($provide) { $element = attrs.$$element; if (newScopeDirective && isObject(newScopeDirective.scope)) { - forEach(newScopeDirective.scope, function(mode, name) { - (LOCAL_MODE[mode] || wrongMode)(name, mode, - scope.$parent || scope, scope, attrs); + var LOCAL_REGEXP = /^\s*([@=&])\s*(\w*)\s*$/; + + var parentScope = scope.$parent || scope; + + forEach(newScopeDirective.scope, function(definiton, scopeName) { + var match = definiton.match(LOCAL_REGEXP) || [], + attrName = match[2]|| scopeName, + mode = match[1], // @, =, or & + lastValue, + parentGet, parentSet; + + switch (mode) { + + case '@': { + attrs.$observe(attrName, function(value) { + scope[scopeName] = value; + }); + attrs.$$observers[attrName].$$scope = parentScope; + break; + } + + case '=': { + parentGet = $parse(attrs[attrName]); + parentSet = parentGet.assign || function() { + // reset the change, or we will throw this exception on every $digest + lastValue = scope[scopeName] = parentGet(parentScope); + throw Error(NON_ASSIGNABLE_MODEL_EXPRESSION + attrs[attrName] + + ' (directive: ' + newScopeDirective.name + ')'); + }; + lastValue = scope[scopeName] = parentGet(parentScope); + scope.$watch(function() { + var parentValue = parentGet(parentScope); + + if (parentValue !== scope[scopeName]) { + // we are out of sync and need to copy + if (parentValue !== lastValue) { + // parent changed and it has precedence + lastValue = scope[scopeName] = parentValue; + } else { + // if the parent can be assigned then do so + parentSet(parentScope, lastValue = scope[scopeName]); + } + } + return parentValue; + }); + break; + } + + case '&': { + parentGet = $parse(attrs[attrName]); + scope[scopeName] = function(locals) { + return parentGet(parentScope, locals); + } + break; + } + + default: { + throw Error('Invalid isolate scope definition for directive ' + + newScopeDirective.name + ': ' + definiton); + } + } }); } @@ -761,12 +781,6 @@ function $CompileProvider($provide) { $transclude: boundTranscludeFn }; - - forEach(directive.inject || {}, function(mode, name) { - (LOCAL_MODE[mode] || wrongMode)(name, mode, - newScopeDirective ? scope.$parent || scope : scope, locals, attrs); - }); - controller = directive.controller; if (controller == '@') { controller = attrs[directive.name]; @@ -1007,9 +1021,10 @@ function $CompileProvider($provide) { attr[name] = undefined; ($$observers[name] || ($$observers[name] = [])).$$inter = true; - scope.$watch(interpolateFn, function(value) { - attr.$set(name, value); - }); + (attr.$$observers && attr.$$observers[name].$$scope || scope). + $watch(interpolateFn, function(value) { + attr.$set(name, value); + }); }) }); } -- cgit v1.2.3