AngularJS Custom Directive Two Way Binding

Если у меня есть директива AngularJS без шаблона, и я хочу, чтобы она установила свойство в текущей области, что это лучший способ сделать это?

Например, директива, которая подсчитывает нажатия кнопок:

<button twoway="counter">Click Me</button>
<p>Click Count: {{ counter }}</p>

С директивой, которая назначает счетчик кликов выражению в двухстороннем атрибуте:

.directive('twoway', [
'$parse',
  function($parse) {
    return {
      scope: false,
      link: function(scope, elem, attrs) {
        elem.on('click', function() {
          var current = scope.$eval(attrs.twoway) || 0;
          $parse(attrs.twoway).assign(scope, ++current);
          scope.$apply();
        });
      }
    };
  }
])

Есть ли лучший способ сделать это? Из того, что я прочитал, изолированный охват будет излишним, но мне нужен охват ребенка? И есть ли более чистый способ написать обратно переменную области видимости, определенную в атрибуте директивы, отличную от использования $parse. Я просто чувствую, что делаю это слишком сложно.

Полный Plunker здесь.

Ответ 1

Почему избыточный избыток изоляции? его довольно полезно для такого рода вещей:

  scope: {
     "twoway": "=" // two way binding
  },

Это довольно идиоматическое решение angular для этой проблемы, поэтому я придерживаюсь этого.

Ответ 2

Я удивлен, что никто не упомянул ng-model, директиву по умолчанию для привязки двух данных. Возможно, это не так хорошо известно, но функция связывания имеет четвертый параметр:

angular.module('directive-binding', [])
  .directive('twoway', 
      function() {
        return {
          require: '?ngModel',
          link: function(scope, elem, attrs, ngModel) {
            elem.on('click', function() {
              var counter = ngModel.$viewValue ? ngModel.$viewValue : 0
              ngModel.$setViewValue(++counter);
              scope.$apply();
            });
          }
        };
      }
    );

В вашем представлении

<button twoway ng-model="counter">Click Me</button>
<p>Click Count: {{ counter }}</p>

Четвертый параметр - это API для ngModelController, который имеет много применений для обработки (например, разбора и форматирования) и обмена данными между директивой и область действия.

Здесь обновлен Plunker.

Ответ 3

Вы можете немного упростить его, не используя $parse

angular.module('directive-binding', []).directive('twoway', [function () {
    return {
        scope: false,
        link: function (scope, elem, attrs) {
            elem.on('click', function () {
                scope[attrs.twoway] = scope[attrs.twoway] == null ? 1 : scope[attrs.twoway] + 1;
                scope.$apply();
            });
        }
    };
}]);

Ответ 4

Отличным способом применения двусторонней привязки является использование директивных компонентов. Вот мое решение. Это позволяет использовать привязку ng-repeat и расширяемые данные.

Просмотр плунжера

HTML

<body ng-controller='MainCtrl'>  
    Data: {{data}}
    <hr>
    <mydirective name='data[0]'></mydirective>
    <hr>
    <mydirective name='data[1]'></mydirective>
</body>

контроллер

app.controller('MainCtrl', function($scope) {
  $scope.data = [];
  $scope.data[0] = 'Marco';
  $scope.data[1] = 'Billy';
});

Директива

app.directive("mydirective", function(){
    return {
        restrict: "EA",
        scope: {name: '='},
        template: "<div>Your name is : {{name}}</div>"+
        "Change your name : <input type='text' ng-model='name' />"
    };
});

В случае счетчика это можно сделать с использованием того же метода.

Ответ 6

Измените шаблон на:

<button twoway bind="counter">Click Me</button>
<p>Click Count: {{ counter.val }}</p>

и директива:

.directive('twoway',
    function() {
        return {
            scope: {
                localValue: '=?bind'
            },
            link: function(scope, elem, attrs) {
                scope.localValue = {
                    val: 0
                };
                elem.on('click', function() {
                    scope.localValue.val = scope.localValue.val + 1;
                    scope.$apply();
                });
            }
        };
    }
);