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.