Angular -bootstrap (вкладки): привязка данных работает только в одну сторону

Я приготовил маленькую скрипку и сварил ее до минимума:

http://jsfiddle.net/lpeterse/NdhjD/4/

<script type="text/javascript">
    angular.module('app', ['ui.bootstrap']);

    function Ctrl($scope) {
      $scope.foo = "42";
}
</script>


<div ng-app="app" ng-controller="Ctrl">
    1: {{foo}}<br />
    2: <input ng-model="foo" />
    <tabs>
        <pane heading="tab">
            3: {{foo}}<br />
            4: <input ng-model="foo" />
        </pane>
    </tabs>    
</div>

В начале все представления ссылаются на модель Ctrl.foo.

Если вы меняете что-то на входе 2:, он правильно обновляет модель, и это изменение распространяется на все виды.

Изменение чего-либо на входе 4: влияет только на представления, включенные в ту же область. Он ведет себя как область, как-то раздвоенная. Впоследствии изменения от 2: больше не отражаются на вкладке.

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

Буду благодарен за любые намеки: -)

Ответ 1

Проблема такая же, как и в ng-repeat при редактировании примитива. Директива <pane> создает новую область, которая наследуется от родителя.

Теперь, учитывая, как работает наследование Javascript директива <pane> имеет свою собственную копию примитива строки foo, а когда вы ее редактируете, вы редактируете ее только в области содержимого панели.

Простым решением было бы поставить foo в объект вашего родителя Ctrl:

function Ctrl($scope) {
  $scope.data = { foo: 42 };
}

Затем вы можете сделать это в своем HTML:

<tabs><pane><input ng-model="data.foo"></pane></tabs>

Почему он работает с объектом? Поскольку, когда <pane> наследует родительскую область, ее ссылка на data будет ссылаться на тот же объект в памяти, что и на родительский Ctrl. Примитивы, такие как строки и числа, копируются в наследование, а объекты просто создают новый указатель на один и тот же объект.

TL; DR: <pane> новая область наследует примитив строки foo как новую копию foo, которая при редактировании не изменится на родительском Ctrl. <pane> новая область могла бы наследовать объект типа data как ссылку на тот же объект, а при редактировании в области <pane> тот же объект будет ссылаться на родительскую область.

Полезная статья: https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance

Ответ 2

В директивах <tabs> и <pane> каждый создает новую переданную дочернюю область (поскольку оба они имеют transclude: true,), которые прототипически наследуются от родительской области и изолированная область дочернего объекта, которая не прототипически наследуется от родительской области, В <input...> внутри <pane> используется область с закрытыми дочерними элементами.

Когда сначала отображается input внутри <pane>, он заполняется значением $scope.foo. Начинается обычное прототипное наследование JavaScript... первоначально foo не, определенном в области трансключенного ребенка (прототипное наследование не копирует примитивы), поэтому JavaScript следует цепочке прототипов и смотрит на родительскую object/$scope и находит его там. 42 помещается в текстовое поле. Трансграничная область ребенка не изменяется/изменена (пока).

Если вы редактируете первое текстовое поле, второе текстовое поле обновляется, потому что JavaScript все еще использует прототипное наследование, чтобы найти значение $scope.foo.

Если вы редактируете второе текстовое поле, чтобы сказать 429, Angular записывает значение в $scope.foo, но обратите внимание, что $scope - это область закрытого дочернего объекта. Поскольку foo является примитивным, он создает новое свойство в этой дочерней области - как работает JavaScript, к лучшему или к худшему. Это новое свойство будет теневое/скрытое свойство родительской области с тем же именем. Прототипное наследование здесь не играет. (Статья Энди упоминает в своем посте (который также на SO) также объясняет это подробно, с картинками.) Поскольку у трансключенного дочернего объекта теперь есть foo свойство, теперь оно будет использовать это локальное свойство для чтения и записи, поэтому оно появляется "отключено" из родительской области.

Использование объекта (а не примитива) решает проблему, потому что прототипальное наследование тогда всегда находится в игре. Объект transcluded child получает ссылку на объект в родительской области. Запись в data.foo записывается в объект data в родительском объекте, а не в область выделенного дочернего объекта.

Ответ 3

Проблема заключается в директиве tabs. Я думаю на строке 1044 из ui-bootstrap-tpls-0.1.0.js.

Если вы меняете scope: {} на scope: { foo: '='}, это должно дать вам двустороннюю привязку данных.

От Angular Документы:

= or = attr - установить двунаправленную привязку между локальным свойством scope и свойством родительской области имени, определяемым значением атрибута attr. Если имя attr не указано, предполагается, что имя атрибута совпадает с локальным именем. Определенное и видимое определение области: {localModel: '= myAttr'}, то свойство scope widget localModel будет отображать значение parentModel в родительской области. Любые изменения в parentModel будут отображаться в localModel, и любые изменения в localModel будут отображаться в parentModel.