amcdnl
amcdnl

Reputation: 8638

Angular Expressive Directive Design

I'm working on a open-source project for a AngularJS Data Table directive ( still WIP ). When you look at components like Angular Grid or UI Grid they all describe their columns and attributes in a object in the parent controller like:

$scope.gridOptions = {
  enableSorting: true,
  enableCellEditOnFocus: true,
  columnDefs: [ 
    { name: 'field1', enableSorting: false, enableCellEdit: false },
    { name: 'field2' },
    { name: 'field3', visible: false }
  ]
};

which works fine, however, I don't think this is really the 'angular way'. It feels more like a jQuery widget. If you look at projects like Angular Material, they are much more expressive in the HTML template vs object driven.

For my implementation, I originally wanted to make it very expressive and expose each one of the inner directives I use, however, that ended up a mess to just create a basic table. So I did some research on other frameworks and found that react had a nice architecture where you just define the columns like:

React.render(
  <Table
    rowHeight={50}
    rowGetter={rowGetter}
    rowsCount={rows.length}
    width={5000}
    height={5000}
    headerHeight={50}>
    <Column
      label="Col 1"
      width={3000}
      dataKey={0}
    />
    <Column
      label="Col 2"
      width={2000}
      dataKey={1}
    />
  </Table>,
  document.getElementById('example')
);

I feel in love with this approach, its simple and is expressive all at the same time. 90% of the time you only want to customize the column template anyways. So instead of this:

    $scope.gridOptions = {
    enableFiltering: true,
    rowTemplate: rowTemplate(),
    data: 'data',
    columnDefs: [
      { name: 'name' },
      { name: 'gender' },
      { name: 'company' },
      { name: 'widgets' },
      { 
        name: 'cumulativeWidgets', 
        field: 'widgets', 
        cellTemplate: '<div class="ui-grid-cell-contents" title="TOOLTIP">{{grid.appScope.cumulative(grid, row)}}</div>' 
          }
    ]
};

with the cell template, you could do something like this:

<dt options="options" rows="data" class="material">
  <Column name="name" width="300"></Column>
  <Column name="Gender">
    <strong>{{value}}</strong>
  </Column>
  <Column name="Company"></Column>
</dt>

notice how I took the React concept and paired it with a more Angular concept where I would include the ability to have a template inside the column.

Ok now for the problem. I want the columns on init, but not after that. I want to replace it with the actual table. Problem is I can never get that HTML when I need it.

So on this line I tried to do something like:

compile: function(tElem, tAttrs){
    var tags = z.getElementsByTagName('column');
    console.log(tags) // equals = []
    return {
        pre: function($scope, $elm, $attrs, ctrl){
        }
    };
}

but the columns are never there til later when I try to transclude them ( not really what I wanna do ). I need a way to get ahold of them before the controller is initialized and the template replaces the inner contents. Here is a plunkr!

Additionally, since my directive is scoped ( which I def wanna do for perf reasons ) I have no way to get access to the parent scope to compile the inner template with the outer contents.

Also, any suggestions / thoughts on this design paradigm? Thanks!

Upvotes: 3

Views: 331

Answers (2)

eladcon
eladcon

Reputation: 5825

Try to use the template as a function, which returns the actual template string. The first parameter is the original html:

var tags;
return {
    template: function(element) { 
        tags = element[0].getElementsByTagName('column');
        return "<div>table</div>"     
    },
    compile: ...
}

Upvotes: 3

Okazari
Okazari

Reputation: 4597

I only found a weird way to achieve what you want.

Before someone give a better solution, here is mine working in this plunker.

Adding the $transclude service to the controller and transclude:'element' to the directive

 app.directive("simple", function(){
    return {
        restrict: "EA",
        replace:true,
        transclude:'element',
        template:"<div>table</div>",
        compile: function(element, attributes, transclude){
            var tags = element[0].getElementsByTagName('column');
            console.log("compile", tags);
            console.log("transclude", transclude);
            return {
                pre: function(scope, element, attributes, controller, transcludeFn){
                    var tags = element[0].getElementsByTagName('column');
                    console.log("pre", tags);
                },
                post: function(scope, element, attributes, controller, transcludeFn){
                    var tags = element[0].getElementsByTagName('column');
                    console.log("post", tags);
                }
            }
        },
        controller: function($scope, $element, $transclude){
              $transclude(function(clone,scope){
                /* for demo am converting to html string*/
                console.log("From controller",angular.element(clone).find('column'));
              });
        }
    };
}); 

I tested some other things. But i'm only able to get the column with this and only into the controller.

Upvotes: 1

Related Questions