AngularJs: disable anchor / ngif negated - angularjs

I have this directive (hidden some sensitive values):
modApp.directive("dir1", [
function () {
return {
scope: {
"results": "=",
"allowView": "#",
"allowPrint": "#",
},
templateUrl: "myurl",
compile: function (elem, attrs) {
// some code
},
};
}]);
and the template snippet is:
<a ng-href="#/MyUrl/{{item.code}}" target="_blank">
<img class="img-thumbnail" ng-src="{{myUrlToImg}}">
</a>
So my issue is that I need to disable the anchor under certain security conditions.
Therefore when it is allowed, it is ok to show the anchor and the image.
But when it is not, it should only show the image.
The easy way would be to simply define something like:
<a ng-show="{{allowView}}"><img></a>
<img ng-hide="{{allowView}}"><img>
The problem that two requests to the image are generated, where it should only be one.
The ideal would be to have something like:
<a disabled></a>
I found a aDisabled directive here in SO, but it didn't work for me.
Also there is ng-if but I couldn't make it work with the negated value of allowView.
Any suggestions?
Plunker: http://plnkr.co/edit/UJgQsaDYaxErP5A2NZy2
This plunker does not still use the allowView scope variable.
Note that in html, I'm setting a hardcoded true/false, it seems that this also has an impact, but not sure why...

Related

ng-class property not working for directive but working for raw code?

I'm trying to refactor an Angular app which is very verbose using directives. As part of the page layout, there is a faux-tab browsing experience (looks like tabs, is actually implemented as normal links) which is the focus of the refactoring. I was successfully able to create a directive called tabbedNav to hold the container of the tabs. I am trying to create a directive to hold the individual tab-links:
.directive("tabHeader", function(){
return {
restrict: 'E',
transclude: true,
template: '<li ng-class="{\'my_css_class\': pageName === \'{{flag}}\' }"><a ng-href = {{url}} ng-transclude></a></li>',
scope: {
flag: '#',
url: '#'
},
link: function(scope, elem, attr) {}
}
})
This is used in code as follows:
<tabbed-nav>
<tab-header flag="value1" url="value2">some text here</tab-header>
</tabbed-nav>
It should be noted that the value pageName is set in my controller as:
$scope.pageName = value1;
The idea is that the tab header is present on every page, and each page has an ID; for each tab (every tab is present on every page), if the page ID is equal to the tab's flag value, then the CSS is set, otherwise it's not.
The issue I'm running into is that, while the href value of the anchor gets set properly, the CSS styling set by the ng-class does not (yes, I have quadruple-checked that the flag value is set correctly). However, when I replace the tab-header tag with the raw HTML in the template (replacing flag and url with their actual values) as follows, then everything works fine:
<tabbed-nav>
<li ng-class="{'my_css_class': pageName === 'value1' }">
<a ng-href = "value2" ng-transclude>
some text here
</a>
</li>
</tabbed-nav>
The reason I want to implement this as a directive is because I intend to have many tab-headers on the page and I would like to reuse this directive many times.
Thanks.
You don't need to use expression with {{}}(interpolation) inside ng-class, it will throw an error. Even the quotes are not needful there. it should look like below
'<li ng-class="{\'my_css_class\': pageName === flag }"><a ng-href = {{url}} ng-transclude></a></li>'
Also since you're using isolatedScope for your directive, pageName value wouldn't be reachable inside your directive. You have to pass the pageName to directive as binding.
Check Plunker
Turns out I was not aware of the impacts of scoping in Angular. My directive was unable to read my page-level scope field directly, and I had to pass the page-level field into the directive as well. This seems awkward, but this seems to be how it needs to be done.
Working code:
.directive("tabHeader", function(){
return {
restrict: 'E',
transclude: true,
template: '<li ng-class="{\'my_css_class\': \'{{scope-value}}\' === \'{{flag}}\' }"><a ng-href = {{url}} ng-transclude></a></li>',
scope: {
flag: '#',
url: '#',
scope-value: '#'
},
link: function(scope, elem, attr) {}
}
})
and HTML:
<tab-header
flag="value1"
url="value2"
scope-value={{value-from-controller-scope}}>
text here
</tab-header>

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}" />

In a Directive, passing function arguments through to the html template

I am on day 2 of Angular and am trying to create a directive. The idea is that I have several images of quilts to display and I don't want to repeat the same HTML. Here's an index.html snippet showing a use of the new directive and two 'arguments' I'll need in the partial:
<ng-samplequilt imgPath="img/t_3x3_no_sashing/jpg"
text="This is a 3x3 quilt without sashing.">
</ng-samplequilt>
Here's the partial:
<div>
<img src="{{imgPath}}"/>
<div>
{{text}}
</div>
</div>
Finally, there's the Directive (which may or may not work):
.directive('SampleQuilt', ['imgPath','text',function(imgPath, text) {
return {
restrict: 'E',
templateUrl: 'partials/sample_quilt.html'
};
}])
So I'm clearly a little over my head. I've read a good deal of the docs and some examples, but none seem to be quite what I'm doing. Or perhaps I have not internalized enough for it to stick.
I'm not looking for a full solution here; I don't mind working through it. But I am stuck - I don't know how to get imgPath and text to make their way to the partial where they can be used.
Also, Directives have embedded controllers. How does the partial know to refer to this controller? Why does it even have access to it, given it's buried in the Directive?
Thanks for a boot in the right direction.
EDIT -
Thanks to #Dalorzo I seem to have a solution.
First, his idea about defining the scope in the Directive worked.
Second, I named the directive "SampleQuilt". This did not work - the directive did nothing/could not be found. When I renamed it to sampleQuilt, however, the internal name translation worked. For similar reasons, the HTML had to refer to img-path, not imgPath.
Here are the three files now.
The index.html snippet:
<sample-quilt img-path="img/t_3x3_no_sashing.jpg"
text="This is a 3x3 quilt without sashing.">
</sample-quilt>
The partial:
<div>
<img src="{{img-path}}"/>
<div>
{{text}}
</div>
</div>
The directive:
.directive('sampleQuilt', function() {
return {
restrict: 'E',
scope:{ imgPath: "#", text: "#" },
templateUrl: 'partials/sample_quilt.html'
};
})
;
EDIT 2 -
The above doesn't work - I was getting burned by browser caching.
It seems as if this snippet in index.html is curious...
<sample-quilt img-path="img/t_3x3_no_sashing.jpg"
text="This is a 3x3 quilt without sashing.">
</sample-quilt>
The img-path attribute can apparently be spelled three different ways: img-path, 'imgPath', and img_path. All are converted to imgPath internally. When displaying the value in the partial, imgPath is correct.
Here's the corrected partial:
<div>
<img src="{{imgPath}}"/>
<div>
{{text}}
</div>
</div>
Based on your example above I think this should be what you intent:
var app = angular.module('demo',[]);
app.directive('SampleQuilt', function() {
return {
restrict: 'E',
scope:{ imgPath: "#", text: "#" },
templateUrl: 'partials/sample_quilt.html'
};
});
By adding scope to the directive we create an "isolated scope". With this approach scope can capture attributes in 3 ways:
# Captures the attribute value from the DOM as string value.
= Evaluates the attribute as property of the parent scope.
& Evaluates the attribute as method of the parent scope.
You can read more about it here:
http://onehungrymind.com/angularjs-sticky-notes-pt-2-isolated-scope/
Regarding your html:
Remove ng and don't use it as part of your directives they are reserved by the angular team and it is good to avoid them to avoid conflicts. You can read more about Angular Naming Conventions here
Cases (camel case or pascal case) means dash in angular directives so SampleQuilt needs to be used as sample-quilt in the html.
Sample:
<sample-quilt imgPath="img/t_3x3_no_sashing/jpg"
text="This is a 3x3 quilt without sashing.">
</sample-quilt>
Regarding your last question about the controller on directives. Directives returned object has a controller property that you can use like:
app.directive('SampleQuilt', function() {
return {
restrict: 'E',
controller: 'myDirController', /* <--- Controller Declaration */
scope:{ imgPath: "#", text: "#" },
templateUrl: 'partials/sample_quilt.html'
};
});
app.controller('myDirController', ['$scope', function ($scope) {
// My Directive Controller implementation
}]);

Detect if a transclude content has been given for a angularjs directive

I have a directive (a progressbar) which should have two possible states, one without any description and one with a label on the left side.
It would be cool to simple use the transcluded content for this label.
Does anyone know how I can add a class to my directive depending whether a transclude content has been given or not?
So I want to add:
<div class="progress" ng-class="{withLabel: *CODE GOES HERE*}">
<div class="label"><span ng-transclude></span>
<div class="other">...</div>
</div>
Thanks a lot!
After release of Angular v1.5 with multi-slot transclusion it's even simpler. For example you have used component instead of directive and don't have access to link or compile functions. Yet you have access to $transclude service. So you can check presence of content with 'official' method:
app.component('myTransclude', {
transclude: {
'slot': '?transcludeSlot'
},
controller: function ($transclude) {
this.transcludePresent = function() {
return $transclude.isSlotFilled('slot');
};
}
})
with template like this:
<div class="progress" ng-class="{'with-label': withLabel}">
<div class="label"><span ng-transclude="slot"></span>
<div class="other">...</div>
</div>
Based on #Ilan's solution, you can use this simple $transclude function to know if there is transcluded content or not.
$transclude(function(clone){
if(clone.length){
scope.hasTranscluded = true;
}
});
Plnkr demonstrating this approach with ng-if to set default content if nothing to transclude: http://plnkr.co/hHr0aoSktqZYKoiFMzE6
Here is a plunker: http://plnkr.co/edit/ednJwiceWD5vS0orewKW?p=preview
You can find the transcluded element inside the linking function and check it's contents:
Directive:
app.directive('progressbar', function(){
return {
scope: {},
transclude: true,
templateUrl: "progressbar.html",
link: function(scope,elm){
var transcluded = elm.find('span').contents();
scope.withLabel = transcluded.length > 0; // true or false
}
}
})
Template:
<div class="progress" ng-class="{'with-label': withLabel}">
<div class="label"><span ng-transclude></span>
<div class="other">...</div>
</div>
You can also create your custom transclusion directive like so:
app.directive('myTransclude', function(){
return {
link: function(scope, elm, attrs, ctrl, $transclude){
$transclude(function(clone){
// Do something with this:
// if(clone.length > 0) ...
elm.empty();
elm.append(clone);
})
}
}
})
Based on the solution from #plong0 & #Ilan, this seems to work a bit better since it works with whitespace as well.
$transcludeFn(function(clonedElement) {
scope.hasTranscludedContent = clonedElement.html().trim() === "";
});
where previously <my-directive> </my-directive> would return that it has a .length of 1 since it contains a text node. since the function passed into $transcludeFn returns a jQuery object of the contents of the transcluded content, we can just get the inner text, remove whitespace on the ends, and check to see if it's blank or not.
Note that this only checks for text, so including html elements without text will also be flagged as empty. Like this: <my-directive> <span> </span> </my-directive> - This worked for my needs though.

parent directive erasing child ng-repeat

I am learning angularjs through the process of taking an existing site that was built primarily with JQuery and trying to "angularize" it. I am having trouble reproducing the same functionality in angular.
Please see the following plunker.
http://plnkr.co/edit/n4cbcRviuzNsieVvr4Im?p=preview
I have a ul element with an angularjs directive called "scroller" as seen below.
<ul class="dropdown-menu-list scroller" scroller style="height: 250px">
<li data-ng-repeat="n in notifications">
<a href="#">
<span class="label label-success"><i class="icon-plus"></i></span>
{{n.summary}}
<span class="time">{{n.time}}</span>
</a>
</li>
</ul>
The scroller directive looks like this:
.directive('scroller', function () {
return {
priority: 0,
restrict: 'A',
scope: {
done: '&',
progress: '&'
},
link: function (scope, element, attrs) {
$('.scroller').each(function () {
var height;
if ($(this).attr("data-height")) {
height = $(this).attr("data-height");
} else {
height = $(this).css('height');
}
$(this).slimScroll({
size: '7px',
color: '#a1b2bd',
height: height,
disableFadeOut: true
});
});
}
};
What i want to happen is that the ng-repeat executes on the notifications array in the controller, producing a collection of li elements that exceed 250px therefore a slimscrollbar would be added. What actually happens is the result of the ng-repeat is not included in the final DOM. I believe the call in the parent scroller directive of $(this).slimScroll() is called after the ng-repeat executes and replaces the DOM. If i remove the scroller attribute, the li elements show up.
I am sure there is a strategy for this and am hoping the community can educate me on a better approach or alternate approach. thoughts? again the plunker is here.
http://plnkr.co/edit/n4cbcRviuzNsieVvr4Im?p=preview
Thanks,
Dan
The issue is actually your directive scope. You are using an explicit object as the scope, which means you are isolating the scope, which means the directive scope isn't inheriting from its parent anymore. So notifications from the parent controller is no longer reachable from the directive scope (and therefore any elements inside of its element).
If you remove this from your directive it should work:
scope: {
done: '&',
progress: '&'
}
I notice that you aren't using those attributes anyway so it shouldn't break any other functionality.
Look at the API docs http://docs.angularjs.org/guide/directive and look for isolate scope for more details.
An alternative to what you're trying to do would just be something like this
scope.$watch(attr.done, function(val) { //do something when the value changes })
Since I don't know your use case I can't say what the best solution would be.

Resources