diff options
| author | Misko Hevery | 2012-06-06 13:58:10 -0700 | 
|---|---|---|
| committer | Igor Minar | 2012-06-08 15:50:13 -0700 | 
| commit | c3a41ff9fefe894663c4d4f40a83794521deb14f (patch) | |
| tree | b44037cfb0089cfea42f253b6ad1a09ccb7e2d86 /src | |
| parent | 5c95b8cccc0d72f7ca3afb1162b9528c1222eb3c (diff) | |
| download | angular.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.js | 121 | ||||
| -rw-r--r-- | src/ng/directive/input.js | 21 | 
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) { | 
