Directive scope variable not working with ng-show - angularjs

I have a "home" controller that uses a directive. I want to be able to show/hide a button in the directive template depending on the value of a variable in the controller.
This is what my directive looks like:
angular.module("goleadoresApp").directive("golNavbar", [golNavbar]);
function golNavbar() {
return {
restrict: "EA",
templateUrl: "views/templates/golNavbar.html",
scope: {
pageTitle: "#",
showLockIcon: "#"
}
};
}
And my template:
<div class="bar bar-header bar-balanced">
<h1 class="title">
<i class="gol-logo-icon icon ion-ios-football"></i>
<span ng-bind="pageTitle"></span>
</h1>
<!--For testing purposes: This variable renders as false, however, ng-show in line below doesn't work-->
{{showLockIcon}}
<!--ng-show won't work. Element would still be visible even if showLockIcon is false-->
<a class="button icon ion-locked pull-right second-right-button"
ng-show="showLockIcon"></a>
<a class="button icon ion-help-circled" href="#/help"></a>
</div>
From my view, I'll use the template and pass it a controller variable (canPredictionsBePosted) that holds a value of true or false, and for which I expect that, if true, button will show in the template:
<gol-navbar page-title="{{matchWeek}}"
show-lock-icon="{{canPredictionsBePosted}}"">
</gol-navbar>
When running this code, I can actually see that the value passed to showLockIcon template variable is correct, but the element in the directive template, which has the ng-show, won't work. That means that when I pass it a value of false, the button is still visible.
Any ideas?
Furthermore, if I change the value of the canPredictionsBePosted variable in my home controller, the value of showLockIcon in the template doesn't get updated. How could I get this to work?
Thanks in advance, I'm new to directives, so all help is appreciated.

You need to make two changes.
1) Use = for showLockIcon like below:
scope: {
pageTitle: "#",
showLockIcon: "="
}
2) Remove the curly braces around canPredictionsBePosted in show-lock-icon="{{canPredictionsBePosted}}"
<gol-navbar page-title="{{matchWeek}}"
show-lock-icon="canPredictionsBePosted">
</gol-navbar>

Related

ng-repeat data binding with custom directive

I have a list and on each item in list I am calling a modal window (custom directive) which should have details about that item being clicked , but the data does not change and remains same across each item. Please find the code below.
angular
.module('Testapp')
.directive('testDirective', function () {
return {
restrict: "AE",
templateUrl: "/Apps/templates/mytem/testdir.html",
translucent: true,
scope: {item:'=data'},
link: function (scope, element, attribute) {
console.log(scope.sequence);
}
};
});
Directive
<div class="modal fade" id="modalAddFilters">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body tree">
{{item}}
</div>
</div>
</div>
</div>
Calling Template
<div>
<div ng-repeat="items in TestList>
<test-Directive id="directive_modalAddFilters" data="items"></test-Directive>
</div>
I am able to see the data correctly loaded in DOM but directive template doesnt change the data.
You code works fine, except that you forget to close you ng-repeat with a quotation mark.
I think you just didn't properly resolved you data into the modal view.
I have made a plunk based on your (partial) code, I added a modal and everything works fine. I've used ui-bootstrap to show the modal with the repeated data injected.

Hide/show an element in Angularjs using custom directive, ng-show and $scope

When a link is clicked in the app navigation a dropdown with ui-view content shows below each respective link.
The HTML:
<div class="sc-dash-header">
<ul>
<li>
<a class="navbar-brand" show-nav-popup href="">download</a>
<div id="nav-download-progress" class="dash-hdr-popup" ng-show="showPopup">
<div ui-view="hdr-download-progress"></div>
</div>
</li>
<li>
<a class="navbar-brand" show-nav-popup href="">add</a>
<div id="nav-add" class="dash-hdr-popup" ng-show="showPopup">
<div ui-view="hdr-add-content"></div>
</div>
</li>
<li>
<a class="navbar-brand" show-nav-popup href="">enter pin</a>
<div id="nav-unlock" class="dash-hdr-popup" ng-show="showPopup">
<div ui-view="hdr-unlock"></div>
</div>
</li>
</ul>
</div>
I've included an ng-show attribute to open the dropdown when $scope.showPopup is set to true.
To achieve this I've created a custom directive with an on click called show-nav-popup.
The JS:
.directive('showNavPopup', function () {
return {
restrict: 'A',
// scope: {},
link: function(scope, el, attrs) {
el.on('click', function(){
scope.$apply(function () {
scope.showPopup = true;
});
console.log(scope);
});
}
};
});
The above works, but the dropdown opens on each element.
Question: I need to isolate the scope, so on each click, only the respective dropdown appears. I uncomment the line // scope: {} - but this doesn't work..
Angularjs n00b here - any help would be much appreciated
Having an isolate scope in this situation wouldn't fix the problem. There are a ton of ways to achieve what you want though. One of which is to assign each show-popup-nav an id, turn $scope.showPopup into an array, and keep an individual true/false for each id. Then for each ng-show, you look at the index corresponding to each id for the true/false value.
I coded it up on that guy's Plunker, working as you expect: http://plnkr.co/edit/CSikLIiuPNT9dfsfZfLk
EDIT: I should say, you COULD use an isolate scope to fix this, but that would require a lot of changes to your DOM, as the ng-show directive is a sibling to your show-popup-nav, and not a child.
When you create the isolate scope, the scope applies to the element that your directive is applied to, and it's child elements. In this case that's just the anchor tag:
<a class="navbar-brand" show-nav-popup href="">download</a>
You are using an ng-show on a tag that is a sibling to the anchor tag:
<div id="nav-download-progress" class="dash-hdr-popup" ng-show="showPopup">
The sibling is not part of the isolate scope, and so it never notices that the value of showPopup has changed.
The ng-show would work if it were applied to a DOM element that was a child of the anchor tag.
EDIT
One way to make this work would be to wrap your two siblings in a parent tag, and use the directive on the parent:
<div show-nav-popup>
Download
<div ng-show="showPopup"></div>
</div>
Then you'd need to modify your directive's code to find the anchor tag and apply the click handler.
You might instead try a completely different approach as suggest in the other answer by #Bill Bergquist

angular trasnclude always adds as child, sibling required

I would like to do the transclude equivalent of element.insertAfter. What I am getting is basically element.appendChild. I would like the new element to share the same parent as the directive's element.
I would like
<div>
<input with-button></input>
</div>
to become
<div>
<input></input>
<button></button>
</div>
but I get instead
<div>
<input>
<button></button>
</input>
</div>
My directive's template looks like
<ng-transclude></ng-transclude>
<button></button>
and the directive looks like
angular
.module('appy')
.directive('withButton', withButton);
function withButton() {
return {
restrict: 'A',
transclude: true,
templateUrl: 'path/to/template'
};
}
According to the docs this should work. What am I missing?
First thing, everything you declare as directive template will be put inside the element on which the directive is used. So in your case your template:
<ng-transclude></ng-transclude>
<button></button>
Will go inside the:
<input with-button></input>
So, no wonder the button finishes inside your input.
Second thing, setting { transclude: true } means "get everything from inside the element on which this directive is applied, and put it inside the element in the directive template on which ng-transclude is used", in this case nothing since there is nothing inside
<input with-button></input>
(it has no children).

When to use transclude 'true' and transclude 'element' in Angular?

When should I use transclude: 'true' and when transclude: 'element' ?
I cant find anything about transclude: 'element' in the angular docs, they are pretty confusing.
I would be happy if someone could explain this in simple language.
What is the benefit of each option? What is the real difference between them?
This is what I have found :
transclude: true
Inside a compile function, you can manipulate the DOM with the help of transclude linking function or you can insert the transcluded DOM into the template using ngTransclude directive on any HTML tag.
and
transclude: ‘element’
This transcludes the entire element and a transclude linking function is introduced in the compile function. You can not have access to scope here because the scope is not yet created. Compile function creates a link function for the directive which has access to scope and transcludeFn lets you touch the cloned element (which was transcluded) for DOM manipulation or make use of data bound to scope in it. For your information, this is used in ng-repeat and ng-switch.
From AngularJS Documentation on Directives:
transclude - compile the content of the element and make it available to the directive. Typically used with ngTransclude. The advantage of transclusion is that the linking function receives a transclusion function which is pre-bound to the correct scope. In a typical setup the widget creates an isolate scope, but the transclusion is not a child, but a sibling of the isolate scope. This makes it possible for the widget to have private state, and the transclusion to be bound to the parent (pre-isolate) scope.
true - transclude the content of the directive.
'element' - transclude the whole element including any directives defined at lower priority.
transclude: true
So let's say you have a directive called my-transclude-true declared with transclude: true that looks like this:
<div>
<my-transclude-true>
<span>{{ something }}</span>
{{ otherThing }}
</my-transclude-true>
</div>
After compiling and before linking this becomes:
<div>
<my-transclude-true>
<!-- transcluded -->
</my-transclude-true>
</div>
The content (children) of my-transclude-true which is <span>{{ something }}</span> {{..., is transcluded and available to the directive.
transclude: 'element'
If you have a directive called my-transclude-element declared with transclude: 'element' that looks like this:
<div>
<my-transclude-element>
<span>{{ something }}</span>
{{ otherThing }}
</my-transclude-element>
</div>
After compiling and before linking this becomes:
<div>
<!-- transcluded -->
</div>
Here, the whole element including its children are transcluded and made available to the directive.
What happens after linking?
That's up to your directive to do what it needs to do with the transclude function. ngRepeat uses transclude: 'element' so that it can repeat the whole element and its children when the scope changes. However, if you only need to replace the tag and want to retain it's contents, you can use transclude: true with the ngTransclude directive which does this for you.
When set to true, the
directive will delete the original content, but make it available for reinsertion within
your template through a directive called ng-transclude.
appModule.directive('directiveName', function() {
return {
template: '<div>Hello there <span ng-transclude></span></div>',
transclude: true
};
});
<div directive-name>world</div>
browser render: “Hello there world.”
The best way of think about transclusion is a Picture Frame.A picture frame has its own design and a space for adding the picture.We can decide what picture will go inside of it.
When it comes to angular we have some kind of controller with its scope and inside of that we will place a directive that supports transclusion. This directive will have it’s own display and functionality . In non-transluded directive, content inside the directive is decided by the directive itself but with transclusion,just like a picture frame,we can decide what will be inside the directive.
angular.module("app").directive('myFrame', function () {
return {
restrict: 'E',
templateUrl:"frame.html",
controller:function($scope){
$scope.hidden=false;
$scope.close=function(){
$scope.hidden=true;
}
},
transclude:true
}
});
Content inside the directive
<div class="well" style="width:350px;" ng-hide="hidden">
<div style="float:right;margin-top:-15px">
<i class="glyphicon glyphicon-remove" ng-click="close()" style="cursor:pointer"></i>
</div>
<div ng-transclude>
/*frame content goes here*/
</div>
</div>
Call Directive
<body ng-controller="appController">
<my-frame>
<span>My Frame content</span>
</my-frame>
</body>
Example

AngularJS - Access isolated scope in directive's link function

I'm giving a first try at AngularJS custom directives.
I'm having trouble using (or understanding ...) the isolated scope in the link function of the directive.
Here is the code of this part of my app :
view.html
...
<raw-data id="request-data" title="XML of the request" data="request">See the request</raw-data>
...
request is a variable published in the scope of the viewCtrl that contains the xml-string of a request.
rawData.js
directives.directive('rawData', function() {
return {
restrict : 'E',
templateUrl : 'partials/directives/raw-data.html',
replace : true,
transclude : true,
scope : {
id : '#',
title : '#',
data : '='
},
link : function($scope, $elem, $attr) {
console.log($scope.data); //the data is correclty printed
console.log($scope.id); //undefined
}
};
});
raw-data.html
<div>
<!-- Button to trigger modal -->
<a href="#{{id}}Modal" role="button" class="btn" data-toggle="modal" ng-transclude></a>
<!-- Modal -->
<div id="{{id}}Modal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="{{id}}Modal" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 id="myModalLabel">{{ title }}</h3>
</div>
<div class="modal-body">
<textarea class="input-block-level" rows="10">{{ data }}</textarea>
</div>
<div class="modal-footer">
<!-- <button class="btn" ng-click="toggleTagText('')">{{'cacher'}} l'image</button> -->
<button class="btn btn-primary" data-dismiss="modal" aria-hidden="true">Fermer</button>
</div>
</div>
</div>
I don't understand why the ID is correclty shown when the modal pops, but when I try to console.log() it, its value is undefined.
Maybe i'm wrong with the isolated scope value (= and #).
Thank you for reading. :)
Isolate scope properties bound with # are not immediately available in the linking function. You need to use $observe:
$attr.$observe('id', function(value) {
console.log(value);
});
Your template works properly because Angular automatically updates isolate scope property id for you. And when it does update, your template automatically updates too.
If you are just passing strings, you can simply evaluate the values once instead of using # binding:
link: function($scope, $elem, $attr) {
var id = $attr.id;
var title = $attr.title
console.log(id, title);
}
However, in your case, since you want to use the properties in templates, you should use #.
If you weren't using templates, then # is useful when attribute values contain {{}}s – e.g., title="{{myTitle}}". Then the need to use $observe becomes more apparent: your linking function may want to do something each time the value of myTitle changes.
This is intentional and has to do with compilation order and the difference between '#' and '='.
Some excerpts from this Google Groups discussion with input from Misko:
# and = do very different things. One copies the attribute value
(which may be interpolated) and the other treats the attribute value
as an expression.
#attrs are not $interpolated until later, so they are not available at
link time. If you want to do something with them in the link function
you either need to $interpolate yourself manually
well, none of the answers above actually mentioned one key aspect, even with '=', it doesn't seem to me you can access the scope inside link function directly like the following,
scope: {
data: '=',
},
link: function(scope, elem, attrs) {
console.debug(scope.data); // undefined
but you can access the scope in the internal function,
link: function(scope, elem, attrs) {
scope.saveComment = function() {
console.debug(scope.data);
so it seems to me there might be a time lag in when scope.data can be available.
You can visit the JSFiddle created by me here: http://jsfiddle.net/7t984sf9/5/:
link: function($scope, $elem, $attr) {
console.log($scope.onewayObject);
$attr.$observe('onewayObject', function(value) {
console.log(value);
});
}
Or some more detailed explanation here: What is the difference between & vs # and = in angularJS

Resources