Krzysztof Krzysztof - 6 months ago 21
AngularJS Question

Angular appends parent attribute value

I have hierarchical data like this:

[
{
"children":[
{
"children":[...],
[...]
},
{
"children":[...],
[...]
},
],
[...]
}
]


I want to build tree-like grid by flattening that data. I am using following directives:

app.directive('tree', function (hierarchyService, logger, $timeout) {
return {
scope: {
data: '='
},
restrict: 'E',
replace: true,
template: '<div>' +
'<table class="table table-striped table-hover">' +
' <thead>' +
' <tr>' +
' <th class="col-md-6">Account name</th>' +
' </tr>' +
' </thead>' +
' <tbody><tr collection data="data" /></tbody>' +
' </table>' +
'</div>'
};
});

app.directive('collection', function() {
return {
restrict: "A",
replace: true,
scope: {
data: '=',
depth: '@'
},
template: '<member ng-repeat="member in data" member="member" depth="{{depth}}" />',
link: function (scope, element, attrs) {
scope.depth = parseInt(scope.depth || 0);
}
}
});

app.directive('member', function($compile) {
return {
restrict: "E",
replace: true,
scope: {
depth: '@',
member: '='
},
template: '<tr ng-class="{selected: member.selected}">' +
'<td>' +
' <span ng-show="depth > 0" style="width: {{depth * 16}}px; display: inline-block;"></span> {{member.name}}' +
'</td>' +
'</tr>',
link: function (scope, element, attrs) {
scope.depth = parseInt(scope.depth || 0);

if (angular.isArray(scope.member.children) && scope.member.children.length > 0) {
var el = angular.element('<tr collection data="member.children" depth="{{newDepth}}" />');
scope.depth = parseInt(scope.depth || 0);
scope.newDepth = scope.depth + 1;
$compile(el)(scope);

// Flatten hierarchy, by appending el to parent
element.parent().append(el);
}
}
}
});


The problem is that, in collection added from
link
method,
depth
from parent scope is appended to
newDepth
. As a result
depth
for level 3 nodes has value
depth="3 2 1 "
.

How to disable inheriting of
depth
?

I have also noticed, that when I change
replace
to false in
collection
and
member
directives, depth works as intended, but then HTML is invalid.

Answer

It seems, that using the same scope for child nodes, causes that weird concatenation. Cure to that is pretty simple, as it turned out - new child scope is required for each child. Link function will look like this:

link: function (scope, element, attrs) {
    if (angular.isArray(scope.member.children) && scope.member.children.length > 0) {
        // Create isolated child scope, pass `scope` as parent
        var childScope = scope.$new(true, scope);
        childScope.depth = parseInt(scope.depth || 0) + 1;
        childScope.member = scope.member;

        var el = angular.element('<tr collection data="member.children" depth="{{depth}}" />');

        // use child scope
        $compile(el)(childScope);

        // Flatten hierarchy, by appending el to parent
        element.after(el);
    }
}

Plunk: https://plnkr.co/edit/xhJwfV?p=preview

I have also found somewhere else on SO and in API, that replace is deprecated, so actually it should't be used. So without replace it can look like this:

app.directive('tree', function () {
    return {
        restrict: 'E',
        template: '<div>' +
            '<table class="table table-striped table-hover">' +
            '    <thead>' +
            '        <tr>' +
            '            <th class="col-md-6">Account name</th>' +
            '            <th class="col-md-1">Depth</th>' +
            '        </tr>' +
            '        </thead>' +
            '        <tbody row data="data"></tbody>' +
            '    </table>' +
            '</div>',
        link: function (scope, element, attrs) {
          scope.data = [
              { 
                name: 'Root',
                children: [
                  {
                    name: 'Level 1',
                    children: [
                      {
                        name: 'Level 2',
                        children: [
                          {name: "Level 3"},
                          {name: "Level 3 -1"}
                        ]
                      }
                    ]
                  },
                  {
                    "name": "Level 1-1"
                  }
                ]
              }
          ];
        }
    };
});

app.directive('row', function() {
    return {
        restrict: "A",
        scope: {
            data:  '=',
            depth: '@'
        },
        template: '<tr cells ng-repeat="member in data" member="member" />',
        link: function (scope, element, attrs) {
            scope.depth = parseInt(scope.depth || 0);
        }
    }
});

app.directive('cells', function($compile) {
    return {
        restrict: "A",
        scope: {
            data:  '=',
            depth:  '@',
            member: '='
        },
        template: //'<tr ng-class="{selected: member.selected}">' +
            '<td>' +
            '   <span ng-show="depth > 0" style="width: {{depth * 16}}px; display: inline-block;"></span> {{member.name}}' +
            '</td>' +
            '<td>{{depth}}</td>',
            //'</tr>',
        link: function (scope, element, attrs) {
            if (scope.member && angular.isArray(scope.member.children) && scope.member.children.length > 0) {
                var childScope = scope.$new(true);
                childScope.depth = parseInt(scope.depth || 0) + 1;
                childScope.data = scope.member.children;

                var el = angular.element('<tr cells ng-repeat="member in data" member="member" depth="{{depth}}" />');

                $compile(el)(childScope);

                // Flatten hierarchy, by appending el to parent
                element.after(el);
            }
        }
    }
});

Plunker: https://plnkr.co/edit/j3YcuQ?p=preview

Comments