AngularJS: как связать постоянный объект с директивой
Я создал директиву со связыванием, используя "scope". В некоторых случаях я хочу привязать постоянный объект. Например, с HTML:
<div ng-controller="Ctrl">
<greeting person="{firstName: 'Bob', lastName: 'Jones'}"></greeting>
</div>
и JavaScript:
var app = angular.module('myApp', []);
app.controller("Ctrl", function($scope) {
});
app.directive("greeting", function () {
return {
restrict: "E",
replace: true,
scope: {
person: "="
},
template:
'<p>Hello {{person.firstName}} {{person.lastName}}</p>'
};
});
Хотя это и работает, это также вызывает ошибку JavaScript:
Error: 10 $digest() iterations reached. Aborting!
(Fiddle демонстрирует проблему)
Каков правильный способ привязки постоянного объекта без возникновения ошибки?
Ответы
Ответ 1
Здесь решение, с которым я столкнулся, основан на ответе @sh0ber:
Внедрить пользовательскую функцию link
. Если атрибут действителен JSON, то это постоянное значение, поэтому мы оцениваем его только один раз. В противном случае наблюдайте и обновляйте значение как обычно (другими словами, старайтесь вести себя как привязка =
). scope
должно быть установлено на true
, чтобы удостовериться, что назначенное значение влияет только на этот экземпляр директивы.
(Пример для jsFiddle)
HTML:
<div ng-controller="Ctrl">
<greeting person='{"firstName": "Bob", "lastName": "Jones"}'></greeting>
<greeting person="jim"></greeting>
</div>
JavaScript:
var app = angular.module('myApp', []);
app.controller("Ctrl", function($scope) {
$scope.jim = {firstName: 'Jim', lastName: "Bloggs"};
});
app.directive("greeting", function () {
return {
restrict: "E",
replace: true,
scope: true,
link: function(scope, elements, attrs) {
try {
scope.person = JSON.parse(attrs.person);
} catch (e) {
scope.$watch(function() {
return scope.$parent.$eval(attrs.person);
}, function(newValue, oldValue) {
scope.person = newValue;
});
}
},
template: '<p>Hello {{person.firstName}} {{person.lastName}}</p>'
};
});
Ответ 2
Вы получаете эту ошибку, потому что Angular оценивает выражение каждый раз. '=' для имен переменных.
Вот два альтернативных способа достижения такого же мнения без ошибки.
Первое решение:
app.controller("Ctrl", function($scope) {
$scope.person = {firstName: 'Bob', lastName: 'Jones'};
});
app.directive("greeting", function () {
return {
restrict: "E",
replace: true,
scope: {
person: "="
},
template:
'<p>Hello {{person.firstName}} {{person.lastName}}</p>'
};
});
<greeting person="person"></greeting>
Второе решение:
app.directive("greeting2", function () {
return {
restrict: "E",
replace: true,
scope: {
firstName: "@",
lastName: "@"
},
template:
'<p>Hello {{firstName}} {{lastName}}</p>'
};
});
<greeting2 first-name="Bob" last-Name="Jones"></greeting2>
http://jsfiddle.net/7bNAd/82/
Ответ 3
Другая опция:
app.directive("greeting", function () {
return {
restrict: "E",
link: function(scope,element,attrs){
scope.person = scope.$eval(attrs.person);
},
template: '<p>Hello {{person.firstName}} {{person.lastName}}</p>'
};
});
Ответ 4
Это связано с тем, что, если вы используете ссылку поля типа =
типа, значение атрибута наблюдается для изменений, но проверено для ссылочного равенства (с !==
), а не для глубокого анализа для равенства. Задание объектного литерала in-line приведет к тому, что angular создаст новый объект всякий раз, когда для доступа к атрибуту будет получен его атрибут - таким образом, когда angular выполняет грязную проверку, сравнение старого значения с текущим всегда сигнализирует об изменении.
Одним из способов преодоления этого является изменение источника angular, как описано здесь:
https://github.com/mgonto/angular.js/commit/09d19353a2ba0de8edcf625aa7a21464be830f02.
В противном случае вы можете создать свой объект в контроллере и указать его по имени в атрибуте элемента:
HTML
<div ng-controller="Ctrl">
<greeting person="personObj"></greeting>
</div>
JS
app.controller("Ctrl", function($scope)
{
$scope.personObj = { firstName : 'Bob', lastName : 'Jones' };
});
Еще один способ - создать объект в директиве родительского элемента ng-init
и позже ссылаться на него по имени (но это менее читаемо):
<div ng-controller="Ctrl" ng-init="personObj = { firstName : 'Bob', lastName : 'Jones' }">
<greeting person="personObj"></greeting>
</div>
Ответ 5
Мне не особенно нравится использовать eval()
, но если вы действительно хотите, чтобы это работало с предоставленным вами HTML-кодом:
app.directive("greeting", function() {
return {
restrict: "E",
compile: function(element, attrs) {
eval("var person = " + attrs.person);
var htmlText = '<p>Hello ' + person.firstName + ' ' + person.lastName + '</p>';
element.replaceWith(htmlText);
}
};
});
Ответ 6
У меня была та же проблема, я решил ее, разобрав json на этапе компиляции:
angular.module('foo', []).
directive('myDirective', function () {
return {
scope: {
myData: '@'
},
controller: function ($scope, $timeout) {
$timeout(function () {
console.log($scope.myData);
});
},
template: "{{myData | json}} a is {{myData.a}} b is {{myData.b}}",
compile: function (element, attrs) {
attrs['myData'] = angular.fromJson(attrs['myData']);
}
};
});
Единственным недостатком является то, что $scope
изначально не заполняется при первом запуске контроллера.
Здесь JSFiddle с этим кодом.