AngularJs Multiple Calling of same directive is causing issue - angularjs

I am having a hard time getting a simple directive to work. I have a directive which will change the button color when I click on it and set it back to the original color when I click on it again.
Basically it is a mark button, which will let you mark something and changes it's color when you are done marking.
In order to change the color I make use of ng-class which sets an active class whenever a scope variable is set. It works fine when I use that directive once, when I use multiple instances it starts causing issues.
Here is the code.
app.directive('myNeeds',function() {
return {
restrict: 'E',
replace: true,
transclude: true,
templateUrl: 'templates/needs.html',
// text binding
scope: {
needkey: '#needKey',
needvalue: '#needValue',
needcity: '#needCity'
},
link: function(scope,element,attrs) {
scope.isNeed = false;
element.bind('click',function() {
scope.isNeed = !scope.isNeed;
});
}
}
});
<span class="star">
<i class="dse-sprite ico-blue-star hand" ng-class="{active:isNeed}"></i>
</span>
Now lets say I call this directive twice on a page, no matter which button I have clicked it will add the active class to the latest or the last button on the page.

Related

AngularJS parent div directive wait for children div stacked directives to complete

I have a portion of html that is like this
<div my-custom-security-directive>
<button ng-if={{some constraints}} ng-click={{logic}}>cancel</button>
<button ng-disabled={{some logic}} ng-click={{some logic}}>submit<button>
</div>
My custom security directive does dom manipulation to the buttons to show/hide them via css. The issue I am having is with timing and perhaps directive precedence? When all the code is finished executing I only see the submit button and not the cancel button. I believe this is because of the ng-if and I attempted to set the priority of my custom directive to a negative number to run last but I think that is only for stacked directives on the same element? I only have a link function defined in my directive which my understanding is that is the same as a post link function and should run once the children complete? Ultimately, my goal is to run my directive 'last' so that I can show both buttons if all the logic in the directive passes.
The shell of my directive :
angular.module('myModule')
.directive("myCustomSecurityDirective", function(a,b) {
//
return {
restrict: "A",
priority: -1,
link: function(scope, element, attrs, ctrl) {
//custom security role/perm logic using injected services a&b etc
if (userHasPermission ) {
element.find('input, textarea, button').addClass('my-show-css');
}
}
};
});
I did recently /today put that priority on the directive but I don't think it does anything in this particular scenario.
Even if my-custom-security-directive is able to attach a CSS class to the button and hide or show it, the button has its own ng-if condition. This means it's possible that the button could be destroyed and recreated later, and it wouldn't have the CSS class anymore. If the button uses ng-show instead of ng-if you may have more control, since the button would become hidden but remain in the DOM.
But I think my-custom-security-directive might want to have more control. You can use transclusion so that my-custom-security-directive acts as a container for each set of elements which should be destroyed or created based on userHasPermission.
Directive:
.directive('myCustomSecurityDirective', function () {
return {
transclude: true,
link: function (scope) {
scope.userHasPermission = true;
},
template: '<div ng-if="userHasPermission" ng-transclude></div>'
}
});
HTML:
<div my-custom-security-directive>
<button ng-if="...">cancel</button>
<button ng-disabled="...">submit</button>
</div>

AngularJS: Should I observe optional attributes?

Assume I have a directive my-button with an optional attribute disabled. People could use this directive like:
<my-button>Button text</my-button>
or
<my-button disabled="variable">Button Text</my-button>
Should I be watching for a disabled attribute? Could these usages somehow transition from one to the other?
In response to JB Nizet's request for the code in question, here's a clean version of the directive function:
function ButtonDirective() {
var directive = {
link: link,
restrict: 'E',
scope: {
click: '&',
disabled: '=?',
},
template: '<a class="my-button" ' +
'data-ng-class="{\'my-button-disabled\': disabled}" ' +
'data-ng-click="disabled || click()" ng-transclude />',
transclude: true
};
function link(scope) {
if (typeof scope.disabled == 'undefined') scope.disabled = false;
}
return directive;
}
The directive creates an anchor tag styled as a button. It accepts two properties/parameters: click and disabled. The latter is optional. When disabled, the click event should fire when clicked, otherwise the the click event should fire when clicked.
To reiterate: Should I worry about someone somehow adding, removing, or modifying the disabled attribute after the fact? If so, how should I go about it?
After hashing things out with JB Nizet, he counseled me to not worry about the HTML attribute changing.

Angular directive to swap ng-click function

I have an app where a person can build lists of 'favorites.' On the page for a particular item there is a button that you can click to add the item to your list of favorites.
What I would like to happen is: Click the button, item is added to favorites, now the button is a different color and if you were to click it again it would remove the item from your favorites.
I think it would be best to build a directive to handle this but I am completely lost.
In my html I have:
<md-button class="md-icon-button" aria-label="Favorite" ng-click="addFavorite(item.id)">
<md-icon md-svg-icon="heart"></md-icon></md-button>
My addFavorite and deleteFavorite functions work correctly, but I can't figure out how to toggle which one happens and how to update that after the request fires.
So lets address a few different things.
Most likely this is going to be a div styled to look like a button to achieve the effect you want. You could go the easy route and use http://getbootstrap.com/css/?#buttons or something else. Styling buttons is just not going to be the best cross browser support.
You could do this entirely with ng-class in a directive, some example code below. Assuming you are using bootstrap. This just becomes what class do you want to change it to kinda thing. Below I have used an icon but the principal is the same.
Directive
.directive('toggleClass', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
element.bind('click', function() {
if(element.attr("class") == "glyphicon glyphicon-pencil") {
element.removeClass("glyphicon glyphicon-pencil");
element.addClass(attrs.toggleClass);
addFavorite(element.id);
} else {
element.removeClass("glyphicon glyphicon-ok");
element.addClass("glyphicon glyphicon-pencil");
removeFavorite(element.id);
}
});
}
};
});
Template
<i class="glyphicon glyphicon-pencil" toggle-class="glyphicon glyphicon-ok"></i>

AngularJS directive: Produce different HTML depending on $scope variable

I just started using AngularJS and immediately ran into a problem:
I have a sidebar which contains "action-buttons" - depending on the currently active view, different buttons should be visible.
My view-controller defines an object which looks as follows:
$scope.sidebar.actionButtons = [
{ icon: "plus", label: "Add", enabled: true, url: "customer.new" },
{ icon: "minus", label: "Delete", enabled: false, action: function(){ alert("Not implemented yet"); }}
];
As you can see, there are two different kinds of action-buttons: Either the button changes to another view (url is set to customer.new), or the button triggers an arbitrary function (action is set to alert()).
Each button type has to generate some slightly different html, and I'm not able to get this working.
After playing around for several hours, here is my current (not-working) approach:
My sidebar uses the following template-code to generate the buttons:
<ul class="nav" id="sidebar-action-buttons">
<action-button ng-repeat="button in actionButtons" button="button"/>
</ul>
Now, the actionButton directive has everything it needs and should produce the html depending on the button type:
angular.module('myApp')
.directive('actionButton', function($compile) {
function linker($scope, $element, $attrs){
var innerHtml = '';
$element.attr('ng-class', '{disabled: !button.enabled}');
if($scope.button.url) {
$element.attr('ui-sref-active', 'active')
innerHtml = '<a ui-sref="{{button.url}}">';
} else {
innerHtml = '<a ng-click="button.action()">';
}
innerHtml += '{{button.label}}</a>';
$element.html(innerHtml).show();
$compile($element.contents())($scope);
}
return {
restrict: 'E',
replace: true,
scope: { button: "=" },
link: linker,
template: "<li></li>"
}
});
This generates the correct content. The problem here is, that the attributes which are placed on the actionButton element (in this case ng-class='{disabled: !button.enabled}') are not compiled.
How can a directive produce different html depending on scope variables? What is the correct approach for doing this? How can I also compile the newly added attributes?
By the time the ng-class is added to the action-button element, the digest is over with for that element. You could call $scope.$apply(), but I would add the ng-class to each anchor element instead, then there would be no need to call $scope.$apply() again.
Because you are compiling content() of the li but ng-class has been added with li itself. Simple solution is to add ng-class directly with the action-button directive i.e.
<action-button ng-repeat="button in actionButtons" button="button" ng-class="{disabled: !button.enabled}" />

AngularJS directive remove class in parent element

I am using the following code to add / remove class "checked" to the radio input parent. It works perfectly when I use JQuery selector inside the directive but fails when I try to use the directive element, can someone please check my code and tell me why it is not working with element and how I can possibly add/ remove class checked to the radio input parent while using element instead of the jquery selectors? Thanks
.directive('disInpDir', function() {
return {
restrict: 'A',
scope: {
inpflag: '='
},
link: function(scope, element, attrs) {
element.bind('click', function(){
//This code will not work
if(element.parent().hasClass("checked")){
scope.$apply(function(){
element.parent().removeClass("checked");
element.parent().addClass("checked");
});
}else{
scope.$apply(function(){
element.parent().addClass("checked");
});
}
//This code works perfectly
$('input:not(:checked)').parent().removeClass("checked");
$('input:checked').parent().addClass("checked");
});
}
};
});
HTML:
<div class="inpwrap" for="image1">
<input type="radio" id="image1" name="radio1" value="" inpflag="imageLoaded" dis-inp-dir/>
</div>
<div class="inpwrap" for="image2">
<input type="radio" id="image2" name="radio1" value="" inpflag="imageLoaded" dis-inp-dir/>
</div>
Your code actually works for me in Plnkr (more or less):
http://plnkr.co/edit/vJJRYQQxH7u2bKSc27UA?p=preview
When you run this, the 'checked' class gets correctly added to the parent DIVs using only the first code you included. (I commented out the jQuery mechanism - I didn't add jQuery to this page, as a test.)
However, I think what you're trying to accomplish isn't working out because you're only capturing click events. The radio button that loses its checked attribute doesn't get a click event, only the next one does. In jQuery your selector is really broad - you're hitting every radio button, so it does what you want. But since you only trap click on the radio button that receives the click, it doesn't do what you want using the other pattern. checked gets added, but never removed.
A more Angular-ish pattern would be something like this:
http://plnkr.co/edit/HN7tLxkRA0jUL5GPjk5V?p=preview
link: function($scope) {
$scope.checked = false;
$scope.$watch('currentValue', function() {
$scope.checked = ($scope.currentValue === $scope.imgNumber);
});
$scope.setValue = function() {
$scope.currentValue = $scope.imgNumber;
};
}
What you see here lets Angular do all the dirty work, which is kind of the point. You can actually go a lot further than this - you could probably cut half the code out and do it all with expressions. The point is that in Angular, you really want to focus on the DATA (the model). You wire all of your behaviors and events up (controller) to things that manipulate that data, and then wire up all your DOM styles, classes, templates (view), etc. up to conditionals against that same data. And that is the point of MVC!

Resources