AngularJS: Routing runs controller twice - angularjs

I'm trying to build custom radio buttons, and I've discovered that my controllers are being executed twice for each instance. I have not specified the view controller twice. That seems to be the common problem. I'm using angular-routing, and the relevant snippet for that is this:
$routeProvider.when('/:action', {
templateUrl: function (params) {
if (!params.action) params.action = 'Index';
return '_' + params['action'];
}
});
I use ng-controller in the template. The routeChangeSuccessful event (or whatever it's called) fires, and it compiles everything normally, but it seems to follow up with some post link function that also compiles everything; thus the double instances.
What am I doing wrong? How can I avoid the duplicate calls?
Update
I've discovered that it's recompiling the initial view, whatever that was, when routing through AngularJS. I can work around this by adding a secret blank page that is always hit first (I'm developing in an .NET MVC project, so I can control that through the MVC routing), but that seems rather silly.
Why is ngRoute recompiling the initial view every time? Is there an elegant workaround?

Last I checked, ngView doesn't prevent the compilation of the template that is initially fetched, and in fact it recompiles it when the view changes before replacing it and compiling the template fetched by ngRoute.
To resolve this, have only AngularJS requests return templates. Return a blank template otherwise. I handle this by having the AngularJS requests include an underscore prefix. In the MVC routing, if the underscore is there, return the requested partial; otherwise, return the layout with an empty body.

Related

Mutliple controllers are being loaded when switching views (manually compiled)

I'm currently trying to integrate some angular into our MVC application. This is requiring some slightly more in-depth knowledge of how angular compiles the DOM, but it doesn't seem unachievable.
Here's a link to the CodePen
Essentially, I have a bunch of code (that I can't touch) which controls the page being loaded into DOM. This uses JQuery.
What I have is an ng-include that loads in a template, which gives me my 'angularised' DOM. Because this element is loaded in via AJAX, I'm having to manually $compile it when its inserted.
This is works okay until I switch to a different view, and then back again. The controller is instantiated again (as expected), but the previous one is still responding to the event.
I think I need to $destroy the old controller and all its child scopes, but how do I obtain them?
What you were missing is destroying the event listener $scope.$on(notifyRefreshEvent, ... and you do that by doing something like this. Here's your EventService snippet which solves this issue:
app.service('EventService', function($rootScope){
var notifyRefreshEvent = "contact::refresh";
var eventListenerDestroy;
return {
...
}
...
function onContactRefresh($scope, handler) {
eventListenerDestroy = $scope.$on(notifyRefreshEvent, function (e, data) {
eventListenerDestroy(); // this guy destroys it
handler(data);
});
}
});
Also, here's the forked codepen solution

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.

Render initial Angular ng-view on server-side and take it from there

I want to avoid the latency in display of initial JavaScript-rendered views. I want the user to see content immediately and have Angular take it from there. I do not want to just replace this ng-view when Angular ngRoute kicks in as a blink will likely happen. I only want it to replace it once the user hits another route.
Let's imagine this is the base route '/'. This would already exist in my HTML, rendered from the server.
<div ng-view>
<h1>Welcome. I am the first view.</h1>
<p>Please do not replace me until a user has triggered another route.</p>
</div>
I know that a common approach is to have some server-side code in an ng-view and when Angular loads it just replaces it. This is not what I'm looking to do. I want Angular to load and understand that this is actually already my first view.
Any creative ideas as to how to do this? I've looked at the source code- no luck. Maybe even a way to have Angular only replace the HTML if it is different.
Edit:
I am not looking to render templates on the server-side for use as Angular templates. I am looking to render my entire index.html on the server-side, and that would already contain everything the user needs to see for this initial base route.
6-10 seconds on any mobile is quite bad. I wouldn't blame angular here, angular is only 30kb, if that is still too slow, you've chosen wrong framework for task.
Use profiling tools to understand what is going on.
How big is the application you're dealing with?
Can you split the application into sub-applications?
Are you doing minification already for CSS & JS?
Are you lazy loading all your views & controllers?
Are you compressing everything? (gzip)
Anyways, it is possible to do pre-processing on server-side for your index.html
You can do pre-processing using nodejs, for example, and cache the pre-processed index.html.
Your nodejs pre-processor could do (pseudo-code):
function preprocessIndexHtml(queryString) {
if(cached[queryString])) return cached[queryString];
// assume angular.js is loaded
// routeConfiguration is an object that holds controller & url.
var routeConfiguration = $routeProvider.
figureOutRouteConfigurationFor(queryString);
var domTree = $(file('index.html'));
var $rootScope = $injector.get('$rootScope');
// find ng-view and clone it
var yourNgView = $($("attribute[ng-view='']").outerHTML);
// le's get rid of existing ng-view attribute
// and configure new ng-view with templateUrl & controller.
yourNgView.RemoveNgViewAttribute();
yourNgView.AddAttribute("ng-controller", routeConfiguration.controller);
yourNgView.AddAttribute("ng-view", routeConfiguration.templateUrl);
// compile the view & pass the rootScope.
$compile(yourNgView)($rootScope);
// replace the existing dom element with our compiled variant
$("attribute[ng-view='']").replaceHtmlWith(yourNgView);
// we can now cache the resulted html.
return cached[queryString] = domTree.html;
}
ngCloak
The ngCloak directive is used to prevent the Angular html template from being briefly displayed by the browser in its raw (uncompiled) form while your application is loading. Use this directive to avoid the undesirable flicker effect caused by the html template display.
https://docs.angularjs.org/api/ng/directive/ngCloak
<body ng-cloak>
You should read this article http://sc5.io/posts/how-to-implement-loaders-for-an-angularjs-app

Make angular see content created by jquery

I'm working on a large enterprise application, our application uses jQuery, and we would like to concentrate on using angular, we can not instantly remove all jQuery code and therefore we have to deal with things that are already present in the system. One of the most frequent things that are blocking us from using angular is a presence of the following code
$.ajax('/HtmlController', {...}).then(function(html) {
$('someElement').replaceWith(html);
});
we can not remove it for now, because it would break up something in the system. Instead we would like to move to angular smoothly, step by step. And one of the first steps we need to make is apply angular's bindings within html returned and replaced with jQuery. In other words we need to put the following markup into html variable
...
1 + 2 = {{ 1 + 2 }}
...
And we need it to get executed by angular, but unfortunately angular doesn't see the newly injected html, how is it possible to make angular see it?
You would have to compile the element and link it with an scope. Assuming you have an angular page, something like this would work:
$.ajax('/HtmlController', {...}).then(function(html) {
var dom = $('someElement');
var el = angular.element(dom), scope = el.scope(),
$compile = el.injector().get('$compile');
scope.$apply(function(){
dom.replaceWith($compile($(html))(scope));
});
});
Here is a simple example: http://plnkr.co/edit/mRhsZV5DahfCjMWkP2My?p=preview

Combating AngularJS executing controller twice

I understand AngularJS runs through some code twice, sometimes even more, like $watch events, constantly checking model states etc.
However my code:
function MyController($scope, User, local) {
var $scope.User = local.get(); // Get locally save user data
User.get({ id: $scope.User._id.$oid }, function(user) {
$scope.User = new User(user);
local.save($scope.User);
});
//...
Is executed twice, inserting 2 records into my DB. I'm clearly still learning as I've been banging my head against this for ages!
The app router specified navigation to MyController like so:
$routeProvider.when('/',
{ templateUrl: 'pages/home.html',
controller: MyController });
But I also had this in home.html:
<div data-ng-controller="MyController">
This digested the controller twice. Removing the data-ng-controller attribute from the HTML resolved the issue. Alternatively, the controller: property could have been removed from the routing directive.
This problem also appears when using tabbed navigation. For example, app.js might contain:
.state('tab.reports', {
url: '/reports',
views: {
'tab-reports': {
templateUrl: 'templates/tab-reports.html',
controller: 'ReportsCtrl'
}
}
})
The corresponding reports tab HTML might resemble:
<ion-view view-title="Reports">
<ion-content ng-controller="ReportsCtrl">
This will also result in running the controller twice.
AngularJS docs - ngController
Note that you can also attach controllers to the DOM by declaring it
in a route definition via the $route service. A common mistake is to
declare the controller again using ng-controller in the template
itself. This will cause the controller to be attached and executed
twice.
When you use ngRoute with the ng-view directive, the controller gets attached to that dom element by default (or ui-view if you use ui-router). So you will not need to attach it again in the template.
I just went through this, but the issue was different from the accepted answer. I'm really leaving this here for my future self, to include the steps I went through to fix it.
Remove redundant controller declarations
Check trailing slashes in routes
Check for ng-ifs
Check for any unnecessary wrapping ng-view calls (I accidentally had left in an ng-view that was wrapping my actual ng-view. This resulted in three calls to my controllers.)
If you are on Rails, you should remove the turbolinks gem from your application.js file. I wasted a whole day to discover that. Found answer here.
Initializing the app twice with ng-app and with bootstrap. Combating AngularJS executing controller twice
When using $compile on whole element in 'link'-function of directive that also has its own controller defined and uses callbacks of this controller in template via ng-click etc. Found answer here.
Just want to add one more case when controller can init twice (this is actual for angular.js 1.3.1):
<div ng-if="loading">Loading...</div>
<div ng-if="!loading">
<div ng-view></div>
</div>
In this case $route.current will be already set when ng-view will init. That cause double initialization.
To fix it just change ng-if to ng-show/ng-hide and all will work well.
Would like to add for reference:
Double controller code execution can also be caused by referencing the controller in a directive that also runs on the page.
e.g.
return {
restrict: 'A',
controller: 'myController',
link: function ($scope) { ....
When you also have ng-controller="myController" in your HTML
When using angular-ui-router with Angular 1.3+, there was an issue about Rendering views twice on route transition. This resulted in executing controllers twice, too. None of the proposed solutions worked for me.
However, updating angular-ui-router from 0.2.11 to 0.2.13 solved problem for me.
I tore my app and all its dependencies to bits over this issue (details here: AngularJS app initiating twice (tried the usual solutions..))
And in the end, it was all Batarang Chrome plugin's fault.
Resolution in this answer:
I'd strongly recommend the first thing on anyone's list is to disable it per the post before altering code.
If you know your controller is unintentionally executing more than once, try a search through your files for the name of the offending controller, ex: search: MyController through all files. Likely it got copy-pasted in some other html/js file and you forgot to change it when you got to developing or using those partials/controllers. Source: I made this mistake
I had the same problem, in a simple app (with no routing and a simple ng-controller reference) and my controller's constructor did run twice. Finally, I found out that my problem was the following declaration to auto-bootstrap my AngularJS application in my Razor view
<html ng-app="mTest1">
I have also manually bootstrapped it using angular.bootstrap i.e.
angular.bootstrap(document, [this.app.name]);
so removing one of them, it worked for me.
In some cases your directive runs twice when you simply not correct close you directive like this:
<my-directive>Some content<my-directive>
This will run your directive twice.
Also there is another often case when your directive runs twice:
make sure you are not including your directive in your index.html TWICE!
Been scratching my head over this problem with AngularJS 1.4 rc build, then realised none of the above answers was applicable since it was originated from the new router library for Angular 1.4 and Angular 2 at the time of this writing. Therefore, I am dropping a note here for anyone who might be using the new Angular route library.
Basically if a html page contains a ng-viewport directive for loading parts of your app, by clicking on a hyperlink specified in with ng-link would cause the target controller of the associated component to be loaded twice. The subtle difference is that, if the browser has already loaded the target controller, by re-clicking the same hyperlink would only invoke the controller once.
Haven't found a viable workaround yet, though I believe this behaviour is consistent with the observation raised by shaunxu, and hopefully this issue would be resolved in the future build of new route library and along with AngularJS 1.4 releases.
In my case, I found two views using the same controller.
$stateProvider.state('app', {
url: '',
views: {
"viewOne#app": {
controller: 'CtrlOne as CtrlOne',
templateUrl: 'main/one.tpl.html'
},
"viewTwo#app": {
controller: 'CtrlOne as CtrlOne',
templateUrl: 'main/two.tpl.html'
}
}
});
The problem I am encountering might be tangential, but since googling brought me to this question, this might be appropriate. The problem rears its ugly head for me when using UI Router, but only when I attempt to refresh the page with the browser refresh button. The app uses UI Router with a parent abstract state, and then child states off the parent. On the app run() function, there is a $state.go('...child-state...') command. The parent state uses a resolve, and at first I thought perhaps a child controller is executing twice.
Everything is fine before the URL has had the hash appended.
www.someoldwebaddress.org
Then once the url has been modified by UI Router,
www.someoldwebaddress.org#/childstate
...and then when I refresh the page with the browser refresh button, the $stateChangeStart fires twice, and each time points to the childstate.
The resolve on the parent state is what is firing twice.
Perhaps this is a kludge; regardless, this does appear to eliminate the problem for me: in the area of code where $stateProvider is first invoked, first check to see if the window.location.hash is an empty string. If it is, all is good; if it is not, then set the window.location.hash to an empty string. Then it seems the $state only tries to go somewhere once rather than twice.
Also, if you do not want to rely on the app's default run and state.go(...), you can try to capture the hash value and use the hash value to determine the child state you were on just before page refresh, and add a condition to the area in your code where you set the state.go(...).
For those using the ControllerAs syntax, just declare the controller label in the $routeprovider as follows:
$routeprovider
.when('/link', {
templateUrl: 'templateUrl',
controller: 'UploadsController as ctrl'
})
or
$routeprovider
.when('/link', {
templateUrl: 'templateUrl',
controller: 'UploadsController'
controllerAs: 'ctrl'
})
After declaring the $routeprovider, do not supply the controller as in the view. Instead use the label in the view.
In my case it was because of the url pattern I used
my url was like /ui/project/:parameter1/:parameter2.
I didn't need paramerter2 in all cases of state change. In cases where I didn't need the second parameter my url would be like /ui/project/:parameter1/. And so whenever I had a state change I will have my controller refreshed twice.
The solution was to set parameter2 as empty string and do the state change.
I've had this double initialisation happen for a different reason. For some route-transitions in my application I wanted to force scrolling to near the top of the page (e.g. in paginated search results... clicking next should take you to the top of page 2).
I did this by adding a listener to the $rootScope $on $viewContentLoaded which (based on certain conditions) executed
$location.hash('top');
Inadvertently this was causing my routes to be reevaluated and the controllers to be reinitialised
My issue was updating the search parameters like so $location.search('param', key);
you can read more about it here
controller getting called twice due to append params in url
In my case renaming the controller to a different name solved the problem.
There was a conflict of controller names with "angular-ui-tree" module: I renamed my controller from "CatalogerTreeController" to "TreeController" and then this controller starts to be initiated twice on the page where "ui-tree" directive used because this directive uses controller named "TreeController".
I had the same problem and after trying all the answers I finally found that i had a directive in my view that was bound to the same controller.
APP.directive('MyDirective', function() {
return {
restrict: 'AE',
scope: {},
templateUrl: '../views/quiz.html',
controller: 'ShowClassController'
}
});
After removing the directive the controller stopped being called twice. Now my question is, how can use this directive bound to the controller scope without this problem?
I just solved mine, which was actually quite disappointing. Its a ionic hybrid app, I've used ui-router v0.2.13. In my case there is a epub reader (using epub.js) which was continuously reporting "no document found" once I navigate to my books library and select any other book. When I reloaded the browser book was being rendered perfectly but when I selected another book got the same problem again.
My solve was very simple. I just removed reload:true from $state.go("app.reader", { fileName: fn, reload: true }); in my LibraryController
I have the same issue in angular-route#1.6.7, and it because the extra slash in the end of regex route:
.when('/goods/publish/:classId/', option)
to
.when('/goods/publish/:classId', option)
and it works correctly.
Just adding my case here as well:
I was using angular-ui-router with $state.go('new_state', {foo: "foo#bar"})
Once I added encodeURIComponent to the parameter, the problem was gone: $state.go('new_state', {foo: encodeURIComponent("foo#bar")}).
What happened?
The character "#" in the parameter value is not allowed in URLs. As a consequence, angular-ui-router created my controller twice: during first creation it passed the original "foo#bar", during second creation it would pass the encoded version "foo%40bar". Once I explicitly encoded the parameter as shown above, the problem went away.
My issue was really difficult to track down. In the end, the problem was occurring when the web page had missing images. The src was missing a Url. This was happening on an MVC 5 Web Controller. To fix the issue, I included transparent images when no real image is available.
<img alt="" class="logo" src="">
I figured out mine is getting called twice is because i was calling the method twice from my html.
`<form class="form-horizontal" name="x" ng-submit="findX() novalidate >
<input type="text"....>
<input type="text"....>
<input type="text"....>
<button type="submit" class="btn btn-sm btn-primary" ng-click="findX()"
</form>`
The highlighted section was causing findX() to be called twice. Hope it helps someone.

Resources