Reputation: 749
I have a directive whose data is being received via an api call. The directive itself works fine, the problem arises (I believe) because the directive is loaded before the api call finishes. This results in the whole shebang just not working. Instead of my expected output, I just get {{user}}
.
My directive looks like this:
app.directive('myDirective', function() {
return {
restrict: 'A',
require: '^ngModel',
scope: {
ngModel: '=',
},
template: '<tbody style="background-color: red;" ng-bind-html="renderHtml(listing_html)"></tbody>',
controller: ['$scope', '$http', '$sce',
function($scope, $http, $sce) {
$scope.listing_html += "<td>{{user.name}}</td>"
$scope.renderHtml = function(html_code) {
return $sce.trustAsHtml(html_code);
};
}
],
link: function(scope, iElement, iAttrs, ctrl) {
scope.$watch('ngModel', function(newVal) {
// This *is* firing after the data arrives, but even then the
// {{user}} object is populated. And the `user in ngModel` doesn't
// run correctly either.
console.log(scope.ngModel);
scope.listing_html = "<tr ng-repeat='user in ngModel'><td>{{user}}</td></tr>"
})
}
};
});
And my html is simply
<table my-directive my-options='{"Name": "name", "Email": "email"}' ng-model='userData'></table>
I've created a plunker with a ton of comments to hopefully help explain the issue.
This question is very similar to this one, with the key distinction of that solution not working. Adding ng-cloak
to mine just makes it not display.
It may also be worth noting that I've been using this as reference on the way to construct a directive.
Upvotes: 0
Views: 1116
Reputation: 48212
I am not 100% sure, but I believe that ngBindHtml
will not help you in this case.
ngBindHtml
is for displaying some "normal" HTML, but you want to display some Angular, magic HTML.
For that you need to $compile
the HTML to something that is Angular-aware and link
the compiled HTML to a scope.
I used the following approach (with apparently good results):
controller: function ($scope, $element, $compile) {
var html = createTmpl(angular.fromJson($scope.myOptions));
$scope.$watch('ngModel', function (newVal) {
var elem = angular.element(html); // Creating element
var linkingFn = $compile(elem); // Compiling element
linkingFn($scope); // Linking element
$element.html(''); // Removing previous content
$element.append(elem); // Inserting new content
// The above is purposedly explicit to highlight what is
// going on. It's moe concise equivalent would be:
//$element.html('').append($compile(html)($scope));
});
where createTmpl()
is defined to take into account myOptions
and return the appropriate template for creating a table with a header-row (based on the keys of myOptions
) and data-rows with the properties defined as myOptions
's values:
function createTmpl(options) {
// Construct the header-row
var html = '<tr>';
angular.forEach(options, function (value, key) {
html += '<th>' + key + '</th>';
});
html += '</tr>\n';
// Construct the data-rows
html += '<tr ng-repeat="user in ngModel">';
angular.forEach(options, function (value, key) {
html += '<td>{{user' + value + '}}</td>';
});
html += '</tr>\n';
// Return the template
return html;
}
See, also, this short demo.
Of course, this is for demonstration purposes only and does not handle everything a production-ready app should (e.g. accounting for errors, missing properties, changes in myOptions
and whatnot).
UPDATE:
I had very strong competion, so I did a slight modification of the code above in order to support nested properties. E.g. given an object with the following structure:
user = {
name: 'ExpertSystem',
company: {
name: 'ExpertSystem S.A.',
ranking: 100
}
};
we can have the company name displayed in a column of our table, just by defining myOptions
like this:
myOptions='{"Company name": "company.name"}
Upvotes: 1
Reputation: 533
I think you're making this a bit more complicated that it needs to be. If you're going to try to insert dynamic HTML with Angular expressions in them, you need to use the $compile service to compile them first (this hooks up the directives, etc, in that dynamic HTML to Angular). With that said, I don't think you need to do that for what you're trying to accomplish.
Take a look at this updated plunk: http://plnkr.co/edit/RWcwIhlv3dMbjln4dOyb?p=preview
You can use the template in the directive to produce the dynamic changes you need. In my example, I've used ng-repeat to repeat over the users provided to the directive, and also to the options provided to the directive. ng-repeat does the watching, so as soon as the data provided to the directive via ng-model is updated, the ng-repeats reflect those changes.
<tbody style="background-color: red;">
<tr><th ng-repeat="option in myOptions">{{option.name}}</th></tr>
<tr ng-repeat="user in ngModel">
<td ng-repeat="option in myOptions">{{user[option.value]}}</td>
</tr>
</tbody>
The options I defined in the main controller like this.
$scope.tableOptions = [
{"name": "Name", "value": "name"},
{"name": "Email", "value": "email"}
];
You could add other properties to this that are used by the directive, such as display order, etc. You could even remove an item from the options dynamically and that data would then be removed from the output table.
Let me know if this helps, or if I've misunderstood what you were trying to accomplish.
Upvotes: 2