AngularJS : tree directive not working properly - angularjs

Creating an angular tree directive with an isolated scope that binds the value of two attributes...items and item-click-handler. Here is the code:
HTML
<div ng-app="myapp">
<div ng-controller="TreeCtrl">
<item-tree class="tree" items="treeFamily" item-click-handler="groupSelectedFunction(itemId)"></item-tree>
</div>
JAVASCRIPT
var module = angular.module('myapp', []);
module.controller("TreeCtrl", function($scope) {
$scope.treeFamily = [{
Name : "Parent",
Code : "Parent",
Children: [{
Name : "Child1",
Code : "Child1",
Children: [{
Name : "Grandchild1",
Code : "Grandchild1",
Children: []
},{
Name : "Grandchild2",
Code : "Grandchild2",
Children: []
},{
Name : "Grandchild3",
Code : "Grandchild3",
Children: []
}]
}, {
Name: "Child2",
Code : "Child2",
Children: []
}]
}];
$scope.groupSelectedFunction = function (itemId) {
alert(itemId + ' selected');
}
});
module.directive("itemTree", function($compile) {
return {
restrict: "E",
scope: {
items: '=',
itemClickHandler: '&'
},
template:
'<ul id="group-nodes" ng-repeat="item in items">' +
'<li id={{ item.Code }}>' +
'<a ng-click="itemClickHandler({itemId: 1})">{{ item.Name }}</a>'+
'<item-tree items="item.Children" item-click-handler="itemClickHandler(itemId)"></item-tree>' +
'</li>' +
'</ul>',
compile: function(tElement, tAttr) {
var contents = tElement.contents().remove();
var compiledContents;
return function(scope, iElement, iAttr) {
if(!compiledContents) {
compiledContents = $compile(contents);
}
compiledContents(scope, function(clone, scope) {
iElement.append(clone);
});
};
}
};
});
The tree builds fine. If i click the top level parent node the controller function runs and shows an alert with the expected value: "Parent selected". However if any of the other nodes are clicked the the function runs but the alert message is: "undefined selected". Relatively new to Angular so this one has left me scratching my head. Would appreciate any help.

It's not clear what ID should be showing in the alert for each child node, but it is more obvious why you're seeing undefined instead of a value...
When using the & isolate scope type, the arguments passed to a function from a template need to be passed in an object, which you followed for the top level element:
ng-click="itemClickHandler({itemId: 1})"
You need to follow the same syntax for the children. So,
item-click-handler="itemClickHandler(itemId)"
should be
item-click-handler="itemClickHandler({itemId: itemId})"
Demo

Related

angularjs how to access controller $scope variable in directive isolated scope

In angularjs I have been trying to access main controller $scope variable in my directive isolated scope.
My html code,
<body ng-controller="MainCtrl">
<div id="TestContainer" class="TestContainer" ng-init=Intialfunc()>
<collection collection='testdata'>{{testdata}}</collection>
</div>
</body>
My directive code,
var app = angular.module('plunker', []);
app.directive('collection', function () {
return {
restrict: "E",
replace: true,
scope: {collection: '='},
//controller: 'TreeController',
//bindToController: true,
template: "<ul><member ng-repeat='member in collection' member='member'></member></ul>"
}
})
app.directive('member', function ($compile) {
var linkerfunc = function(scope, element, attrs) {
var collectionSt = '<collection collection="member.children"></collection>';
$compile(collectionSt)(scope, function(cloned, scope) {
element.append(cloned);
});
}
return {
restrict: "E",
replace: true,
scope: {member: '=', ShowDetailsCtrlFunc : '&'},
template: "<li><span ng-click=ShowDetailsCtrlFunc()>{{member.NodeName}}</span></li>",
controller: 'MainCtrl',
//controllerAs: 'MainCtrl',
//bindToController: true,
link: linkerfunc
}
})
My controller code,
app.controller('MainCtrl', function ($scope) {
$scope.Intialfunc = function() {
$scope.testdata = []
var myjsondata = JSON.parse('{ "NodeName": "Parent", "children": [ { "NodeName": "mychild", "children": [ { "NodeName": "chld1", "children": [] } ] } ] }');
$scope.testdata.push(myjsondata);
console.log($scope.testdata) //This one is showing
}
$scope.ShowDetailsCtrlFunc = function(element,event) {
console.log("in function ShowDetailsCtrlFunc"); // coming to this fucntion on click.
console.log($scope.testdata) // but this one is not showing . shows undefined.
//event.stopImmediatePropagation();
};
});
it is coming to the function but not showing the controller $scope. I have created a plunker ,
plunker
Please help me. I have been struggling for many days.
You need to add a function expression to both of your directives' isolate scopes in order to properly call a function in your parent scope. Taking your original code, it should look something like this:
var app = angular.module('plunker', []);
app.directive('collection', function () {
return {
restrict: "E",
//replace: true, <- this is deprecated and should no longer be used
scope: {
collection: '=',
onMemberClick: '&'
},
template: "<ul><member ng-repeat='member in collection' member='member' on-click='onMemberClick()'></member></ul>"
}
})
app.directive('member', function ($compile) {
return {
restrict: "E",
//replace: true, <- this is deprecated and should no longer be used
scope: {
member: '=',
onClick : '&'
},
template: "<li><span ng-click='onClick()'>{{member.NodeName}}</span></li>"
}
});
And you original html should look something like this:
<body ng-controller="MainCtrl">
<div id="TestContainer" class="TestContainer" ng-init=Intialfunc()>
<collection collection='testdata' on-member-click='ShowDetailsCtrlFunc ()'>{{testdata}}</collection>
</div>
</body>
Argument binding
If you would like to actually know which member was clicked, you'll need to bind arguments to your function calls.
var app = angular.module('plunker', []);
app.directive('collection', function () {
return {
restrict: "E",
scope: {
collection: '=',
onMemberClick: '&'
},
template: "<ul><member ng-repeat='member in collection' member='member' on-click='onMemberClick({member: member})'></member></ul>"
}
})
app.directive('member', function ($compile) {
return {
restrict: "E",
scope: {
member: '=',
onClick : '&'
},
template: "<li><span ng-click='onClick({member: member})'>{{member.NodeName}}</span></li>"
}
});
Html:
<body ng-controller="MainCtrl">
<div id="TestContainer" class="TestContainer" ng-init=Intialfunc()>
<collection collection='testdata' on-member-click='ShowDetailsCtrlFunc (member)'>{{testdata}}</collection>
</div>
</body>
MainCtrl:
app.controller('MainCtrl', function ($scope) {
$scope.Intialfunc = function() {
$scope.testdata = []
var myjsondata = JSON.parse('{ "NodeName": "Parent", "children": [ { "NodeName": "mychild", "children": [ { "NodeName": "chld1", "children": [] } ] } ] }');
$scope.testdata.push(myjsondata);
console.log($scope.testdata) //This one is showing
}
$scope.ShowDetailsCtrlFunc = function(member) {
console.log("In show details function");
console.log(member);
};
});
plunker
Lets Begin with the query you have. You want to call a function from link inside the directive even when the scope is isolated. It's simple you want to access parent scope.
Here's the code you can use to access parent scope.
scope.$parent.yourFun();
//or you can do this by the code give below.
//Inside Directive Use this.
scope:{
fun:"&"
},
//now you can call this function inside link
link:function(scope, element,attr){
scope.fun();
}
In your app.directive, just put scope : false.
Your directive will use the same scope as his parent scope.

AngularJS directive isolate scope and parent scope

I'm trying to implement a recursive directive and it seems like to get it to work nicely I need to define an isolate scope as well as have access to the parent scope. Basically I want my directive to have access to variables set as attributes on the directive itself, but i also want to be able to access variables and methods set in the controller's scope. Is there a way to combine the two? I've tried with transclude but i suppose i'm not entirely sure if i've used it properly. Here is a fiddle example of my problem, where i'd like each 'child' in the directive to be able to call the function sayHi(): http://jsfiddle.net/n8dPm/655/
You have to pass the sayHi function to your directive.
Directives create their own scope, So sayHi function is not part of your directive scope, the way to allow it is by creating a new prop an pass it.
HTML
<div ng-app="myapp">
<div ng-controller="TreeCtrl">
<tree family="treeFamily"
say-hi="sayHi(name)"
ngTransclude></tree>
</div>
</div>
JS
var module = angular.module('myapp', []);
module.controller("TreeCtrl", function($scope) {
$scope.treeFamily = {
name : "Parent",
children: [{
name : "Child1",
children: [{
name : "Grandchild1",
children: []
},{
name : "Grandchild2",
children: []
}]
}, {
name: "Child2",
children: []
}]
};
$scope.sayHi = function(name){
alert(name+' says hello!')
}
});
module.directive("tree", function($compile) {
return {
restrict: "E",
scope: {
family: '=',
sayHi : '&'
},
transclude: true,
template:
'<p>{{ family.name }}</p>'+
'<ul>' +
'<li ng-repeat="child in family.children">' +
'<tree family="child" say-hi="sayHi(name)"></tree>' +
'<button ng-click="sayHi({name : child.name})">Say Hi</button>' +
'</li>' +
'</ul>',
compile: function(tElement, tAttr) {
var contents = tElement.contents().remove();
var compiledContents;
return function(scope, iElement, iAttr) {
if(!compiledContents) {
compiledContents = $compile(contents);
}
compiledContents(scope, function(clone, scope) {
iElement.append(clone);
});
};
}
};
});

Selected item in directive not working

I created a select directive and am using this directive twice. I need to see the selected items of both. What should I do?
HTML
<div select-list="items"></div>
<div select-list="items2"></div>
Controller
var myApp = angular.module('myApp',[]);
myApp.controller('mainController', function($scope) {
$scope.items = [
{
name: "1"
},
{
name: "2"
}
];
$scope.items2 = [
{
name: "3"
},
{
name:"4"
}
];
$scope.selectedValues = [];
});
Select directive
myApp.directive("selectList", function() {
return {
restrict: "EACM",
template: '<select ng-model="selectedValues" ng-options="item.name for item in data"></select>',
scope: {
data: '=selectList'
}
}
});
I need to add selected items of both "selects" into $scope.selectedValues.
I tried through ng-change, but it didn't work.
Your directive use isolated scope, so you can't access from the controller to the directive or from the directive to the controller.
You have to create a new entry.
I let you a fiddle that is working :
https://jsfiddle.net/Lv1q2sh2/1/
// Code goes here
var myApp = angular.module('app', []);
angular.module('app')
.directive("selectList", function(){
return {
restrict: "EACM",
require: 'ngModel',
template: '<select ng-model="selected" ng-change="onSelectedValue()" ng-options="item.name for item in data"></select>',
scope: {
data: '=selectList'
},
link: function (scope, element, attr, ngModel) {
scope.onSelectedValue = function () {
ngModel.$setViewValue(scope.selected);
}
}
}
})
.controller('mainController', function($scope) {
$scope.items = [
{name: "1"},
{name: "2"}
];
$scope.items2 = [
{name:"3"},
{name:"4"}
];
$scope.selectedValues = [];
});
Directive needs to be created properly:
Have a controller for your directive
If you are using isolated scope, make sure to pass selectedValue to the scope.
ex:
Directive:
myApp.directive("selectList", function(){
return{
restrict: "EACM",
template: '<select ng-model="selectedValues" ng-options="item.name for item in data"></select>',
scope: {
data: '=selectList',
ngModel: '='
}
//Add link function here, crate watcher on ngModel and update it back on select dropdown selection.
})};
HTML:
<div select-list="items" ng-model="selectedValue1" ></div>
<div select-list="items2" ng-model="selectedValue2"></div>
Add link function to directive and put a watcher on ngModel, once user makes change in selection, update parent ng-model.

AutoComplete using ngTagsInput Cannot Read Property 'then' of undefined

I am trying to figure out this problem but I am having no luck.
This is the plunker I wrote that works. Notice that the code works perfectly when I am accessing tags.json using a $http.get.
Angular Directive Code:
app.directive('tag', function($http) {
return {
restrict: 'E',
templateUrl: 'tag.html',
link: function (scope, el) {
scope.tags = [
{ text: 'Tag1' },
{ text: 'Tag2' },
{ text: 'Tag3' }
];
var test = [{ "text": "Tag9" },{ "text": "Tag10" }];
scope.loadTags = function (query) {
return $http.get('tags.json');
}
}
}
});
HTML inside of the 'tag.html':
<tags-input ng-model="tags">
<auto-complete source="loadTags($query)"></auto-complete>
</tags-input>
<p>Model: {{tags}}</p>
Working Pic:
Great but, I DON'T want to use $http.get because I already have an object that has the tags inside of it that I want to use for auto-complete. SO I tried this
Angular Directive Code:
app.directive('tag', function($http) {
return {
restrict: 'E',
templateUrl: 'tag.html',
link: function (scope, el) {
scope.tags = [
{ text: 'Tag1' },
{ text: 'Tag2' },
{ text: 'Tag3' }
];
var test = [{ "text": "Tag9" },{ "text": "Tag10" }];
scope.loadTags = test;
}
}
});
HTML inside of my 'tag.html':
<tags-input ng-model="tags">
<auto-complete ng-model="loadTags"></auto-complete>
</tags-input>
<p>Model: {{tags}}</p>
BUT this doesn't work at all. Instead I get
TypeError: Cannot read property 'then' of undefined
at http://cdnjs.cloudflare.com/ajax/libs/ng-tags-input/2.0.0/ng-tags-input.min.js:1:5044
at http://code.angularjs.org/1.2.15/angular.js:13777:28
at completeOutstandingRequest (http://code.angularjs.org/1.2.15/angular.js:4236:10)
at http://code.angularjs.org/1.2.15/angular.js:4537:7 angular.js:9563
Link to my Plunk:
http://plnkr.co/edit/wEqVMf?p=info
So the loadFunction needs to be changed so that it returns a promise:
app.directive('tag', function($q) {
...
link: function(scope) {
$scope.loadTags = function() {
var deferred = $q.defer();
deferred.resolve([{ text: 'Tag9' },{ text: 'Tag10' }]);
return deferred.promise;
}
}
}
In addition to that, you need to fix your markup so it uses the source option:
<auto-complete source="loadTags()"></auto-complete>
This fixed my problem

ng-model doesn't bind properly in directive template's select

See fiddle http://jsfiddle.net/5FdgC/2
var myModule = angular.module('myModule', [])
.controller('MyCtrl', ['$scope', function ($scope) {
$scope.model = { name: ' hans', game: 'wow' };
$scope.items = [{ name: ' jens', game: 'wow' }, { name: ' hans', game: 'wow' }];
$scope.equal = angular.equals($scope.items[1], $scope.model);
}]);
myModule.directive('selectBlock', function () {
return {
restrict: 'A',
replace: true,
template: '<select ng-model="model" ng-options="item.name for item in items"></select>',
scope: {
model: '=',
items: '='
},
link: function (scope, element, attributes) {
}
}
});
Problem:
I pass a model and some items. model angular.equals to true with an object in items. However, setting ng-model="model" in the select does not display model in the dropdown. It remains blank with the items listed beneath.
Say
items = [ob1, ob2]
angular.equals(ob2, model) = true
ng-model="model"
select renders as
--blank-- //selected value
ob1
ob2
instead of
ob1
ob2 // selected value
Cheers
Ok, I have found the problem.
http://jsfiddle.net/5FdgC/3/
.controller('MyCtrl', ['$scope', function ($scope) {
$scope.items = [{ name: ' jens', game: 'wow' }, { name: ' hans', game: 'wow' }];
// here, you have to set the default by reference, not by value.
$scope.model = $scope.items[1];
[...]

Resources