I've been struggling with Angular's isolate scope for over 24hrs now. Here's my scenario: I have an ng-repeat
iterating over an array of objects from which I want to use a custom directive to either generate a <select>
or <input>
based on the field_type
property of the current object being iterated. This means I'll have to generate the template and $compile
in the post-link function of the directive since I have no access to the iterated object in the template function.
Everything works as expected, apart from the actual binding of the generated template to the controller (vm) in my outer scope. I think my approach (adding this in the template string: ng-model="vm.prodAttribs.' + attr.attribute_code +'"
) may be wrong, and would appreciate pointers in the right direction. Thanks!
See sample code below:
directives:
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>
controller:
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": {}
}];
})
I would suggest to use templates instead of html compilation manually. The solution is much simpler:
Controller would contain data declaration:
Your directive would be as simple as that:
template.html
would be:And your html:
issue is probably here :
element.html() returns a html as a string, not the ACTUAL dom content, so what you inserted into your directive's element is never actually compiled by angular, explaining your (absence of) behaviour.
should work way better.
For directive requiring parent controller, I would also change your ctrl.compile method (renamed to insertAndCompile here)
You would just have to call it this way :
instead of the 2 lines I gave as first answer.