AngularJS 1.4 директивы: область действия, двусторонняя привязка и bindToController

Обновление. Должно быть, это было что-то глупое в другой части кода. Теперь он работает, поэтому синтаксис bindToController прекрасен.

Мы используем AngularJS 1.4, который ввел директиву новый способ использования bindToController.

После небольшого количества чтения (и, возможно, не понимая всего) мы определили нашу директиву следующим образом:

  .directive('mdAddress', function mdAddress() {
    var directive = {
      restrict: 'EA',
      scope: {},
      bindToController: {
        address: '='
      },
      templateUrl: 'modules/address/address.html',
      controller: AddressController,
      controllerAs: 'dir'
    };

Вызов из другого вида:

  <md-address address="vm.address"></md-address>

Ранее определенный в контроллере представления:

  vm.address = {
    street: null,
    countryCode: null,
    cityCode: null,
    postalCode: null
  };

Ссылка на переменные в шаблоне директивы следующим образом:

  <md-input-container>
    <label>{{'ADDRESSNUMBER' | translate}}</label>
    <input type="number" ng-model="dir.address.streetNumber">
  </md-input-container>

Мы потратили 4 часа, пытаясь понять, почему наша директива не работает. Ну, он работал, но двухсторонняя привязка между контроллером и директивой не была, vm.address.street был безнадежно установлен на нуль.

Через некоторое время мы просто попробовали старый способ:

  .directive('mdAddress', function mdAddress() {
    var directive = {
      restrict: 'EA',
      scope: {
        address: '='
      },
      bindToController: true,
      templateUrl: 'modules/address/address.html',
      controller: AddressController,
      controllerAs: 'dir'
    };

И это волшебно сработало. Любая идея ПОЧЕМУ?

Ответ 1

Update:

Благодаря ссылке на это сообщение в блоге мне нужно обновить свой ответ. Поскольку AngularJS 1.4 действительно кажется, что вы можете использовать

scope: {},
bindToController: {
  variable: '='
}

который будет делать (точную) ту же самую вещь, что и старый синтаксис:

scope: {
  variable: '='
},
bindToController: true

Полезные строки исходного кода AngularJS для объяснения этого поведения:

if (isObject(directive.scope)) {
  if (directive.bindToController === true) {
    bindings.bindToController = parseIsolateBindings(directive.scope,
                                                     directiveName, true);
    bindings.isolateScope = {};
  } else {
    bindings.isolateScope = parseIsolateBindings(directive.scope,
                                                 directiveName, false);
  }
}
if (isObject(directive.bindToController)) {
  bindings.bindToController =
      parseIsolateBindings(directive.bindToController, directiveName, true);
}

Источник: AngularJS 1.4.0

Оригинальный ответ:

Надеюсь, я могу объяснить вам, почему это поведение вы испытали правильно и где вы пропустили понимание концепции привязки сферы.

Позвольте мне объяснить, что вы сделали в своем первом фрагменте кода:

.directive('mdAddress', function mdAddress() {
    var directive = {
      restrict: 'EA',
      scope: {},
      bindToController: {
        address: '='
      },
      templateUrl: 'modules/address/address.html',
      controller: AddressController,
      controllerAs: 'dir'
    };

С помощью scope: {} вы создали изолированную область (без какого-либо наследования) для вашей директивы mdAddress. Это означает: данные не передаются между родительским контроллером и вашей директивой.

Имея это в виду, относительно второго фрагмента кода:

<md-address address="vm.address"></md-address>

vm.address из вашего родительского контроллера/представления будет назначаться как выражение для атрибута адреса директивы, но поскольку вы ранее определяли изолированную область, данные не передаются в AddressController и, следовательно, недоступны в bindToController.

Давайте рассмотрим определение объекта scope как "какие данные будут переданы" и bindToController как "какие данные будут доступны в моем объекте controllerAs как объект".

Итак, теперь давайте посмотрим на последний (и фрагмент рабочего кода):

.directive('mdAddress', function mdAddress() {
    var directive = {
      restrict: 'EA',
      scope: {
        address: '='
      },
      bindToController: true,
      templateUrl: 'modules/address/address.html',
      controller: AddressController,
      controllerAs: 'dir'
    };

Здесь вы создали изолированную область, но на этот раз вы добавили атрибут address, который будет передан как выражение. Итак, теперь address, который вы передали из представления во втором фрагменте, будет доступен в области контроллера. Теперь установка bindToController: true свяжет все текущие свойства области с контроллером (или, скорее, объект controllerAs). И теперь это работает так, как вы ожидаете, потому что данные будут переданы в область и данные будут переданы в область шаблонов контроллера.

Этот краткий обзор поможет вам лучше понять концепцию объектов определения scope и bindToController?