0

This is a challenging task for an AngularJS newbie like me so I would like to ask some help or assistance. So what I want to achieve is to group the data via tree level and tree id.

My current code for the HTML:

<!DOCTYPE html>
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>

<body>

  <div ng-app="myApp" ng-controller="myCtrl">
      <ul>
          <li ng-repeat="item in list">
              [[ item.code ]]

          </li>
      </ul>
  </div>

</body>

</html>

My current code for the JS:

var app = angular.module('myApp', []);

app.controller('myCtrl', function($scope) {

    $scope.list = [
        {
            "id": 1,
            "code": "TANK",
            "tree_id": 1,
            "level": 0,
            "parent": null
        },
        {
            "id": 2,
            "code": "OIL",
            "tree_id": 1,
            "level": 1,
            "parent": 1
        },
        {
            "id": 3,
            "code": "CHEM",
            "tree_id": 1,
            "level": 1,
            "parent": 1
        },
        {
            "id": 6,
            "code": "PROD",
            "tree_id": 1,
            "level": 2,
            "parent": 3
        },
        {
            "id": 4,
            "code": "BULK",
            "tree_id": 2,
            "level": 0,
            "parent": null
        },
        {
            "id": 5,
            "code": "LOG",
            "tree_id": 2,
            "level": 1,
            "parent": 4
        }
    ],
});

If you checked the $scope.list, you can find the tree_id, level and parent. So what I really want to achieve is to group the objects via tree id then the level 0 or top level will be the one present while the level 1 and up will be just be a collapsible content. Please take that the tree_id serves as a groupings between the object, the level can be considered as their hierarchy order and heir hierarchy root will depend on the integer value of the parent which is a tree id. Also please be aware that this is not limited to one branch but multiple, flexible and unlimited branches

The render should look something like this:

  • TANK
    • CHEM
    • OIL
      • PROD
  • BULK
    • LOG
2
  • can you change the structure of $scope.list or you have to use the existing structure? Commented Mar 29, 2016 at 11:18
  • The existing structure is as is,, actually it came from an API response Commented Mar 29, 2016 at 11:20

4 Answers 4

1

It's already discussed in StackOverFlow. You can use following link.

How can I group data with an Angular filter?

Using groupBy filter you can achieve tree structure.

Sign up to request clarification or add additional context in comments.

1 Comment

Hi! I appreciate the answer but I kinda changed the question into a more interesting and challenging one. The answers in your link is just limited to one level
1

You can as well use custom directives to handle recursive levels :

EDIT to add properties réf in directives and your new array structure

var app = angular.module('myApp', []);

app.directive('collection', function () {
	return {
		restrict : "E",
		replace : true,
        transclude: true,
		scope : {
			collection : '=',
			list : '=',
			coldisplay : '=',
			colfrom : '=',
			colto : '='
		},
		template : "<ul><member ng-repeat='member in collection track by $index' member='member' list='list' coldisplay='coldisplay' colfrom='colfrom' colto='colto'></member></ul>"
	};
});

app.directive('member', function ($compile) {
	return {
		restrict : "E",
		replace : true,
        transclude: true,
		scope : {
			member : '=',
			list : '=',
			coldisplay : '=',
			colfrom : '=',
			colto : '='
		},
		template : "<li>{{member[coldisplay]}}</li>",
		link : function (scope, element, attrs) {
			scope.children = [];
			angular.forEach(scope.list, function (value, key) {
				if (value[scope.colfrom] === scope.member[scope.colto]) {
					this.push(value);
				}
			}, scope.children);

			if (scope.children.length > 0) {
				element.append("<collection collection='children' list='list' coldisplay='coldisplay' colfrom='colfrom' colto='colto'></collection>");
				$compile(element.contents())(scope);
			}
		}
	}
});

app.controller('myCtrl', function ($scope) {

	$scope.list = [{
			"id" : 1,
			"code" : "TANK",
			"tree_id" : 1,
			"level" : 0,
			"parent" : null
		}, {
			"id" : 2,
			"code" : "OIL",
			"tree_id" : 1,
			"level" : 1,
			"parent" : 1
		}, {
			"id" : 3,
			"code" : "CHEM",
			"tree_id" : 1,
			"level" : 1,
			"parent" : 1
		}, {
			"id" : 6,
			"code" : "PROD",
			"tree_id" : 1,
			"level" : 2,
			"parent" : 3
		}, {
			"id" : 4,
			"code" : "BULK",
			"tree_id" : 2,
			"level" : 0,
			"parent" : null
		}
	];

	$scope.rootList = [];

	angular.forEach($scope.list, function (value, key) {
		if (value.parent == null) {
			this.push(value);
		}
	}, $scope.rootList);

})
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>

<body>
  <div ng-app="myApp" ng-controller="myCtrl">
	  <collection collection='rootList' list='list' coldisplay='"code"' colfrom='"parent"' colto='"id"'></collection>
  </div>
</body>
</html>

Inspired by https://toddmotto.com/everything-about-custom-filters-in-angular-js/

4 Comments

One question, I tried inspecting the elements and the TANK and CHEM are inside a span element. Where did the span element came from?
If you dont want this behaviour just add 'transclude: true' (Sample edited)
What do you mean by behavior? By the way what I am trying to do right now is toggle the child elements
Hi, I know this is too much but do you have a work around on putting it into tr instead of li as directives has issues on the tr tag. Also with toggle function?
0

in angular groupBy work's for object not for array. so you can generate this in your controller as you need.

I can show you a process to generate as you want. If want can try it.

in your controller call a function after getting you list from api.

    $scope.newList = {parents:[],childs:[]};


    var generateNav = function(parent) {
      $scope.newList.parents.push(parent);
      angular.forEach($scope.list, function(item) {
        if(item.tree_id === parent.tree_id) {
          $scope.newList.childs.push(item);
        }
      })
    };

    var generateNewList = function(list) {
      angular.forEach(list, function(item) {
      if(item.level === 0) {
        generateNav(item);
      }
      });
    };

    // after api cal and get data call this function with your `list`
    generateNewList($scope.list);

and In html: used newList

<ul>
          <li ng-repeat="item in newList.parents">
              {{ item.code }}
              <ul>
                <li ng-repeat="child in newList.childs" ng-if="child.tree_id === item.tree_id && item.id!==child.id">{{child.code}}</li>
              </ul>
          </li>
      </ul>

you can visit PLUNKER DEMO LINK

1 Comment

Hi! I appreciate the answer but I kinda changed the question into a more interesting and challenging one
0

to fix your issue I would suggest few changes. First of all since you are getting data from an API we need to fix the format to get a more reasonable structure. Something like below

[{
  id: 1,
  code: "TANK",
  tree_id: 1,
  parent: null,
  children: [{
    id: 3,
    code: "CHEM",
    tree_id: 1,
    parent: 1,
    children: [{
        id: 9,
        code: "PROD",
        tree_id: 1,
        parent: 3
    }]
  }, {
    id: 2,
    code: "OIL",
    tree_id: 1,
    parent: 1,
  }]
}, {
  id: 4,
  code: "BULK",
  tree_id: 2,
  parent: null,
  children: [{
    id: 5,
    code: "LOG",
    tree_id: 2,
    parent: 4,
  }]
}]

For this we need to write function as below

var createObject = function(json) {
    return {
        id: json.id,
        code: json.code,
        tree_id: json.tree_id,
        parent: json.parent,
        children: []
    }
};

var processList = function(temporaryObj) {

    for (var i = 0; i < list.length; i++) {
        if (list[i].parent === temporaryObj.id) {
            temporaryObj.children.push(processList(createObject(list[i])));
        }
    }

    return temporaryObj;
};

var convertList = function(list) {

    var temp = [];
    for (var i = 0; i < list.length; i++) {

        if (list[i].level === 0) {
            temp.push(processList(createObject(list[i])));
        }

    }

    return temp;
};

var newList = convertList(list_from_api);

And after this we need to create a recursive directive to print content in UI

app.directive("tree", ['$compile', '$templateCache', function($compile, $templateCache) {
  return {
    type: "E",
    scope: {
      list: "="
    },
    link: function(scope, element, attrs) {
      element.replaceWith( $compile( $templateCache.get('tree.html'))(scope));
    }
  }
}]);

where tree.html is as below

<ul ng-if="list.length > 0">
   <li ng-repeat="item in list">
      <span>{{ item.code }}</span>
      <tree list="item.children"></tree>
   </li>
</ul>

You can refer to this plunkr link for more details https://plnkr.co/edit/8TtgFfgUQd73Du47b7XB?p=preview

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.