AngularJS - ng-if - show when body has a class - angularjs

I want to show/hide an element using ng-if
e.g. I would like to show an element if body has got specific class
I've tried the following, but no success - is this even valid expression?
<div ng-if="document.querySelector('body').className.indexOf('bodyHasThisClass') >= 0"></div
Any suggestions much appreciated. Here is a plunk example: http://plnkr.co/edit/kzXXg0sne8nyQZuufao7?p=preview
From AngularJS docs: https://docs.angularjs.org/guide/expression
Angular expressions do not have access to global variables like window, document or location. This restriction is intentional. It prevents accidental access to the global state – a common source of subtle bugs.

How about adding a controller to your directive
app.directive('wrapperDirective', function() {
return {
restrict: 'A',
templateUrl: 'wrapper-directive.html',
replace: true,
transclude: true,
controller: function($scope){
$scope.a = (document.querySelector('body').className.indexOf('bodyHasThisClass')>= 0)? true : false;
$scope.b = (document.querySelector('body').className.indexOf('noSuchClass')===-1)? true:false;
}
};
});
And in template:
<div ng-if="a==true">
visible if body has class bodyHasThisClass
</div>
<div ng-if="b==false">
this should be hidden
</div>

Related

Angular directive checking active or not

Is it possible to know whether dirictive active or not?
Such as if I have something similar code
<my-dir><my-dir>
<div ng-show='my-dir'></div>
This purpose is important for me when I comment out my directive inside of div should not shown. That's why I set ng-show
for that you must create a variable that checks which have an established directive, example:
.directive("myDir", function(){
return {
restrict: 'E',
link:function($scope){
$scope.myDir = true
}
}
})
<my-dir><my-dir>
<div ng-show='myDir'>on</div>
Or simplifying:
<my-dir ng-init="myDir = true"><my-dir>
<div ng-show='myDir'>on</div>
I'd suggest you to use directive that would be using isolated scope, so that it could be used in multiple places, In that we need to pass showDiv variable that will work with two way binding of variable which is supplied in show-div attribute.
Markup
<my-dir show-div="myDir"><my-dir>
<div ng-show='myDir'>Content shown when directive is present</div>
Directive
.directive("myDir", function(){
return {
restrict: 'E',
scope:{
showDiv: '='
},
link:function($scope,element){
$scope.showDiv = true; //div will shown when directive is present
$scope.$on('$destroy', function(){
$scope.showDiv = false; //div will get hidden when directive is removed or not present
});
}
}
})

AngularJS : directives which take a template through a configuration object, and show that template multiple times

I'm looking to create a custom directive that will take a template as a property of a configuration object, and show that template a given number of times surrounded by a header and footer. What's the best approach to create such a directive?
The directive would receive the configuration object as a scope option:
var app = angular.module('app');
app.directive('myDirective', function() {
return {
restrict: 'E',
scope: {
config: '=?'
}
...
}
}
This object (called config) is passed optionally to the directive using two way binding, as show in the code above. The configuration object can include a template and a number indicating the number of times the directive should show the template. Consider, for example, the following config object:
var config = {
times: 3,
template: '<div>my template</div>'
};
It would, when passed to the directive, cause the directive to show the template five times (using an ng-repeat.) The directive also shows a header and a footer above and below the template(s):
<div>the header</div>
<div>my template</div>
<div>my template</div>
<div>my template</div>
<div>the footer</div>
What's the best way to implement this directive? Note: When you reply, please provide a working example in a code playground such as Plunker, as I've run into problems with each possible implementation I've explored.
Update, the solutions I've explored include:
The use of the directive's link function to append the head, template with ng-repeat, and footer. This suffers from the problem of the template not being repeated, for some unknown reason, and the whole solutions seems like a hack.
The insertion of the template from the configuration object into middle of the template of the directive itself. This proves difficult because jqLite seems to have removed all notion of a CSS selector from its jQuery-based API, leading me to wonder if this solution is "the Angular way."
The use of the compile function to build out the template. This seems right to me, but I don't know if it will work.
You could indeed use ng-repeat but within your directive template rather than manually in the link (as that wouldn't be compiled, hence not repeated).
One question you didn't answer is, should this repeated template be compiled and linked by Angular, or is it going to be static HTML only?
.directive('myDirective', function () {
return {
restrict: 'E',
scope: {
config: '=?'
},
templateUrl: 'myTemplate',
link: function(scope) {
scope.array = new Array(config.times);
}
}
}
With myTemplate being:
<header>...</header>
<div ng-repeat="item in array" ng-bind-html="config.template"></div>
<footer>...</footer>
I'd think to use ng-transclude in this case, because the header & footer wrapper will be provided by the directive the inner content should change on basis of condition.
Markup
<my-directive>
<div ng-repeat="item in ['1','2','3']" ng-bind-html="config.template| trustedhtml"><div>
</my-directive>
Directive
var app = angular.module('app');
app.directive('myDirective', function($sce) {
return {
restrict: 'E',
transclude: true,
template: '<div>the header</div>'+
'<ng-transclude></ng-transclude>'+
'<div>the footer</div>',
scope: {
config: '=?'
}
.....
}
}
Filter
app.filter('trustedhtml', function($sce){
return function(val){
return $sce.trustedHtml(val);
}
})

Angular - condition, transclude

I've written a sample directive with a conditional content (component.html):
<div class="panel panel-default">
<div class="panel-heading">{{title}}</div>
<div class="panel-body">
<p ng-show="loadingVisible()" class="text-center">Loading...</p>
<div ng-show="!loadingVisible()" ng-transclude></div>
</div>
Directive code (component.js):
app.directive('component', function () {
return {
restrict : 'A',
transclude : true,
replace : true,
templateUrl : 'component.html',
require : '^title',
scope : {
title : '#',
loadingVisible : '&'
},
controller : [ '$scope', function ($scope) {
if (!$scope.loadingVisible) {
$scope.loadingVisible = function () {
return false;
};
}
} ]
};
});
The main use of this directive is something like this (sample.html):
<div component title="Title">
<div id="t"></div>
</div>
And the controller code (sample.js):
app.directive('sample', function () {
return {
restrict: 'A',
templateUrl: 'sample.html',
controller: [ '$scope', function ($scope) {
$('#id');
} ]
};
});
0
The problem is that the div aquired by using jQuery selector isn't visible. I guess it's because the loadingVisible method (conditional content) hides that div in the construction phase. So when the sample directive tries to get it it fails. Am I doing something wrong? What's the coorect resolution of this problem? Or maybe my knowledge of directives is wrong?
I'll appreciate any help :)
if you're trying to interact with the DOM (or the directive element itself), you'll want to define a link function. The link function gets fired after angular compiles your directive and gives you access to the directives scope, the element itself, and any attributes on the directive. so, something like:
link: function (scope, elem, attrs) {
/* interact with elem and/or scope here */
}
I'm still a little unclear about what your directive is trying to accomplish though, so it's tough to provide much more help. Any additional details?
so if you want to ensure that a title is specified, you can just check for the presence of the title scope var when the directive gets linked, then throw an error if it's not there.
also, is there any reason loadingVisible needs to be an expression? (using '&' syntax). If you just need to show/hide content based on this value, you could just do a normal one-way '#' binding. so overall, something like:
app.directive('component', function () {
return {
restrict : 'A',
transclude : true,
replace : true,
templateUrl : 'component.html',
scope : {
title : '#',
loadingVisible : '#'
},
link : function (scope, elem, attrs) {
if (!scope.title) {
throw 'must specify a title!';
}
if (!attrs.loadingVisible) {
scope.loadingVisible = false;
}
}
};
});
If you need to get access to any of your transcluded content, you can use the elem value that gets injected into your link function, like so:
elem.find('#a');
an (updated) working plnkr: http://embed.plnkr.co/JVZjQAG8gGhcV2tz1ImK/preview
The problem is that directive structure is like this:
<div component>
<div id="a"></div>
</div>
The directive is used somewhere like this:
asd
The test directive uses a "a" element in its controller (or link) function. The problem is that the test controller code is run before the div is transcluded and it cannot see the content :/ A simple workaround is that the component should be before the test directive. Do you have any other solutions to this problem?

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.

Call a function in a angular-controller from outside of the controller?

I have a lightbox-dierective and controller that looks like this:
directive('modalDialog', function() {
return {
restrict: 'E',
scope: {
show: '='
},
replace: true, // Replace with the template below
transclude: true, // we want to insert custom content inside the directive
template: '<div class="ng-modal" ng-show="show"><div class="ng-modal-overlay" ng-click="hideModal()"></div><div class="ng-modal-dialog" ng-style="dialogStyle"><div class="ng-modal-dialog-content" ng-transclude><div class="ng-modal-close" ng-click="hideModal()">X</div></div></div></div>'
};
}).controller('Lightbox', function($scope) {
$scope.modalShown = false;
$scope.toggleModal = function() {
$scope.modalShown = !$scope.modalShown;
};
});
Here is the desierd html, what I need is to open the secon ligthbox from withing the first one:
<div ng-controller="Lightbox">
<span ng-mousedown='toggleModal()'>Open lightbox one</span>
<modal-dialog show='modalShown'>
<h2>One lightbox <span ng-mousedown='toggleModal()'>Open lightbox two</span></h2>
</modal-dialog>
</div>
<div ng-controller="Lightbox">
<span ng-mousedown='toggleModal()'>Open lightbox one</span>
<modal-dialog show='modalShown'>
<h2>another lightbox</h2>
</modal-dialog>
</div>
For most cases it works great! I use it in several occations throughout the site, with diffrent lightboxes and different content.
I have now come across a case, where I need to call one of the lightboxes from outside of the controller. Can this be achieved and in that case how do I reference the right lightbox?
I'd extend that setting to an object
var modalSet = {
shown: false,
toggle: function(){ modalSet.shown = !modalSet.shown }
}
Then put it on your main controller (the one with ngApp attribute) and have your entire scope modaleble.
Also, directives do have a controller option, but since only one modal is gonna show up at any given time, you might not want to re-create a controller for every new instance.
Upon re-reading your question: Where is it exactly -> "outside of the controller"?

Resources