Custom directive doesn't get updated inside cellTemplate of ui-grid - angularjs

I am using a custom directive named flag inside the cellTemplate of ui-grid. This directive just accepts a country name (USA or CANADA), and displays its corresponding flag. The directive works fine when the page loads for the first time. However, later even if I change the value of flag to anything, the custom directive doesn't update and display the new flag. Not sure what am I missing.
Plnkr

The directive gets compiled once! You need to watch the attribute or set a two way binding!
app.directive('flag', function() {
var flags = {
USA: 'https://github.com/hjnilsson/country-flags/raw/master/png100px/us.png',
CANADA: 'https://github.com/hjnilsson/country-flags/raw/master/png100px/ca.png'
};
return {
restrict: "A",
scope: {
flag: "#"
},
link: function(scope, elem, attrs) {
scope.$watch('flag', function(newValue, oldValue){
angular.element(elem).attr('src', flags[newValue]);
});
}
};
});
working plunkr ==> http://plnkr.co/edit/dcHDMEY8MpQnCnm99aDd?p=preview

Related

Please provide me explanation for the Slider directive

I am trying to understand the Slider directive provided here.
myApp.directive('slider', function() {
return {
restrict: 'A',
scope: {
ngModel: '='
},
link: function(scope, elem, attrs) {
console.log(scope.ngModel);
return $(elem).slider({
range: "min",
animate: true,
value: scope.ngModel,
slide: function(event, ui) {
return scope.$apply(function(){
scope.ngModel = ui.value;
});
}
});
}
};
});
Like purpose of ngmodel, range, animate, value:scope.ngModel etc. I have read some article about the same from here but this seems to be a little complicated for me.
This is a typical isolated scope directive configuration that is using a jQuery plugin .
scope.ngModel is passed in to the directive through the matching attribute ng-model in the html.
The '=' makes it a 2 way binding to it's parent.
As for the slider it is a jQuery UI slider and the options like animate and range etc are documented in their API
$apply() is used whenever events outside of angular context are used to modify scope. Angular needs to be told to run a digest to update the view

Angular remove class on location change

I want to remove a class when ever the user changes page, mostly because I want to collapse the bootstrap navbar. To do this I have created a directive that will remove a specified class. Whats the best way to trigger my directive on the $routeChangeSuccess event without listening to to the event within the directive as I would like to keep it flexible.
app.directive('removeClass', function() {
return {
scope: {},
restrict: 'A',
link: function(scope, element, attrs) {
//someway to trigger this method e.g. from an event listener outside the directive
scope.removeClass = function() {
element.removeClass(attrs.removeClass);
}
}
};
});
You can change a $rootScope variable with ng-class. Set that variable when there in a controller depending on page.

Dynamically add angular attributes to an element from a directive

I'm trying to build a directive that change loading status on buttons for slow ajax calls. Basically the idea is to set an attribute "ng-loading" to a button element, and let the directive add the rest of stuff.
This is the html code:
<button class="btn btn-primary" name="commit" type="submit" ng-loading="signupLoading">
Submit
</button>
And this is the directive code:
.directive('ngLoading', ['$compile', function($compile) {
return {
restrict: 'A',
replace: false,
terminal: true,
link: function(scope, element, attrs) {
element.attr('ng-class', '{loading:' + attrs['ngLoading'] +'}');
element.attr('ng-disabled', attrs['ngLoading']);
element.append(angular.element('<img class="loading-icon" src="/assets/images/loading-icon.gif"/>'));
$compile(element.contents())(scope);
}
};
}]);
It all looks correct in the rendered HTML, but the attributes added from the directive is not funcioning at all. I can move those attributes to the HTML code and everything works great, but that's quite some redundant code in many places.
I referenced the post Angular directive to dynamically set attribute(s) on existing DOM elements but it does not solve my problem.
Any comment/suggestion are welcome. Thanks in advance.
You don't need to recompile that directive if all you want is some DOM manipulation, you can add and remove class in regards to the changes of a scope property. You can use $watch instead.
JAVASCRIPT
.directive('ngLoading', function() {
return function(scope, element, attrs) {
var img = angular.element('<img class="loading-icon" src="/assets/images/loading-icon.gif"/>');
element.append(img);
scope.$watch(attrs.ngLoading, function(isLoading) {
if(isLoading) {
img.removeClass('ng-hide');
element.addClass('loading');
element.attr('disabled', '');
} else {
img.addClass('ng-hide');
element.removeClass('loading');
element.removeAttr('disabled');
}
});
};
});
Note: Your code doesn't work because it compiles the contents of the elements, not the element itself, where you attach the attributes you have implemented.
try $compile(elem)(scope); and it should work properly, but I don't recommend it because each element with such directive will have to re-compile again.
UPDATE:
before using $compile remove the attribute 'ngLoading' to the element to prevent infinite compilation.
elem.removeAttr('ng-loading');
$compile(elem)(scope);

Selectize & AngularJS not playing along with Select box (Multiple options)

I've been trying to implement Selectize with AngularJS (1.2.4). I'm using this directive to interface with the plugin and everything is working smoothly until now. When using the ngModel from a normal select box It works fine, and returns the expected object but when I try to use it with the multiple attribute, it won't set the model.
I've inspected the DOM and appears the script removes unselected options from the hidden select and that might be messing with the angular binding.
I've created a Plunkr to demonstrate the behaviour.
http://plnkr.co/It6C2EPFHTMWOifoYEYA
Thanks
As mentioned in the comments above, your directive must listen to changes in the selectize plugin and then inform angular of what happened via ng-model.
First, your directive needs to ask for an optional reference to the ngModel controller with the following:
require: '?ngModel'.
It is injected into your link function as an argument in the 4th position:
function(scope,element,attrs,ngModel){}
Then, you must listen for changes in selectize with
$(element).selectize().on('change',callback)
and inform ngModel with ngModel.$setViewValue(value)
Here is a modified version of your directive. It should get you started.
angular.module('angular-selectize').directive('selectize', function($timeout) {
return {
// Restrict it to be an attribute in this case
restrict: 'A',
// optionally hook-in to ngModel's API
require: '?ngModel',
// responsible for registering DOM listeners as well as updating the DOM
link: function(scope, element, attrs, ngModel) {
var $element;
$timeout(function() {
$element = $(element).selectize(scope.$eval(attrs.selectize));
if(!ngModel){ return; }//below this we interact with ngModel's controller
//update ngModel when selectize changes
$(element).selectize().on('change',function(){
scope.$apply(function(){
var newValue = $(element).selectize().val();
console.log('change:',newValue);
ngModel.$setViewValue(newValue);
});
});
});
}
};
});
Also:
plunker
angular docs for ngModelController

AngularJS - directive components dropdown not getting initialized for edit

I am new to Angular JS.
What I am trying to do is create a reusable component using Directive. As of now it just has one drop-down. While doing Add the drop-down is getting populated with the values in permissionValues array and binding with the empty model with the selected value is happening. But when I try to Edit the drop-down is not getting initialized with existing model value value.
The directive code.
directive('userPermissions', function() {
return {
restrict: 'E',
template:'<div><select ng-model="ngModel" ng-options="abc [optValue] as abc [optDescription] for abc in array"></select>{{tab}}</div>',
replace: true,
transclude: true,
scope:{ ngModel: '=', tab:'='},
link: function (scope, element, attrs) {
scope.optValue = attrs.optValue;
scope.optDescription = attrs.optDescription;
scope.$watch(attrs.array, function(newVal, oldVal){
if(newVal != oldVal){
scope.array = newVal;
}
});
}
};
});
The HTML code
<user-permissions tab="tab"
ng-model="newUser.canCheckout1"
array="permissionValues"
opt-value="value"
opt-description="label"></user-permissions>
in main controller
$scope.permissionValues = [{label:'Standard', value:'true'},{label:'Restricted', value:'false'}];
As you can see I have add "tab" for testing. I am changing the value of tab on load function which is getting called when I click on Edit. New value of tab is getting printed but the drop-down is not getting initialized.
You're trying to two-way bind your array attribute from the controller using:
scope: {array: '=' ... }
The problem is that you don't have any $scope.array in your controller. So you need to somehow link the two. try this:
link: function(scope, element, attrs){
scope.optValue = attrs.optValue;
scope.optDescription = attrs.optDescription;
scope.array = scope.$eval(attrs.array);
}
This will work but wouldn't create dynamic binding. It will only read the value once and then it won't be updated if the scope[attrs.array] ever changes. In order to do that, you'd have to do a $watch to listen for changes:
scope.$watch(attrs.array, function(newVal, oldVal){
if(newVal != oldVal){
scope.array = newVal;
}
});
Hope that helps.
UPDATE:
To clarify, there are multiple ways to do this, so its up to you for how you want to do it. But the idea behind the scope: { ... } binding in a directive is that you're specifying which properties of the scope you want to be bound and how you want them bound (one-way or two-way). You were telling angular to bind the $scope.array property from your controller into your directive. But because there wasn't any $scope.array property, nothing was being bound. So you can either change your binding to say scope: {permissionValues: '='} or you can change your $scope.permissionValues in your controller function to be $scope.array. Either should work.
But since you're trying to use the array="permissionValues" attribute on your directive to signify which $scope property to use, I figured you would want to do it the way I specified above. Using scope.$watch means you don't have to use any scope: { ... } binding at all. The $watch function manually creates that binding for you and allows you to use the attrs.array value to dynamically bind to the proper scope parameter at runtime. Using scope: {} requires that you know what the scope property is called when the directive is created, and not when it is read from the DOM.
Does that make more sense?

Resources