How to pass a variable through scope into directive - angularjs

Inside an element directive named tabset, i am using element directive named tab twice which requires tabset.
I am doing a console.log(attr.newvar) from link of tabset.
newvar is the value passed into the scope of the tabset directive.
So the tabset gets called 2 times (which i suppose is correct), and hence output is consoled twice.
1st time, the console output is giving the correct output, but the 2nd time it is showing newvar as undefined .
but i am not able to access newvar through scope.newvar.In case of console.log(scope.newvar), i get output as undefined twice.
Why is this happening ?
HTML snippet
<tabset newvar="black">
<tab></tab>
<tab></tab>
</tabset>
JS snippet
.directive('tab',function(){
return{
restrict:'E',
require:'^tabset',
transclude:true,
scope:{
heading:"#"
},
template:'<div ng-show="active" ng-transclude></div>',
link:function(scope,elem,attr,tabsetCtrl){
scope.active = false;
tabsetCtrl.add(scope);
}
}
})
.directive('tabset',function(){
return{
restrict:'E',
scope:{
item:"=",
newvar:"#"
},
transclude:true,
templateUrl:'/partials/tabset/tabset.html',
bindToController:true,
controllerAs:'tabset',
controller:function($scope){
var self = this;
self.tabs = []
self.add = function add(tab){
self.tabs.push(tab);
if(self.tabs.length === 1){
tab.active = true;
}
}
self.click = function click(selectedTab){
angular.forEach(self.tabs,function(tab){
if(tab.active && tab !== selectedTab)
tab.active = false;
})
selectedTab.active = true;
}
},
link:function(scope,elem,attr,optionsCtrl){
console.log(scope.newvar)
scope.resetInput = function(){
console.log("in resetInput")
optionsCtrl.firstBox = "e"
scope.item = "";
}
}
}
})
tabset template
<ul class="nav nav-tabs" ng-class="'{{newvar}}'" >
<li class='' ng-repeat="tab in tabset.tabs" >
<a href="" ng-click="tabset.click(tab);resetInput();" ng-class='{"tab-active":tab.active,"tab-inactive":tab.active === false}'> {{tab.heading}}</a>
</li>
</ul>
<ng-transclude>
</ng-transclude>

As you are using bindToController: true you could access you controller scope from link function 4th parameter which is controller, which will give you access to the directive controller this which is using controllerAs syntax in it.
link: function(scope, elem, attr, ctrl) {
console.log(ctrl.newvar);
//ctrl.newvar = attr.newvar;
}
Update
As you want to add class to your tab ul element. I think you shouldn't use ng-class there. ng-class used when conditionally show/hide any class in html. You should use plane {{}} interpolation in you class attribute. While accessing scope variable you need to use tabset. because you are using controllerAs syntax with its alias. I tried to add class with ng-attr-class but the class gets added but other two classes was getting removed nav nav tabs that's the reason behind using {{tabset.newvar}}.
Template
<ul class="nav nav-tabs {{tabset.newvar}}">
<li class='' ng-repeat="tab in tabset.tabs" >
<a href="" ng-click="tabset.click(tab);resetInput();" ng-class='{"tab-active":tab.active,"tab-inactive":tab.active === false}'> {{tab.heading}}</a>
</li>
</ul>
Working Plunkr

You can use scope.tabset to reference the controller and thereby give you access to your variable. Like this:
link: function(scope, elem, attr) {
console.log(scope.tabset.newvar);
}

Related

Use ngAnimate on template in directive

I have this function,
app.directive('movieDetails', MovieDetailsDirectiveFn)
.controller('MovieRowCtrl', ['$scope', '$rootScope', MovieRowCtrlFn]);
function MovieDetailsDirectiveFn() {
return {
restrict: 'E',
scope: {
movie: '='
},
templateUrl: '/tpl.html',
// template: '<div class="movie" style="background-image:url(https://image.tmdb.org/t/p/w1280{{movie.backdrop}})">{{movie.title}}</div>'
}
}
function MovieRowCtrlFn($scope, $rootScope) {
$scope.selectedMovie;
$scope.rowActive = false;
$scope.$on('movieRowActivated', ($event, dataObject) => {
if ($scope.$id != dataObject.targetScopeId) {
$scope.rowActive = false;
}
});
$scope.updateSelectedMovie = function updateVisibleMovieIndexFn(movie) {
$scope.selectedMovie = movie;
$rootScope.$broadcast('movieRowActivated', {targetScopeId: $scope.$id});
$scope.rowActive = true;
console.log ('hello')
}
}
And this html,
<div class="content" ng-repeat="movie in movieGroup" ng-init='$movieIndex = $index'>
<a href='' ng-click='updateSelectedMovie(movie)'>{{ movie.title }}</a>
</div>
<div ng-if='rowActive'>
<movie-details movie='selectedMovie'></movie-details>
</div>
When a user clicks on a button the updateSelectedMovie function triggers and the directive element movie-details is updated with new information.
Check the plunker here http://plnkr.co/edit/Ea1OtRUc1wvC4UNw1sNi?p=preview
What I want to do is animate a fadeOut/fadeIn transition when the movie-details directive element changes content. I've used ngAnimate on ui-router elements, since they get the ng-enter ng-animate classes etc. but that doesn't work with a directive.
For anyone having the same problemn. I "fixed" this by putting a ui-view inside the template that's being loaded by the directive. And fixed a state with it. So now the directive creates a template and goes to another state. The state then places the template inside the freshly created ui-view (from the directive) and now I can animate that element.

How to update Directive on State Changes

I have a root state that defines the overall structure of the Angular template. In the root state, I have the sidebar included that has dynamic menus via directive that changes based on the state. Like this:
.state(‘root', {
abstract: true,
url: ‘/root',
templateUrl: ‘views/root.html',
})
root.html includes the sidebar.html that has dynamic menu called through Directive like this:
sidebar.html
<ul class="nav" id="side-menu">
<li class="nav-header">
<img alt="avatar" ng-src="{{ avatar }}" />
</li>
<!—Dynamic Menus Directive -->
<li sidebar-menus></li>
</ul>
The directive shows the menu based on $state.includes(). But what happens is, the directive shows fine in the first load but it doesn’t update the directive during state changes. To resolve this, I tried the following methods but nothing worked:
Added the $state to scope in Main controller but it still doesn’t change the directive
once it is compiled first.
Tried adding $stateChangeSuccess watcher to trigger recompiling the directive, but it doesn’t
recompile again after the first time (or) maybe it is recompiling but the changes are not seen in the template (this is the code I have now
which I will give below).
Moving the sidebar inside separate child
states instead of having it in root state works, but it beats the
purpose since I am trying to load the overall structure in the root
state first and only refresh the menu sections in subsequent state
changes.
I am not really sure how to approach this. I have a feeling my approach can be out of whack and hoping someone can guide me here. This is my directive code at the moment:
.directive('sidebarMenus', ['$compile', '$state', '$rootScope',
function($compile, $state, $rootScope) {
return {
restrict: 'A',
replace: true,
link: function(scope, element, attrs) {
var state = scope.$state; // Scope from Main Controller
// HTML Template
function contructHtml(state) {
var htmlText = '';
// First Child State
if (state.includes('root.child1')) {
var htmlText = '<li>Child 1 Menu</li>';
}
// Second Child State
if (state.includes('root.child2')) {
var htmlText = '<li>Child 2 Menu</li>';
}
// Third Child State
if (state.includes('root.child3')) {
var htmlText = '<li>Child 3 Menu</li>';
}
$compile(htmlText)(scope, function( _element, _scope) {
element.replaceWith(_element);
});
}
$rootScope.$on('$stateChangeSuccess', function() {
var state = scope.$state; // scope.$state is added in main controller
contructHtml(state);
});
// Initial Load
contructHtml(state);
}
}
}])
You can get rid of the compile business by using template.
You template could look something like this:
<li ng-if="state.includes('root.child1')">Child 1 Menu</li>
<li ng-if="state.includes('root.child2')">Child 2 Menu</li>
<li ng-if="state.includes('root.child3')">Child 3 Menu</li>
So your directive code should look sth like this
return {
restrict: 'A',
replace: true,
template:'<div> <li ng-if="state.includes('root.child1')">Child 1 Menu</li>
<li ng-if="state.includes('root.child2')">Child 2 Menu</li>
<li ng-if="state.includes('root.child3')">Child 3 Menu</li>
</div>'
link: function(scope, element, attrs) {
$scope.state = scope.$state; // Scope from Main Controller
$rootScope.$on('$stateChangeSuccess', function() {
$scope.state = scope.$state; // scope.$state is added in main controller
});
}
}

AngularJS: How create a new directive joining existents directives?

I would like improve my current solution.
I have a big menu using <ul> and <li> tags. I need show to the user only the <li> tags that they have permission to access.
I have resolved this problem using two directives: ng-init and ng-show
...
<li ng-init="ok=hasPemission('item1')" ng-show="ok">
Item 1
</li>
...
I needed to use 'ng-init' to get a stable hasPermission result. I can't use ng-show="hasPemission('item1')" because 'hasPemission' returns a new 'promise' object and the ng-show has problem with not stable expressions ([$rootScope:infdig] infinit $digest loop).
Now, I wanted create a new directive that join this both directives. I created this one but I think that there is a way to reuse these two existents directives.
This is my new directive:
.directive('myPermissionShow',['$q','$animate','AccessControl',
function ($q, $animate, AccessControl) {
return {
restrict:'A',
scope: {
resourceName: '#myPermissionShow'
},
link: function ($scope, $element) {
var user = AccessControl.getLoggedUser();
AccessControl.hasPermission(user,$scope.resourceName).then(
function (value) {
// I copied the line below from ngShow directive:
$animate[value ? 'removeClass' : 'addClass']($element, 'ng-hide');
}
);
}
}
}])
So, my html changed to:
...
<li my-permission-show="item1">
Item 1
</li>
<li my-permission-show="item2">
Item 2
</li>
...
Is there a way to create this new directive reusing the directives 'ng-init' and 'ng-show'?
Something like this:
!!!NOT WORKING CODE!!!
.directive('myPermissionShow',['AccessControl',
function (AccessControl) {
return {
restrict:'A',
replaceAttribute: true, /* this property does not exists... */
template: 'ng-init="val=hasPermission(user,resourceName)" ng-show="val"',
scope: {
resourceName: '#myPermissionShow'
},
controller: function ($scope, $element) {
$scope.hasPemission = AccessControl.hasPermission;
$scope.user = AccessControl.getLoggedUser();
}
}
}])
!!!NOT WORKING CODE!!!

creating a new directive with angularjs

so i'm making a simple directive called "hover", it's a basic nav menu that when you pass a mouse over a specific aba, this aba changes the color. See my script code:
var app = angular.module('myModule', []);
app.directive('hover', function(){
return{
restrict: 'E',
controller: function($scope) {
$scope.hover = null;
$scope.selected = null;
$scope.onHover = function (index){
$scope.hover = index;
}
$scope.mouseLeave = function(){
if($scope.selected)
$scope.hover = $scope.selected;
else
$scope.hover = -1;
}
$scope.onClick = function(index) {
$scope.hover = index;
$scope.selected = index;
}
},
compile: function(el, attrs){
el.children().attr('data-ng-mouseover', 'onHover('+attrs.index+')');
el.children().attr('data-ng-mouseleave', 'mouseLeave()');
el.children().attr('data-ng-click', 'onClick('+attrs.index+')');
el.children().attr('data-ng-class', '{'+ attrs.onhover +': hover == ' + attrs.index + ' || selected == ' + attrs.index + '}');
}
}
});
And now my html:
<ul>
<hover index="0" onhover="hover"><li>Home</li></hover>
<hover index="1" onhover="hover"><li>About Us</li></hover>
<hover index="2" onhover="hover"><li>Contact</li></hover>
<hover index="3" onhover="hover"><li>Share with us!</li></hover>
</ul>
This is working fine, but when i put my html in this way:
<ul>
<li hover index="0" onhover="hover">Home</li>
<li hover index="1" onhover="hover">About Us</li>
<li hover index="2" onhover="hover">Contact</li>
<li hover index="3" onhover="hover">Share with us!</li>
</ul>
This doesn't work, i have to wrap my "li" with "hover" tag to make this work(and yes, i'm changing the restrict property to "A"), why? And another question, wrapping my "li" with "hover" tag is a bad trick to validation my html?
This is my html compiled:
<ul>
<li onhover="hover" index="0" hover="" data-ng-mouseover="onHover()">Home</li>
<li onhover="hover" index="1" hover="" data-ng-mouseover="onHover()">About Us</li>
<li onhover="hover" index="2" hover="" data-ng-mouseover="onHover()">Contact</li>
<li onhover="hover" index="3" hover="" data-ng-mouseover="onHover()">Share with us!</li>
</ul>
When i pass the mouse over these elements, my function: "onHover" doesn't is called.
First a clarification..
I do not recommend overusing $compile, there are better ways for binding event listeners to a scope.
I solved this question for me to learn how compilation works and to share it with others.
what happens when manipulating template element inside a the compile function?
The compilation phase iterates down the DOM , from parent to children element.
When you manipulate children of a DOM element inside a compile function it happens before $compile got down to these children elements to collect their directives, so every change you make to the contents of the template element will be compiled and linked with the continuation of the compilation phase.
This is not the case when you manipulate the template element itself , then $compile will not look for more directives in that same element because it's only collecting directives once per each DOM element.
So these attributes you added are just not being compiled!
Lets $compile it manually:
I tried to add $compile(el) but my browser crashed (don't laugh at me), The reason is it got into a loop where it's infinitely compiling itself.
So I removed the directive attribute and then $compile again.
I set { priority:1001 } and { terminal:true }. This iss needed to prevent other directive compile functions to run before our directive or after out manual compiling.
Solution:
here is a plunker: http://plnkr.co/edit/x1ZeigwhQ1RAb32A4F7Q?p=preview
app.directive('hover', function($compile){
return{
restrict: 'A',
controller: function($scope) {
// all the code
},
priority: 1001, // we are the first
terminal: true, // no one comes after us
compile: function(el, attrs){
el.removeAttr('hover'); // must remove to prevent infinite compile loop :()
el.attr('data-ng-mouseover', 'onHover('+attrs.index+')');
el.attr('data-ng-mouseleave', 'mouseLeave()');
el.attr('data-ng-click', 'onClick('+attrs.index+')');
el.attr('data-ng-class', '{'+ attrs.onhover +': hover == ' + attrs.index + ' || selected == ' + attrs.index + '}');
var fn = $compile(el); // compiling again
return function(scope){
fn(scope); //
}
}
}
});

AngularJS directive only when the condition is true

I am going to have a contextmenu directive in ng-repeat items.
Based on whether a condition is true, the directive should be applied.
How do I put a condition like only when item.hasMenu == true then apply the directive ?
<ul ng-controller="ListViewCtrl" >
<li contextmenu ng-repeat="item in items">{{item.name}} </li>
</ul>
EDIT
This seems to have worked for me. First the directive.
app.directive('menu',function(){
return {
restrict : 'A',
link : function(scope,element,attrs){
if(scope.hasMenu){
element.contextmenu({
menu:[
{title:"Remove" , "cmd" : "remove"},
{title:"Add" , "cmd" : "add"},
],
select:function(event,ui){
//alert("select " + ui.cmd + " on" + ui.target.text());
if (ui.cmd ==='remove'){
alert('Remove selected on ' + scope.item);
}
if (ui.cmd ==='add'){
alert("Add selected");
}
}
});
}
}
}
}
);
Then the html
<ul ng-controller="ListViewCtrl" >
<li menu ng-repeat="item in items">{{item.name}} </li>
</ul>
Can you do something like this, using ng-if?
<ul ng-controller="ListViewCtrl" >
<li ng-repeat="item in items">
<span>{{item.name}}</span>
<div contextmenu ng-if="item.hasMenu"></div>
</li>
</ul>
Here are the docs for ng-if.
EDIT:
If you are driving the context menu off of a class, you should be able to do this:
<ul ng-controller="ListViewCtrl" >
<li ng-class="{'hasmenu': item.hasMenu}" ng-repeat="item in items">{{item.name}} </li>
</ul>
I think this is pretty tricky if you don't want to change your DOM structure. If you could just place your contextmenu directive on a sub DOM node inside the <li> things would be a lot easier.
However, let's assume you can't do that and let's also assume that you don't own the contextmenu directive so that you can't change it to your needs.
Here is a possible solution to your problem that might be a bit hackish (actually I don't know!)
'use strict';
angular.module('myApp', [])
.controller('TestController', ['$scope', function($scope) {
$scope.items = [
{name:1, hasMenu: true},
{name:2, hasMenu: false },
{name:3, hasMenu: true}
];
}])
.directive('contextmenu', function(){
return {
restrict: 'A',
link: function(scope, element){
element.css('color', 'red');
}
}
})
.directive('applyMenu', ['$compile', function($compile){
return {
restrict: 'A',
link: function(scope, element){
if (scope.item.hasMenu){
//add the contextmenu directive to the element
element.attr('contextmenu', '');
//we need to remove this attr
//otherwise we would get into an infinite loop
element.removeAttr('apply-menu');
//we also need to remove the ng-repeat to not let the ng-repeat
//directive come between us.
//However as we don't know the side effects of
//completely removing it, we add it back after
//the compile process is done.
var ngRepeat = element.attr('ng-repeat');
element.removeAttr('ng-repeat');
var enhanced = $compile(element[0])(scope);
element.html(enhanced);
element.attr('ng-repeat', ngRepeat);
}
}
}
}]);
I faked the contextmenu directive to just change the color to red just so that we can see it's taking place.
Then I created an apply-menu attribute directive. This directive than checks if the hasMenu property is true and if so hooks in and adds the contextmenu directive and does a manual $compile process.
However, what worries me a bit about this solution is that I had to temporally remove the ng-repeat directive (and also the apply-menu directive) to get the $compile process to act the way we want it to act. We then add the ng-repeat directive back once the $compile has been made. That is because we don't know the side effects of removing it entirely from the resulting html. This might be perfectly valid to do, but it feels a bit arkward to me.
Here is the plunker: http://plnkr.co/edit/KrygjX
You can do this way
angularApp.directive('element', function($compile) {
return {
restrict: 'E',
replace: true,
transclude: true,
require: '?ngModel',
scope: 'isolate',
link: function($scope, elem, attr, ctrl) {
$scope.isTrue = function() {
return attr.hasMenu;
};
if($scope.isTrue())
//some html for control
elem.html('').show();
else
//some html for control
elem.html('').show();
$compile(elem.contents())($scope);
}
};
});

Resources