atul atul - 2 months ago 12
AngularJS Question

Issue $watch is not firing in converting jquery tree plugin into angular directive

I am trying to convert this jquery tree plugin jqTree. Therefore I started as follows.

Here is the Controller

angular.module('mainApp')
.controller('HomeController', ['$scope', '$window', '$timeout', function($scope, $window, $timeout ){
$scope.treeData = [
{
"label": "Ports",
"id": "portRoot",
"nodeType": "portRoot",
"contextMenu": true,
"className": "portIcon",
"children": [
{
"label": "bazingaJJRow1",
"id": "portID_3",
"nodeType": "portParent",
"className": "portIcon currentPortStatus ellipses",
"children": [
{
"label": "Stream",
"className": "streamIcon",
"id": "port_stream_3",
"nodeType": "stream",
"contextMenu": true
},
{
"label": "IPV4",
"className": "ipv4Icon",
"id": "port_ipv4_3",
"nodeType": "ipv4",
"contextMenu": true
},
{
"label": "IPV6",
"className": "ipv6Icon",
"id": "port_ipv6_3",
"nodeType": "ipv6",
"contextMenu": true
}
],
"tgenType": "Ixia",
"trafficState": 1,
"contextMenu": true
},
{
"label": "PORT:JJ:Bazinga1:Gi0/1/1",
"id": "portID_8",
"nodeType": "portParent",
"className": "portIcon currentPortStatus ellipses",
"children": [
{
"label": "Stream",
"className": "streamIcon",
"id": "port_stream_8",
"nodeType": "stream",
"contextMenu": true
},
{
"label": "IPV4",
"className": "ipv4Icon",
"id": "port_ipv4_8",
"nodeType": "ipv4",
"contextMenu": true
},
{
"label": "OSPF v2",
"className": "ospfv2Icon",
"id": "port_ospfv2_8",
"nodeType": "ospfv2",
"contextMenu": true
}
],
"tgenType": "Ixia",
"trafficState": 0,
"contextMenu": true
},
{
"label": "PORT:JJ:Bazinga2:Gi0/2/23",
"id": "portID_10",
"nodeType": "portParent",
"className": "portIcon currentPortStatus ellipses",
"children": [
{
"label": "Stream",
"className": "streamIcon",
"id": "port_stream_10",
"nodeType": "stream",
"contextMenu": true
},
{
"label": "IPV4",
"className": "ipv4Icon",
"id": "port_ipv4_10",
"nodeType": "ipv4",
"contextMenu": true
},
{
"label": "IPV6",
"className": "ipv6Icon",
"id": "port_ipv6_10",
"nodeType": "ipv6",
"contextMenu": true
}
],
"tgenType": "Spirent",
"trafficState": 0,
"controllerextMenu": true
}
]
}
];

$timeout(function () {
$scope.treeData[0].label = "New Port";
},5000);
}]);


For testing purpose,here in the controller I am updating the lable on the scope after 5 seconds using $timeout service.

HTML/ Directive Template

<jq-Tree tree-Data="treeData"></jq-Tree>


Directive Code

angular.module('mainApp')
.directive('jqTree', ['$window', '$state', '$timeout', function($window, $state, $timeout){
return {
restrict: 'E',
replace : true,
scope : {
treeData : "="
},
template: '<div id="navigationLeft"></div>',
controller : function jqTreeController($scope){

},
link : function jqTreeLink (scope, elem, attrs) {
elem.on({
"tree.click" : function(event) {
event.preventDefault();
console.log("clicked");
}
})
.tree({
data : scope.treeData,
slide : false,
useContextMenu : false,
onCreateLi: function(node, $li) {
$li.addClass("list-group-item")
.find("span.jqtree-title")
.addClass(node.className)
.data("primarykey", node.id)
.data("uplinkstate", "UP");

if(node.insertIcon){ // stats tab
$(node.insertIcon).insertBefore($li.find("span.jqtree-title"));
}
// uplink port up/down arrow icons for corresponding downlink port
if(node.nodeType == "portParent"){
$("<i class='fa fa-arrow-up text-success fa-uplnk-icon' title='Uplink por status'></i>").insertAfter($li.find("span.jqtree-title"));
}
}
});

scope.$watch('treeData', function (newValue, oldValue) {
if (newValue !== oldValue) { //ignore initialization of watcher
elem.tree('loadData', newValue);
}
});
}
};
}]);


The problem I am facing is that even after the timeout is fired, the angular watch method inside the directive is not triggering to update the tree structure. What I am doing wrong here

Answer

As documentation say:

  • The listener is called only when the value from the current watchExpression and the previous call to watchExpression are not equal (with the exception of the initial run, see below). Inequality is determined according to reference inequality, strict comparison via the !== Javascript operator, unless objectEquality == true (see next point)

  • When objectEquality == true, inequality of the watchExpression is determined according to the angular.equals function. To save the value of the object for later comparison, the angular.copy function is used. This therefore means that watching complex objects will have adverse memory and performance implications.

You need pass third parameter into $watch function.

Example on jsfiddle.

angular.module('ExampleApp', [])
  .controller('ExampleController', function($timeout) {
    var vm = this;
    vm.treeData = [{
      "label": "Ports",
      "id": "portRoot",
      "nodeType": "portRoot",
      "contextMenu": true,
      "className": "portIcon",
      "children": [{
        "label": "bazingaJJRow1",
        "id": "portID_3",
        "nodeType": "portParent",
        "className": "portIcon currentPortStatus ellipses",
        "children": [{
          "label": "Stream",
          "className": "streamIcon",
          "id": "port_stream_3",
          "nodeType": "stream",
          "contextMenu": true
        }, {
          "label": "IPV4",
          "className": "ipv4Icon",
          "id": "port_ipv4_3",
          "nodeType": "ipv4",
          "contextMenu": true
        }, {
          "label": "IPV6",
          "className": "ipv6Icon",
          "id": "port_ipv6_3",
          "nodeType": "ipv6",
          "contextMenu": true
        }],
        "tgenType": "Ixia",
        "trafficState": 1,
        "contextMenu": true
      }, {
        "label": "PORT:JJ:Bazinga1:Gi0/1/1",
        "id": "portID_8",
        "nodeType": "portParent",
        "className": "portIcon currentPortStatus ellipses",
        "children": [{
          "label": "Stream",
          "className": "streamIcon",
          "id": "port_stream_8",
          "nodeType": "stream",
          "contextMenu": true
        }, {
          "label": "IPV4",
          "className": "ipv4Icon",
          "id": "port_ipv4_8",
          "nodeType": "ipv4",
          "contextMenu": true
        }, {
          "label": "OSPF v2",
          "className": "ospfv2Icon",
          "id": "port_ospfv2_8",
          "nodeType": "ospfv2",
          "contextMenu": true
        }],
        "tgenType": "Ixia",
        "trafficState": 0,
        "contextMenu": true
      }, {
        "label": "PORT:JJ:Bazinga2:Gi0/2/23",
        "id": "portID_10",
        "nodeType": "portParent",
        "className": "portIcon currentPortStatus ellipses",
        "children": [{
          "label": "Stream",
          "className": "streamIcon",
          "id": "port_stream_10",
          "nodeType": "stream",
          "contextMenu": true
        }, {
          "label": "IPV4",
          "className": "ipv4Icon",
          "id": "port_ipv4_10",
          "nodeType": "ipv4",
          "contextMenu": true
        }, {
          "label": "IPV6",
          "className": "ipv6Icon",
          "id": "port_ipv6_10",
          "nodeType": "ipv6",
          "contextMenu": true
        }],
        "tgenType": "Spirent",
        "trafficState": 0,
        "controllerextMenu": true
      }]
    }];

    $timeout(function() {
      console.log('$timeout', vm.treeData[0].label);
      vm.treeData[0].label = "New Port";
    }, 5000);
  })
  .directive('jqTree', [
    function() {
      return {
        restrict: 'E',
        replace: true,
        scope: {
          treeData: "="
        },
        template: '<div id="navigationLeft"></div>',
        controller: function jqTreeController($scope) {

        },
        link: function jqTreeLink(scope, elem, attrs) {
          elem.on({
              "tree.click": function(event) {
                event.preventDefault();
                console.log("clicked");
              }
            })
            .tree({
              data: scope.treeData,
              slide: false,
              useContextMenu: false,
              onCreateLi: function(node, $li) {
                $li.addClass("list-group-item")
                  .find("span.jqtree-title")
                  .addClass(node.className)
                  .data("primarykey", node.id)
                  .data("uplinkstate", "UP");

                if (node.insertIcon) { // stats tab
                  $(node.insertIcon).insertBefore($li.find("span.jqtree-title"));
                }
                // uplink port up/down arrow icons for corresponding downlink port
                if (node.nodeType == "portParent") {
                  $("<i class='fa fa-arrow-up text-success fa-uplnk-icon' title='Uplink por status'></i>").insertAfter($li.find("span.jqtree-title"));
                }
              }
            });

          scope.$watch('treeData', function(newValue, oldValue) {
            console.log('$watchCollection', newValue[0].label);
            if (newValue !== oldValue) { //ignore initialization of watcher
              elem.tree('loadData', newValue);
            }
          }, true);
        }
      };
    }
  ]);;
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqtree/1.3.5/tree.jquery.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqtree/1.3.5/jqtree.css" />
<div ng-app="ExampleApp">
  <div ng-controller="ExampleController as vm">
    After 5 second ports change to New Port
    <jq-tree tree-data="vm.treeData"></jq-tree>
    Change default root
    <input ng-model="vm.treeData[0].label">
  </div>
</div>

Comments