一种以角度方式绑定到对象的方法

时间:2015-05-09 18:01:38

标签: javascript angularjs

我希望在指令的属性之间进行单向(非一次)绑定,但我在如何在没有attrs.$observe的情况下表达这种情况的情况下挣扎。我现在能想到的最好的方法是通过&attr进行绑定,并在我的模板中调用我绑定的变量,例如{{attr()}}

app.controller('MainCtrl', function($scope) {
  $scope.names = ['Original'];
  setTimeout(function () {
    $scope.names.push('Asynchronously updated name');
    $scope.$apply();
  }, 1000);
});

app.directive('helloComponent', function () {
  return {
    scope: {
      'names': '&names'
    },
    template: '<li ng-repeat="name in names()">Hello {{name}}</li>'
  }
});

 <body ng-controller="MainCtrl">
    <ul>
      <hello-component names="names"/>
    </ul>
  </body>

Plunker

有没有更好的方法来保留单向绑定而不需要调用绑定属性?

修改

我已经更新了示例代码,以澄清我想绑定到一个对象,而不仅仅是一个字符串。所以@attr(使用字符串属性)是不是的解决方案。

4 个答案:

答案 0 :(得分:5)

"&"实际上是正确的做法。我反对这种方法(使用@JoeEnzminger,herehere),因为它在语义上是有问题的。但整体Joe是对的 - 这是创建与实际对象的单向绑定的方式,而"@"绑定到字符串。

如果您不喜欢隔离范围,那么使用$parse可以获得相同的效果:

var parsedName = $parse(attrs.name);
$scope.nameFn = function(){
  return parsedName($scope);
}

并在模板中使用它:

"<p>Hello {{nameFn()}}</p>"

答案 1 :(得分:3)

我在其他答案中没有看到任何提及它,但从Angular 1.5开始,支持对象的单向绑定(see scope section in $compile docs for Angular 1.5.9):

  

<<attr - 在本地范围属性和通过属性attr传递的表达式之间建立单向(单向)绑定。表达式在父作用域的上下文中计算。如果未指定attr名称,则假定属性名称与本地名称相同。您还可以通过添加?<?<?attr来使绑定成为可选项。

     

例如,给定<my-component my-attr="parentModel">scope: { localModel:'<myAttr' }的指令定义,则隔离范围属性localModel将反映父范围上parentModel的值。对parentModel的任何更改都会反映在localModel中,但localModel中的更改不会反映在parentModel中。然而,有两个警告:

     
      
  1. 单向绑定不会将值从父级复制到隔离范围,它只是设置相同的值。这意味着如果绑定值是对象,则隔离范围中对其属性的更改将反映在父范围中(因为它们都引用同一对象)。
  2.   
  3. 单向绑定监视更改为父值的标识。这意味着父值的$watch仅在对值的引用发生更改时才会触发。在大多数情况下,这不应该引起关注,但是知道您是否单向绑定到对象,然后在隔离范围中替换该对象可能很重要。如果现在更改父作用域中对象的属性,则更改将不会传播到隔离作用域,因为父作用域上对象的标识未更改。相反,您必须分配一个新对象。
  4.         

    如果您不打算将对隔离范围绑定的更改传播回父级,则单向绑定很有用。但是,它并不能完全不可能。

在下面的示例中,单向绑定用于将控制器范围内的对象中的更改传播到指令。

更新

正如@Suamere所指出的,你确实可以用单向绑定来改变绑定对象的属性;但是,如果整个对象从本地模型更改,则与父模型的绑定将中断,因为父和本地范围将引用不同的对象。双向绑定可以解决这个问题。更新了代码段以突出显示差异。

&#13;
&#13;
angular.module('App', [])

.directive('counter', function() {
  return {
    templateUrl: 'counter.html',
    restrict: 'E',
    scope: {
      obj1: '<objOneWayBinding',
      obj2: '=objTwoWayBinding'
    },
    link: function(scope) {
      scope.increment1 = function() {
        scope.obj1.counter++;
      };
      scope.increment2 = function() {
        scope.obj2.counter++;
      };

      scope.reset1 = function() {
        scope.obj1 = {
          counter: 0,
          id: Math.floor(Math.random()*10000),
          descr: "One-way binding",
          creator: "Directive"
        };
      };

      scope.reset2 = function() {
        scope.obj2 = {
          counter: 0,
          id: Math.floor(Math.random()*10000),
          descr: "Two-way binding",
          creator: "Directive"
        };
      };
    }
  };
})

.controller('MyCtrl', ['$scope', function($scope) {
  $scope.increment = function() {
    $scope.obj1FromController.counter++;
    $scope.obj2FromController.counter++;
  };

  $scope.reset = function() {
    $scope.obj1FromController = {
      counter: 0,
      id: Math.floor(Math.random()*10000),
      descr: "One-way binding",
      creator: "Parent"
    };

    $scope.obj2FromController = {
      counter: 0,
      id: Math.floor(Math.random()*10000),
      descr: "Two-way binding",
      creator: "Parent"
    };
  };

  $scope.reset();
}])

;
&#13;
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.js"></script>

<div ng-app="App">

  <script type="text/ng-template" id="counter.html">
    <h3>In Directive</h3>
    <pre>{{obj1 | json:0}}</pre>

    <button ng-click="increment1()">
      Increment obj1 from directive
    </button>

    <button ng-click="reset1()">
      Replace obj1 from directive (breaks binding)
    </button>

    <pre>{{obj2 | json:0}}</pre>

    <button ng-click="increment2()">
      Increment obj2 from directive
    </button>

    <button ng-click="reset2()">
      Replace obj2 from directive (maintains binding)
    </button>

  </script>

  <div ng-controller="MyCtrl">
    
    <counter obj-one-way-binding="obj1FromController"
             obj-two-way-binding="obj2FromController">
    </counter>
    
    <h3>In Parent</h3>

    <pre>{{obj1FromController | json:0}}</pre>
    <pre>{{obj2FromController | json:0}}</pre>

    <button ng-click="increment()">
      Increment from parent
    </button>
    
    <button ng-click="reset()">
      Replace from parent (maintains binding)
    </button>

  </div>
</div>
&#13;
&#13;
&#13;

答案 2 :(得分:0)

执行属性从字面上传递字符串。所以不要这样做:

<hello-component name="name"/>

你可以这样做:

<hello-component name="{{name}}"/>

答案 3 :(得分:-2)

这可能与New Dev提出的方法基本相同,但是我通过从我的隔离范围中取出一个对象并为其调用scope.$parent.$eval(attrs.myObj)来获取一个getter函数,为自己解决了类似的问题。

在一个看起来更像你的简化版本中我​​改变了:

app.directive('myDir', [function() {
    return {
        scope : {
            id : '@',
            otherScopeVar : '=',
            names : '='
        },
        template : '<li ng-repeat="name in names">{{name}}</li>'
    }
}]);

app.directive('myDir', [function() {
    return {
        scope : {
            id : '@',
            otherScopeVar : '='
        },
        template : '<li ng-repeat="name in getNames()">{{name}}</li>',
        link : function(scope, elem, attrs) {
            scope.getNames() {
                return scope.$parent.$eval(attrs.myList);
            };
        }
    }
}]);

这样,无论何时运行摘要,您的对象都会从父作用域中拉出。对我来说,这样做的好处是我能够将指令从双向绑定更改为单向绑定(这使我的性能从无法使用到工作正常)而不更改使用该指令的视图。

修改 第二个想法我不确定这是否只是单向绑定,因为更新变量并运行摘要时将始终使用更新的对象,在更改时没有固有的方法来运行其他逻辑,就像使用{ {1}}。