aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorMisko Hevery2012-06-06 13:58:10 -0700
committerIgor Minar2012-06-08 15:50:13 -0700
commitc3a41ff9fefe894663c4d4f40a83794521deb14f (patch)
treeb44037cfb0089cfea42f253b6ad1a09ccb7e2d86 /src
parent5c95b8cccc0d72f7ca3afb1162b9528c1222eb3c (diff)
downloadangular.js-c3a41ff9fefe894663c4d4f40a83794521deb14f.tar.bz2
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.
Diffstat (limited to 'src')
-rw-r--r--src/ng/compile.js121
-rw-r--r--src/ng/directive/input.js21
2 files changed, 80 insertions, 62 deletions
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);
+ });
})
});
}
diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js
index aa79082b..04af4c2a 100644
--- a/src/ng/directive/input.js
+++ b/src/ng/directive/input.js
@@ -857,8 +857,8 @@ var VALID_CLASS = 'ng-valid',
* </example>
*
*/
-var NgModelController = ['$scope', '$exceptionHandler', '$attrs', 'ngModel', '$element',
- function($scope, $exceptionHandler, $attr, ngModel, $element) {
+var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse',
+ function($scope, $exceptionHandler, $attr, $element, $parse) {
this.$viewValue = Number.NaN;
this.$modelValue = Number.NaN;
this.$parsers = [];
@@ -870,6 +870,14 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', 'ngModel', '$e
this.$invalid = false;
this.$name = $attr.name;
+ var ngModelGet = $parse($attr.ngModel),
+ ngModelSet = ngModelGet.assign;
+
+ if (!ngModelSet) {
+ throw Error(NON_ASSIGNABLE_MODEL_EXPRESSION + $attr.ngModel +
+ ' (' + startingTag($element) + ')');
+ }
+
/**
* @ngdoc function
* @name angular.module.ng.$compileProvider.directive.ngModel.NgModelController#$render
@@ -974,7 +982,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', 'ngModel', '$e
if (this.$modelValue !== value) {
this.$modelValue = value;
- ngModel(value);
+ ngModelSet($scope, value);
forEach(this.$viewChangeListeners, function(listener) {
try {
listener();
@@ -987,9 +995,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', 'ngModel', '$e
// model -> value
var ctrl = this;
- $scope.$watch(function() {
- return ngModel();
- }, function(value) {
+ $scope.$watch(ngModelGet, function(value) {
// ignore change from view
if (ctrl.$modelValue === value) return;
@@ -1044,9 +1050,6 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', 'ngModel', '$e
*/
var ngModelDirective = function() {
return {
- inject: {
- ngModel: 'accessor'
- },
require: ['ngModel', '^?form'],
controller: NgModelController,
link: function(scope, element, attr, ctrls) {