Sebastian
Sebastian

Reputation: 868

AngularJS: Compiling the output of a directive

i have some kind of legacy angularjs code which creates a dynamic table using a directive where the controller can overwrite the behavior of the table (on how to display the data)

It consists of the following setup (simplified):

Directive's controller

.directive('datatable', [function () {
    return {
        scope: {
            items: '=',
            tablemetadata: '=',
            processors: '=?'
        },
        controller: ...
        $scope.processField = function processField(item, data){
        if($scope.processors === undefined){return;}
            for(var i = 0; i < $scope.processors.length; i++){
                if($scope.processors[i].field===field){
                    var newData = $scope.processors[i].processor(item, data);
                    return $sce.trustAsHtml(newData);
                }
            }
            return data;
        };
    ...

Directive's Template

<tr ng-repeat="item in items">
    <td ng-repeat="column in tableMetadata.columns" ng-bind-html="processField(column.field, $eval('item.'+column.field))"></td>
</tr>

Controller

$scope.myItems = [{id: 2, otherProperty: "text"}];

$scope.tableMetadata = {
    columns: [
        {field: 'id', headerKey: 'object id'},
        {field: 'otherProperty', headerKey: 'some data'},
    ]
};

$scope.tableProcessors = [
    {field: 'id', processor: function(entry, data){ //data = content of object.id
        var retVal = "<a ng-click='alert(" + data + ");'>click me</a>";
        return retVal;
    }}
];

Controller's view

<datatable items="myItems" tablemetadata="tableMetadata" processors="tableProcessors"></datatable>

I need to generate buttons (or other html-elements) for some specific properties, like a link (like shown above).

The Button is displayed but the ng-click handler is not working. This makes sense since it wasn't compiled to the scope.

How do I correctly compile the new element and add it to the table?

Upvotes: 0

Views: 143

Answers (2)

Silvinus
Silvinus

Reputation: 1445

A simple solution can be to not use an isolated scope. Change your scope from scope: { ... } to scope: true and use $scope.$eval to evaluate your attributes.

Another solution (most elegant) can be to use angularjs transclusion (see here). But this solution ask to modify your dom representation of your directive.

Upvotes: 1

Daniel Lizik
Daniel Lizik

Reputation: 3144

In your link method in the directive you have to use

elem.append( $compile(html)(scope) );

As for separating the concerns cleanly, I would make each <td> its own directive that inherits what you are currently concatenating as a string in its isolated scope properties. Instead of

var retVal = "<a ng-click='alert(" + data + ");'>click me</a>";

<tr ng-repeat="item in items">
    <td ng-repeat="column in tableMetadata.columns" ng-bind-html="processField(column.field, $eval('item.'+column.field))"></td>
</tr>

use something like:

<tr ng-repeat="item in items">
  <table-item ng-repeat="..." process-field="item"></table-item>
</tr>

/** directive compiles dynamically */

scope: {
  processField: '='
},
link: function(scope, elem, attr, ctrl) {
  var template = `<a ng-click="${ctrl.processField}"></a>`;
  elem.append( $compile(template)(scope) );
}

Upvotes: 1

Related Questions