AngularJS: сделать привязку шаблона директивы области видимости к родительской области видимости
Я боролся с изолятором Angular уже более 24 часов. Вот мой сценарий: у меня есть ng-repeat
перебирая массив объектов, из которых я хочу использовать пользовательскую директиву для генерации <select>
или же <input>
на основе field_type
свойство текущего объекта, который повторяется. Это означает, что мне придется сгенерировать шаблон и $compile
в функции post-link директивы, так как у меня нет доступа к повторному объекту в функции шаблона.
Все работает как положено, кроме фактической привязки сгенерированного шаблона к контроллеру (vm) в моей внешней области видимости. Я думаю, что мой подход (добавив это в строке шаблона: ng-model="vm.prodAttribs.' + attr.attribute_code +'"
) может быть не так, и был бы признателен за указатели в правильном направлении. Спасибо!
Смотрите пример кода ниже:
директивы:
directives.directive('productAttributeWrapper', ['$compile', function($compile){
//this directive exists solely to provide 'productAttribute' directive access to the parent scope
return {
restrict: 'A',
scope: false,
controller: function($scope, $element, $attrs){
this.compile = function (element) {
$compile(element)($scope);
console.log('$scope.prodAttribs in directive: ', $scope.prodAttribs);
};
}
}
}]);
directives.directive('productAttribute', ['$compile', function($compile){
return {
restrict: 'A',
require: '^productAttributeWrapper', //use the wrapper's controller
scope: {
attribModel: '=',
prodAttribute: '=productAttribute', //binding to the model being iterated by ng-repeat
},
link: function(scope, element, attrs, ctrl){
var template = '';
var attr = scope.prodAttribute;
if(!attr) return;
switch(attr.attribute_field_type.toLowerCase()){
case 'textfield':
template =
'<input type="text" id="'+attr.attribute_code+'" ng-model="vm.prodAttribs.' + attr.attribute_code +'">';
break;
case 'dropdown':
template = [
'<select class="cvl" id="'+attr.attribute_code+'" ng-model="vm.prodAttribs.' + attr.attribute_code +'">',
'#cvl_option_values',
'\n</select>'
].join('');
var options = '\n<option value="">Select One</option>';
for(var i=0; i<attr.cvl_option_values.length; i++) {
var optionVal = attr.cvl_option_values[i].value;
options += '\n<option value="'+optionVal+'">' + attr.cvl_option_values[i].value + '</option>';
}
template = template.replace('#cvl_option_values', options);
break;
}
element.html(template);
ctrl.compile(element.html()); //try to bind template to outer scope
}
}
}]);
HTML:
<div ng-controller="ProductController as vm">
<div product-attribute="attrib" ng-repeat="attrib in vm.all_attribs"></div>
</div>
контроллер:
app.controller('ProductDetailsController', function(){
var vm = this;
//also added the property to $scope to see if i could access it there
$scope.prodAttribs = vm.prodAttribs = {
name: '',
description: '',
price: [0.0],
condition: null
}
vm.all_attributes = [
{
"attribute_id": 1210,
"attribute_display_name": "Product Type",
"attribute_code": "product_type",
"attribute_field_type": "Textfield",
"cvl_option_values": [],
"validation_rules": {}
},
{
"attribute_id": 902,
"attribute_display_name": "VAT",
"attribute_code": "vat",
"attribute_field_type": "dropdown",
"cvl_option_values": [
{
"option_id": "5",
"value": "5%"
},
{
"option_id": "6",
"value": "Exempt"
}
],
"validation_rules": {}
}];
})
2 ответа
Проблема, вероятно, здесь:
element.html(template);
ctrl.compile(element.html()); //try to bind template to outer scope
element.html () возвращает html как строку, а не содержимое ACTUAL dom, поэтому то, что вы вставили в элемент вашей директивы, никогда не компилируется с помощью angular, что объясняет ваше (отсутствие) поведение.
element.append(ctrl.compile(template));
должен работать лучше.
Для директивы, требующей родительского контроллера, я бы также изменил ваш метод ctrl.compile (переименован в insertAndCompile здесь)
ctrl.insertAndCompile = function(content) {
$compile(content)($scope, function(clone) {
$element.append(clone);
}
}
Вы бы просто назвали это так:
ctrl.insertAndCompile(template);
вместо 2 строк я дал первый ответ.
Я бы предложил использовать шаблоны вместо компиляции HTML вручную. Решение намного проще:
Контроллер будет содержать объявление данных:
app.controller('ProductDetailsController', function($scope) {
$scope.prodAttribs = {
name: '',
description: '',
price: [0.0],
condition: null
}
$scope.all_attribs = [{
"attribute_id": 1210,
"attribute_display_name": "Product Type",
"attribute_code": "product_type",
"attribute_field_type": "Textfield",
"cvl_option_values": [],
"validation_rules": {}
}, {
"attribute_id": 902,
"attribute_display_name": "VAT",
"attribute_code": "vat",
"attribute_field_type": "dropdown",
"cvl_option_values": [{
"option_id": "5",
"value": "5%"
}, {
"option_id": "6",
"value": "Exempt"
}],
"validation_rules": {}
}];
});
Ваша директива будет такой простой:
app.directive('productAttribute', function() {
return {
restrict: 'A',
scope: {
attribModel: '=',
prodAttribute: '=productAttribute'
},
templateUrl: 'template.html',
controller: function($scope) {}
}
});
template.html
было бы:
<div>
<select ng-show="prodAttribute.attribute_field_type.toLowerCase() == 'dropdown'" class="cvl" id="" ng-model="prodAttribs.attribute_code">
<option value="">Select One</option>
<option ng-repeat="item in prodAttribute.cvl_option_values track by $index" value="{{item.value}}">{{item.value}}</option>
</select>
<input ng-show="prodAttribute.attribute_field_type.toLowerCase() == 'textfield'" type="text" id="{{prodAttribute.attribute_code}}" ng-model="prodAttribute.attribute_code">
</div>
И ваш HTML:
<div ng-controller="ProductController">
<div ng-repeat="attrib in all_attribs" product-attribute="attrib">{{attrib}}</div>
</div>