I am writing a directive to integrate SlickGrid with my angular app. I want to be able to configure SlickGrid columns with an angular template (instead of a formatter function). To achieve this, I need the directive to dynamically create formatter functions that return HTML as a string.
My approach has been to create a temporary scope, link the template against that, capture the html, and then destroy the scope. This works, but complains that $digest already in progress
. Is there a way I can render an angular template in this fashion, isolated from the global $digest cycle?
BTW: I tried using $interpolate, which works great, but doesn't support ng-repeat
or other directives.
var columnsConfig = [
{
id: "name",
name: "Name",
field: "name",
template: '<a href="{{context.url}}">{{value}}</a>'
},
{
id: "members",
name: "Members",
field: "members",
template: '<div ng-repeat="m in value">{{m}}</div>'
}
];
myModule.directive('SlickGrid', ['$compile', function($compile) {
return {
restrict: 'E',
scope: {
model: '='
},
link: function(scope, element, attrs) {
var columns = angular.copy(columnsConfig);
// Special Sauce: Allow columns to have an angular template
// in place of a regular slick grid formatter function
angular.forEach(columns, function(column){
var linker;
if (angular.isDefined(column.template)) {
linker = $compile(angular.element('<div>' + column.template + '</div>'));
delete column.template;
column.formatter = function(row, cell, value, columnDef, dataContext) {
var cellScope = scope.$new(true);
cellScope.value = value;
cellScope.context = dataContext;
var e = linker(cellScope);
cellScope.$apply();
cellScope.$destroy();
return e.html();
};
}
});
var options = {
enableColumnReorder: false,
enableTextSelectionOnCells: true,
autoHeight: true
};
var dataView = new Slick.Data.DataView();
var grid = new Slick.Grid(element, dataView, columns, options);
dataView.onRowCountChanged.subscribe(function (e, args) {
grid.updateRowCount();
grid.render();
});
dataView.onRowsChanged.subscribe(function (e, args) {
grid.invalidateRows(args.rows);
grid.render();
});
scope.$watch('model', function(data) {
if (angular.isArray(data)) {
dataView.setItems(data);
}
});
}
};
}]);
Ok so I needed to do pretty much the same thing, and came up with a solution that could be considered a bit of a hack (but there's no other way AFAIK, since SlickGrid only deals with html string, not html/jquery objects).
In a nutshell, it involves compiling the template in the formatter (as you did), but in addition to that, stores the generated object (not the HTML string) into a dictionnary, and use it to replace the cell content by using asyncPostRender (http://mleibman.github.io/SlickGrid/examples/example10-async-post-render.html).
Here is the part of the link function that is of interest here:
The column can be defined as such:
The context.user will be properly interpolated (thanks to $interpolate) and the ng-click will be working thanks to $compile and the fact that we use the real object and not the HTML on the asyncPostRender.
This is the full directive, followed by the HTML and the controller:
Directive:
The HTML:
The controller:
Important:
on the rebind() method, notice the
This is very important to have that, otherwise the asyncPostRender is never called.
Also, for the sake of completeness, here is the GuidGenerator service: