Edit in JSFiddle

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

myApp.directive('uiTree', function() {
  return {
    template: '<ul class="uiTree"><ui-tree-node ng-repeat="node in tree"></ui-tree-node></ul>',
    replace: true,
    transclude: true,
    restrict: 'E',
    scope: {
      tree: '=ngModel',
      attrNodeId: "@",
      loadFn: '=',
      expandTo: '=',
      selectedId: '='
    },
    controller: function($scope, $element, $attrs) {
        $scope.loadFnName = $attrs.loadFn;
      // this seems like an egregious hack, but it is necessary for recursively-generated
      // trees to have access to the loader function
      if($scope.$parent.loadFn)
        $scope.loadFn = $scope.$parent.loadFn;

      // TODO expandTo shouldn't be two-way, currently we're copying it
      if($scope.expandTo && $scope.expandTo.length) {
        $scope.expansionNodes = angular.copy($scope.expandTo);
        var arrExpandTo = $scope.expansionNodes.split(",");
        $scope.nextExpandTo = arrExpandTo.shift();
        $scope.expansionNodes = arrExpandTo.join(",");
      }
    }
  };
})
.directive('uiTreeNode', ['$compile', '$timeout', function($compile, $timeout) {
  return { 
    restrict: 'E',
    replace: true,
    template: '<li>' + 
      '<div class="node" data-node-id="{{ nodeId() }}">' +
        '<a class="icon" ng-click="toggleNode(nodeId())""></a>' +
        '<a ng-hide="selectedId" ng-href="#/assets/{{ nodeId() }}">{{ node.name }}</a>' +
        '<span ng-show="selectedId" ng-class="css()" ng-click="setSelected(node)">' + 
            '{{ node.name }}</span>' +
      '</div>' +
    '</li>',
    link: function(scope, elm, attrs) {
      scope.nodeId = function(node) {
        var localNode = node || scope.node;
        return localNode[scope.attrNodeId];          
      };
      scope.toggleNode = function(nodeId) {
        var isVisible = elm.children(".uiTree:visible").length > 0;
        var childrenTree = elm.children(".uiTree");
        if(isVisible) {
          scope.$emit('nodeCollapsed', nodeId);
        } else if(nodeId) {
          scope.$emit('nodeExpanded', nodeId);
        }
        if(!isVisible && scope.loadFn && childrenTree.length === 0) {
          // load the children asynchronously
          var callback = function(arrChildren) {
            scope.node.children = arrChildren;
            scope.appendChildren();
            elm.find("a.icon i").show();
            elm.find("a.icon img").remove();
            scope.toggleNode(); // show it
          };
          var promiseOrNodes = scope.loadFn(nodeId, callback);
          if(promiseOrNodes && promiseOrNodes.then) {
            promiseOrNodes.then(callback);
          } else {
              $timeout(function() {
                  callback(promiseOrNodes);
              }, 0);
          }
          elm.find("a.icon i").hide();
          var imgUrl = "http://www.efsa.europa.eu/efsa_rep/repository/images/ajax-loader.gif";
          elm.find("a.icon").append('<img src="' + imgUrl + '" width="18" height="18">');
        } else {
          childrenTree.toggle(!isVisible);
          elm.find("a.icon i").toggleClass("icon-chevron-right");
          elm.find("a.icon i").toggleClass("icon-chevron-down");
        }
      };

      scope.appendChildren = function() {
        // Add children by $compiling and doing a new ui-tree directive
        // We need the load-fn attribute in there if it has been provided
        var childrenHtml = '<ui-tree ng-model="node.children" attr-node-id="' + 
            scope.attrNodeId + '"';
        if(scope.loadFn) {
          childrenHtml += ' load-fn="' + scope.loadFnName + '"';
        }
        // pass along all the variables
        if(scope.expansionNodes) {
          childrenHtml += ' expand-to="expansionNodes"';
        }
        if(scope.selectedId) {
          childrenHtml += ' selected-id="selectedId"';
        }
        childrenHtml += ' style="display: none"></ui-tree>';
        return elm.append($compile(childrenHtml)(scope));
      };

      scope.css = function() {
        return { 
          nodeLabel: true,
          selected: scope.selectedId && scope.nodeId() === scope.selectedId
        };
      };
      // emit an event up the scope.  Then, from the scope above this tree, a "selectNode"
      // event is expected to be broadcasted downwards to each node in the tree.
      // TODO this needs to be re-thought such that the controller doesn't need to manually
      // broadcast "selectNode" from outside of the directive scope.
      scope.setSelected = function(node) {
        scope.$emit("nodeSelected", node);
      };
      scope.$on("selectNode", function(event, node) {
        scope.selectedId = scope.nodeId(node);
      });
      
      if(scope.node.hasChildren) {
        elm.find("a.icon").append('<i class="icon-chevron-right"></i>');
      }

      if(scope.nextExpandTo && scope.nodeId() == parseInt(scope.nextExpandTo, 10)) {
        scope.toggleNode(scope.nodeId());
      }
    }
  };
}]);

function MyCtrl($scope, $timeout) {
    $scope.assets = [
        { assetId: 1, name: "parent 1", hasChildren: true},
        { assetId: 2, name: "parent 2", hasChildren: false}
    ];
    $scope.selected = {name: "child 111"};
    $scope.hierarchy = "1,11";
    $scope.loadChildren = function(nodeId) {
        return [
            {assetId: parseInt(nodeId + "1"), name: "child " + nodeId + "1", hasChildren: true}, 
            {assetId: parseInt(nodeId + "2"), name: "child " + nodeId + "2"}
        ];
    }
    $scope.$on("nodeSelected", function(event, node) {
        $scope.selected = node;
        $scope.$broadcast("selectNode", node);
    });
}
<div ng-controller="MyCtrl">
  <ui-tree ng-model="assets" load-fn="loadChildren" expand-to="hierarchy" selected-id="111" attr-node-id="assetId"></ui-tree>
    <div>selected: {{ selected.name }}</div> 
</div>
/* tree */
.uiTree {
    list-style-type: none;
    margin-left: 10px;
    padding-bottom: 5px;
}
.uiTree li a.icon {
    display: inline-block;
    width: 24px;
    cursor: pointer;
    vertical-align: text-bottom;
}
.uiTree li .node:hover > a.addChild {
    display: inline-block;
}
.uiTree li a.addChild {
    margin-left: 10px;
    cursor: pointer;
    display: none;
    vertical-align: text-bottom;
}
.uiTree li .node {
    padding-bottom: 5px;
}
.uiTree span.nodeLabel {
    cursor: pointer;
}
.uiTree span.nodeLabel.selected {
    border: 1px solid #aaa;
    background-color: #ddd;
    padding: 1px 3px;
}

External resources loaded into this fiddle: