Angular directive link function called twice - angularjs

In my angular app, directives are working fine during the first visit, but once a page been visited twice, all the directive link function gets called twice too. Say I am on page A, click a link to go to page B and then back to page A, all directives on page A will execute the their link function twice. if I refresh the browser it will become normal again.
Here is an example where the console.log will output twice when the second visit.
#app.directive 'testChart', ["SalesOrder", (SalesOrder) ->
return {
scope: {options: '='}
link: (scope, elem, attrs) ->
console.log("............checking")
SalesOrder.chart_data (data) ->
Morris.Line
element: "dash-sales"
data: data
xkey: 'purchased_at'
ykeys: ['total']
labels: ['Series a']
}
]
Any idea?
Update
My Route
when("/dash", {
templateUrl: "<%= asset_path('app/views/pages/dash.html') %>",
controller: DashCtrl
}).
so my chart is duplicated

also make sure you are not including your directive in your index.html TWICE!

I had this exact same problem.
After a loooooong time digging around I found that I hadn't used the correct closing tag which resulted in the chart being called twice.
I had
<line-chart><line-chart>
instead of
<line-chart></line-chart>

The link() function is called every time the element is to be bound to data in the $scope object.
Please check if you are fetching data multiple times , via GET call. You can monitor the resource fetching via Network tab , of chrome debugger.
A directive configures an HTML element and then updates that HTML subsequently whenever the $scope object changes.
A better name for the link() function would have been something like bind() or render(), which signals that this function is called whenever the directive needs to bind data to it, or to re-render it.

Maybe this will help somebody...
I had a problem with directive transclude, I used a transclude function which was adding child elements and also at the same time I forgot ng-transclude in directive template. Child elements were also directives and their link function was called twice!
Spent some time on this one..
More in details:
I had a "main" directive and "child" directives, idea was to use one inside another, something like that:
main
child
child
So problem was that link of "child" directive was called twice, and I didn't understand why,
Turned out I had ng-transclude in "main" directive template (I am posting it as it is in PUG format, sorry for that):
md-card(layout-fill)
md-card-content(flex)
.map-base(id="{{::config.id}}", layout-fill)
ng-transclude
and also in link function of "main" directive I called transclude function:
link: function($scope, $element, $attrs, controller, transcludeFn) {
$element.append(transcludeFn());
}
I think I just tried different combinations and forgot about that, visually everything was ok, but link was called twice and code was running twice and logic was broken..
So problem is that you can't have both and you have to choose one of the ways.
Hopefully now it is more clearer.

In my case I had a main-nav and sub-nav that both called a directive by its name attribute. Since the first instance already set the scope needed the second sub-nav the 2nd call wasn't needed. Incase anyone has a similar issue.

Related

Get element from child directive in parent one

How I can get element with class .second from second directive in link function of first directive?
http://plnkr.co/edit/PiCyZzgvdwAuOyNKOi9E?p=preview
P.S it works with template in link functions, but I need templateUrl.
Since you are using templateURL, the HTML need to be "downloaded" and then it is an asynchronous event which takes some time.
You could work around that with a timeout, but that wouldn't be clean as you don't know how long to wait.
One option, if <second> is supposed to always be inside <first>, is to run a callback from it once it is ready, from its own link function:
{
restrict: "E",
templateUrl:'second.html',
link: function(scope) {
scope.onSecondReady();
}
}
Check this plunkr for the full code:
http://plnkr.co/edit/NLdWaL8zRYkroGC7ZkYL?p=preview
Now if <second> is supposed to be re-usable in different context, and not always within the first one, then you need to use events. Once second is ready, you use scope.$emit('second-is-ready') and from the first one you listen to it scope.$on('second-is-ready')

Angular Directive not executing on UI Bootstrap Modal open

I have a one-page site that I am building out and this is my first time using Angular on a site. Building it on top of Laravel too for the backend but that is beyond the scope of this question.
I need to be able to open a modal on a main page view which will add a new resource (e.g. a new client) or edit a resource. I want to somehow get the form's html inside the modal body when the $uibModal.open()'s controller is called and set the $scope.modalBody equal to the injected items.modalBody (the only way this works is if I use:
$scope.modalBody = $sce.trustAsHtml(items.modalBody);
The only problem now is that anything inside the HTML body, Angular will not use it's magic and do any data-binding. It is still in the raw form of
{{ object.property }} or since I'm using Laravel and avoiding conflict with the Blade template engine:
<% object.property %>
See screenshot:
screenshot
I have been banging my head against the wall on this one...I have tried putting $scope.$apply() in my directive and my controller, neither of which worked. I have a feeling that is the source of my problem though. I have also tried making the html just a <new-client></new-client> directive and using templateUrl: 'views/clients/add.php' which would be ideal, but the template is not being included inside the <new-client></new-client>.
I'm using ui-bootstrap 0.14.3 and Angular 1.4.8.
Could this be a bug? Or am I doing something wrong? Anyone have a better way of getting a form into my modal? Let me know what code you want to see so I don't clutter this post with unnecessary code blocks.
I have come across a similar issue with using jQuery's AJAX to receive template strings and append it to a server.
So when HTML is added via jQuery, bound html string, etc., angular doesn't know it needs to automagically compile this data.
What you need to do is use the $compile service, to $compile your html and then attach the correct $scope to it:
`$compile('jQuerySelectorReturningHtmlOrAnHTMLStringThatNeedsToBeCompiled')($scope);`
There are multiple examples in Angulars Documentation for $compile that can give you an idea of what is happening. I think by what you have described the same thing is happening here in your situation.
The key is to call this $compile service function after the html has been bound to the page.
EDIT:
There are a few other options based on some comments, that will serve as a viable solution to rendering this content on your view. For example a directive that takes a string attribute representing the HTML string of your desired view.
1. Modify your directive template in the compile step:
You have the ability to modify your template before the directive compiles and binds any attributes to it, to that directives scope:
app.directive('myAwesomeCompileStepDirective', [myAwesomeCompileStepDirectivef]);
function myAwesomeCompileStepDirectiveFn() {
return {
restrict: 'EA',
compile: function compileFn(tAttrs, tElement) {
//Here you can access the attrs that are passed into your directive (aka html string)
tElement.html(tAttrs['stringThatYouWantToReplaceElementWith']);
return function linkFn(scope, element, attrs, controller, transcludeFn) {
//if all you want to do is update the template you really don't have to do anything
//here but I leave it defined anyways.
}
}
}
}
You can view a file I wrote for a npm component which uses this method to modify my directive template before it is compiled on the page & you can also view the codepen for the complete component to see it in action.
2. Use $compile service to call $compile in link function using directive attrs.
In the same way as the aforementioned method, you can instead inject the $compile service, and call the function mentioned above. This provides a bit more work, for you but more flexibility to listen to events and perform scope based functions which is not available in the compile function in option 1.

Angular: running function in controller when all directives are loaded

I'm trying to come up with some code which allows me to run a function in the controller but only once the whole dom is setup and ready (including the directives link function run etc.).
I'm currently communicating between ctrl/service and the directive via $rootScope broadcasts. The first broadcast at the time of the controller loading is not being picked up by the directive. The reason is of course that the controller loads before the directive link function runs. I've read a few similar questions on SO where people recommended on using $timeout for these calls. This unfortunately doesn't always work and I don't want to clutter my ctrl/services with lots of $timeout calls. Therefore I'm looking for another solution to my problem.
Communication pattern is as follows:
1.) Controller tells Service to prepare some data (via function call in service)
2.) Service tells directive to display data (via broadcast)
3.) Directive displays data ...or not in my case :(
EDIT:
As timing is essential in my app, I'm basically looking for a way to initiate a function in the controller as soon as all angular components have finished loading. That function in the controller will display content by assigning a value to a scope variable. At the same time it will start taking the time. I can of course only start doing that once the directives are loaded, otherwise the tmining is wrong or the directive is not yet ready to display content etc.
I've read through a blog post by Ben Nadel, which basically shows how directives are loaded. I was hoping to setup an outer directive which loads last so I can trigger the finished loading from there. Unfortunately that doesn't work as soon as any of the inner directives use a templateUrl.
http://www.bennadel.com/blog/2603-directive-controller-and-link-timing-in-angularjs.htm
Using $timeout would be terrible. Don't do that. You can't define how long a server call is going to take.
I would recommend using this pattern:
Have the controller use a service to load some data, and have the
promise in the controller assign the return data to a scope variable.
Pass that scope variable into your directive.
Setup a watch in the directive link function, when it loads it will go from undefined to desired value. Done!
// in your controller
YourService.all().then(function(data) {
$scope.data = data;
});
// in your view
<some-directive your-data="data"></some-directive>
// in your directive
angular.module('blah.directives').directive('someDirective', function() {
return {
scope: {
yourData: '='
},
link: function(scope, element, attrs) {
var watcher = scope.$watch('yourData', function() {
if(scope.yourData === undefined) return;
// at this point your data will be loaded, do work
// optionally kill watcher at this point if it's not going to update again
watcher();
});
}
}
});

Why element is not available in directive's link function

You have probably heard it as many times as I have. "Do all your DOM manipulation in directives". But no one ever seems to say what could happen if you actually do DOM manipulation outside a directive in Angular.
I have a problem that I managed to reproduce in this Plunk
I have made a very simple directive that just outputs the element to the console.
app.directive('dirre', function(){
return {
link: function(scope, element, attrs){
console.log({message:"dirrens linkFn", element: element, count: element.length})
}
}
});
I have two identical jquery UI accordions, the only difference is the way they are called. One is called in a controller and the other one in a directive. Calling accordion from a controller is of course something bad.
As you can see if you run the application there is a situation where one of the dirre-directives does not seem to have an element but there are no errors.
The same thing happens in a big application I'm working with right now. The problem seems to be that someone in our team decided to call Jquery UI's accordion in a controller and not in a directive.
I haven't been able to step through the code to see what actually happens but I strongly suspect that the DOM is modified while Angular is compiling and something goes wrong.
Is this a plausible explanation?
Is this an example of what can go wrong if you do DOM manipulations outside a directive?
The controller and the directive links function are called asynchronously.
Usually you can see directives being built before the main Controller complete. When the controller terminates, the directives update their watched variable (ngModel, $watch(something)...). Basically this is done with promises.
The link/compile function however is not called again. You have to compile, watch, apply the new DOM. Which basically means writing the similar code to angularjs.

AngularJS - inner directive - Order of postLink function calls change when I am replacing template property with templateUrl property

I have a weird situation.
If I have an outer directive that contains 2 directives
inner-directive1 - has a template defined.
inner-directive2 - has a templateUrl defined.
The inner-directive1 postLink function is being called before the outer-directive postLink function - as expected.
But, the inner-directive2 poslink function is being called after the outer-directive postLink - NOT as expected.
The calls to the postLink function are : inner-directive1, outer-directive, inner-directive2 and I was expecting : inner-directive1, inner-directive2, outer-directive.
The template for the outer directive is:
<div ng-transclude><div inner1></div><div inner2></div></div>
please look at the JsFiddle
Does anyone know the reason why?
And is there a way I can make it work as it is expected ?
JSFiddle - Please look at the console log.
Thanks,
Ben
Here's why, from the Angular directive docs(http://docs.angularjs.org/guide/directive):
templateUrl - Same as template but the template is loaded from the specified URL. Because the template loading is asynchronous, the compilation/linking is suspended until the template is loaded.
So that particular directive stops linking until your template is loaded. During that time your other directives jump in and run.
If the timing of you link function is critical, you'll need to include the template directly instead of as a templateUrl. Unless someone can come up with a cool way around this.
Not sure of the exact use-case, but I ran into this issue when trying to use element[0].querySelector('#dynamicId');
This would result in "null" in this case since the link function was being executed from input 1 after outer link.
The fix was rather simple, in the outer directive's link function, wrap the code using element.ready():
element.ready(function(){
var item = element[0].querySelector('#dynamicId');
item.bind('blur', function(){
alert('blur')
});
})
This allows me to find the dynamic element and attach any events even if it's being async loaded from the template url.

Resources