Updating directive when scope variable changes in ng-grid? - angularjs

I have a requirement for consuming an array of objects within ng-grid that are custom styled to look like a tag (similar to tags on here).
I have taken the approach of using a cellTemplate and have created a custom directive for this.
What is happening is when you sort, other columns change but the 'Tags' column does not, it stays as is, like the directive isn't getting updated.
Here is my directive:
app.directive('tag', function($compile){
return {
restrict: 'EA',
link: function(scope, element, attrs) {
attrs.$observe('tags', function(value) {
var array = JSON.parse(value);
var newHtml = '<ul>';
for(var i=0;i<array.length;i++)
{
newHtml += '<li>' + array[i].text + '</li>';
}
newHtml += '</ul>';
var e = $compile(newHtml)(scope);
element.replaceWith(e);
});
}
}
});
Here is a plunker: http://plnkr.co/edit/OxeUPaLLWtiCnvmgehnl
Thanks

Don't know if this fulfils all your requirements but you can change your tag directive to this:
app.directive('tag', function($compile){
var ddo = {
restrict: 'EA',
template: '<div><ul><li ng-repeat="tag in tags">{{tag}}</li></div>',
scope: { tags: "=tags" }
};
return ddo;
});
Or if you want to keep your code, just change the DOM first and compile it afterwards:
app.directive('tag', function($compile){
var ddo = {
restrict: 'EA',
scope: { tags: "#tags" },
link: function(scope, element, attrs) {
attrs.$observe('tags', function(value) {
var array = JSON.parse(value);
var newHtml = '<ul>';
for(var i=0;i<array.length;i++)
{
newHtml += '<li>' + array[i].text + '</li>';
}
newHtml += '</ul>';
element.html(newHtml);
$compile(newHtml)(scope);
});
}
};
return ddo;
});
Edit: Also, if all you want to do is change the layout, nothing stops you from calling ng-repeat in your cellTemplate:
cellTemplate: '<ul><li ng-repeat="val in row.entity.arr">{{val}}</li></ul>'}

Related

How to make directive use the controller specified in directive attribute?

So I have a directive:
<directive data="user" templateUrl="./user.html" controller="UserController"></directive>
I want that directive to use the controller specified in "controller" attribute, as you see above.
Is it possible with AngularJS directives? Or should I do it other way, maybe with components?
My code currently looks like this:
app.directive('directive', function() {
var controllerName = "UserController"; // i want that to dynamicaly come from attribute
// check if controller extists:
var services = [];
app['_invokeQueue'].forEach(function(value){
services[value[2][0]] = true;
});
if (!services[controllerName]) controllerName = false;
return {
scope: { 'data' : '=' },
link: function (scope) {
Object.assign(scope, scope.data);
},
templateUrl: function(element, attr) {
return attr.templateurl;
},
controller: controllerName
}
});
You can do following (not exactly what you ask - it creates bunch of nested scopes, but should be sufficient):
.directive('directive', () => {
scope: { 'data' : '=' },
template: (elem, attrs) => {
return '<div ng-controller="' + attrs.controller + ' as vm"><div ng-include="' + attrs.template + '"></div></div>';
}
});
<directive data="user" templateUrl="./user.html" controller="UserController"></directive>
you may use $templateCache directly instead of ng-include
if you need controller/template/... to be dynamic, you need to observe/watch + dom manipulation + recompile stuff
Okay, so after analysing Petr's answer I post the working code using nested divs:
app.directive('directive', function() {
return {
scope: { 'data' : '=' },
link: function (scope) {
// this makes your fields available as {{name}} instead of {{user.name}}:
Object.assign(scope, scope.data);
},
template: function(element, attrs) {
var controllerName = attrs.controller;
var controllerString = controllerName + ' as vm';
// check if controller extists:
var services = [];
app['_invokeQueue'].forEach(function(value){
services[value[2][0]] = true;
})
if (!services[controllerName]) {
return '<div ng-include="\'' + attrs.templateurl + '\'"></div>';
} else {
return '<div ng-controller="' + controllerString + '"><div ng-include="\'' + attrs.templateurl + '\'"></div></div>';
}
}
}
});

Two Way binding with isolated scope not working in AngularJs

I have written a directive for simple dropdown. On click of one value, I am calling a function and updating the value.
If I log 'scope.$parent.selectedItem' , I am able to see the value. But that is not updated in parent controller.
This is Directive code
app.directive('buttonDropdown', [function() {
var templateString =
'<div class="dropdown-button">'+
'<button ng-click="toggleDropDown()" class="dropbtn">{{title}}</button>'+
'<div id="myDropdown" ng-if="showButonDropDown" class="dropdown-content">'+
'<a ng-repeat="item in dropdownItems" ng-click="selectItem(item)">{{item.name}}</a>'+
'</div>'+
'</div>';
return {
restrict: 'EA',
scope: {
dropdownItems: "=",
selectedOption: '=',
title: '#'
},
template: templateString,
controller: function($scope,$rootScope,$timeout) {
$scope.selectedOption = {};
$scope.showButonDropDown = false;
$scope.toggleDropDown = function() {
$scope.showButonDropDown = !$scope.showButonDropDown;
};
$scope.$watch('dropdownItems', function(newVal,oldval){
if(newVal){
console.log(newVal);
}
});
$scope.selectItem = function(item){
console.log(item);
$scope.selectedOption = item;
}
},
link: function(scope, element) {
scope.dropdownItems = scope.dropdownItems || [];
window.onclick = function (event) {
if (!event.target.matches('.dropbtn')) {
scope.showButonDropDown = false;
}
console.log(scope.$parent);
}
}
}
}]);
This is my HTML
<button-dropdown title="Refer a Friend" dropdown-items='dropDownList' selected-option='selectedItem'></button-dropdown>
This is my controller code
$scope.$watch('selectedItem',function(newVal,oldVal){
if(newVal){
console.log("*** New Val** ");
console.log(newVal);
}
});
I didn't understand one thing.. If I print 'scope.$parent.selectedItem', I could see the value. but it is not updating in the controller. Didn't understand, what am I missing. Can anyone help on this. Thanks in advance.
Try in this way
1. Try to $emit the scope variable in directive.
2. get that in controller by using $on.
Directive:
$scope.$emit('selectedItem',scopeVariable);
Controller:
$scope.$on('selectedItem',function(event,newVal){
if(newVal){
// logic here
}
});

Update ng-options hosted by a directive

I have a custom directive that displays a dropdownlist.
Is it possible to dynamically re-populate the ng-options from a datasource that comes from the controller that hosts the directive.
The datasource itself comes from a service.
Currently it works well from the initial array passed to the directive, but when I add new data (from the controller/service to this array I would like to update the item list.
Any help?
EDIT :
This is how I use my directive.
<select-item-obj-from-array datasource="ctrl.ActivityAddresses" ng-model="form.Activity.AddressID" name="AddressID" value="AddressID" label="City" .... />
My directive looks like:
app.directive('selectItemObjFromArray', function () {
return {
restrict: 'E',
replace: true,
template: function (element, attrs) {
var tpl = '';
tpl += "<div><div class=\"form-group clearfix\" >";
tpl += '<label for="' + attrs.name + '" class="col-lg-3 control-label">' + attrs.label + '</label>';
tpl += '<div class="col-lg-9">';
tpl += '<select ng-disabled="ngDisabled" name="' + attrs.name + '" ng-model="ngModel" chosen="datasource" ng-options="c.Name for c in datasource"></select>';
tpl += '</div>';
tpl += '</div>';
tpl += '</div>';
return tpl;
},
scope: {
ngModel: "=",
datasource: "="
},
link: function (scope, elem, attrs) {
var select = elem.find("select").eq(0);
select.chosen();
scope.$watch(function () {
return select[0].length;
},
function (newvalue, oldvalue) {
if (newvalue !== oldvalue) {
select.trigger("chosen:updated");
}
});
scope.$watch(attrs.ngModel, function () {
select.trigger('chosen:updated');
});
}
};
});
if my controller/service updated the ctrl.ActivityAddresses I don't know how to "reinvoke" the directive to update the dropdownlist..
You can broadcast from your service like this:
var broadcast = function() {
$rootScope.$broadcast('items.update');
};
Assuming that items is an array.
Then you can catch the broadcast in your controller or directive:
$scope.$on('items.update', function (event) {
//Do whatever you want with the items.
});
I think this is what you want? You don't need to change the ng-options directive for this.

AngularJS - cannot access directive controller in another directive

I have two angularjs directives (extWindow and taskBar) and want to inject taskBar's controller into extWindow in order to access it's scope. Because they don't share the same scope I used
require : '^$directive'
syntax to include it.
Doing so I could get rid of the error 'Controller 'taskBar', required by directive 'extWindow', can't be found!' but TaskBarCtrl is still undefined in link(..) method of the extWindow directive.
Any suggestions how to fix it?
var mod = angular.module('ui', [])
.directive('taskBar', function() {
var link = function(scope, el, attrs) {
$(el).css('display', 'block');
$(scope.titles).each(function(i,t) {
el.append('<span>' + t + '</span>')
});
};
return {
scope: {},
restrict : 'E',
controller: function($scope, $element, $attrs) {
$scope.titles = [];
this.addTitle = function(title) {
$scope.titles.push(w);
};
this.removeTitle = function(title) {
$scope.titles = jQuery.grep(function(n,i) {
return title != n;
});
}
},
link: link
};
}).directive('extWindow', function() {
return {
scope: {},
require: '^?taskBar',
restrict: 'E',
transclude: true,
template: '<div class="ui-window">\
<div class="ui-window-header"><span>{{windowTitle}}</span><div class="ui-window-close" ng-click="close()">X</div></div>\
<div class="ui-window-content" ng-transclude></div>\
</div>',
link: function(scope, element, attrs, taskBarCtrl) {
scope.windowTitle = attrs['windowTitle'];
scope.close = function() {
$(element).css('display', 'none');
}
//taskBarCtrl is not recognized!!!
taskBarCtrl.addTitle(scope.windowTitle);
}
}
});
http://jsfiddle.net/wa9fs2nm/
Thank you.
golbie.
If you have a controller for your parent directive and you need something like.
this.scope = $scope;
this.attrs = $attrs;
And in your in you link function for the child you need something like
var Ctrl = ctrl || scope.$parent.tBarCtrl;
Here's a Plunker

angular, in directive, adding to the template an element with ng model

I'm trying to add an input element with ng-model inside a directive.
my code
the link function of my directive:
link: function (scope, element, attrs) {
var elem_0 = angular.element(element.children()[0]);
for (var i in scope.animals[0]) {
elem_0.append(angular.element('<span>' + scope.animals[0][i].id + '</span>'));
//this part doesn't work
var a_input = angular.element('<input type="text">');
a_input.attr('ng-model', 'animals[0][' + i + '].name');
//end
elem_0.append(a_input);
}
it seems i need to call $compile() at the end, but have no idea how.
Try
var a_input = angular.element($compile('<input type="text" ng-model="animals[0][' + i + '].name"/>')($scope))
elem_0.append(a_input);
You are making directive more complicated than necessary by manually looping over arrays when you could use nested ng-repeat in the directive template and let angular do the array loops:
angular.module("myApp", [])
.directive("myDirective", function () {
return {
restrict: 'EA',
replace: true,
scope: {
animals: '=animals'
},
template: '<div ng-repeat="group in animals">'+
'<span ng-repeat="animal in group">{{animal.id}}'+
'<input type="text" ng-model="animal.name"/>'+
'</span><hr>'+
'</div>'
}
});
DEMO: http://jsfiddle.net/Ajsy7/2/

Resources