将transcluded指令插入控制器会中断模型绑定

时间:2015-05-28 08:31:05

标签: angularjs angularjs-directive

我正在研究我的第一个'正确的' AngularJS项目在我的控制器中使用transcluded指令时遇到了问题。总的来说,我希望我的transcluded指令能够包装'我看来有些表单元素。这是简化的代码......

(function () {
   angular.module('testApp', [])
   .directive('xyzFieldSet', function () {
       return {
         template: '<fieldset ng-transclude></fieldset>',
         restrict: 'E',
         transclude: true
       };
   })
   .controller('testCtrl', ['$scope', function($scope) {
       $scope.name = 'Fred';
       $scope.changedName = '';

       $scope.nameChanged = function() {
         $scope.changedName = $scope.name;  
       };
   }]);
}());

和相应的HTML ...

<div ng-app="testApp">
    <div ng-controller="testCtrl">
        <h2>Without 'fieldset' directive</h2>
        <p>The 'Changed Name' field changes as the 'Name' is changed.</p>
        <p>Name: <input ng-model="name" ng-change="nameChanged()" /></p>
        <p>Changed Name: {{ changedName }}</p>
    </div>    
    <hr />            
    <div ng-controller="testCtrl">
        <h2>With 'fieldset' directive</h2>
        <p>
            With the transcluded directive 'wrapping' the content,
            the 'Changed Name' field <em>does not</em> change as 
            the 'Name' is changed.
        </p>
        <xyz-field-set>
            <p>Name: <input ng-model="name" ng-change="nameChanged()" /></p>
            <p>Changed Name: {{ changedName }}</p>
         </xyz-field-set>
    </div>    
</div>

如果没有transcluded指令,对输入字段的任何更改都会正确绑定到作用域,但是,当我添加transcluded指令时,数据绑定不起作用。

可以在https://jsfiddle.net/tgspwo73/1/

找到证明问题的小提琴

根据我的阅读,我猜测该指令正在改变其子元素的范围。如果是这种情况,有没有办法绕过这种行为?

1 个答案:

答案 0 :(得分:1)

这与范围原型继承以及当您不使用带点(.)的模型时产生的相当不直观的行为有关。

这里有一个很好的详尽解释:

What are the nuances of scope prototypal / prototypical inheritance in AngularJS?

和我的次要贡献here

上述链接中的问题/答案讨论了最常出现行为的子范围。例如,ng-if会创建一个子范围,因此如果您这样做,您当前正在处理的方法会中断:

<p>Name: <input ng-if="true"
                ng-model="name" 
                ng-change="nameChanged()" placeholder="Type name here" />
</p>

但类似的事情发生在一个被转换的范围(使用ng-transclude),因为这个范围原型继承自父级(尽管它是指令的子范围,但这不是重点)。

解决此问题的方法是遵循始终绑定到对象属性的最佳做法(即使用.中的ng-model):

<xyz-field-set>
  <p>Name: <input ng-model="form.name" 
                  ng-change="nameChanged()" placeholder="Type name here" />
  </p>
  <p>Changed Name: {{ changedName }}</p>
</xyz-field-set>

这需要在控制器中进行以下更改:

.controller('testCtrl', ['$scope', function($scope) {
   $scope.form = {
       name: 'Fred' // optionally, set the property
   };

   $scope.changedName = '';

   $scope.nameChanged = function() {
     // this, btw, is unnecessary since you already have $scope.form.name
     // and you can bind to it with {{form.name}}
     // (unless you need to add more logic)
     $scope.changedName = $scope.form.name;  
   };
}]);