129

I'm having an issue with changing the URL of the page after a form has been submitted.

Here's the flow of my app:

  1. Routes are set, URL is recognized to some form page.
  2. Page loads, controller sets variables, directives are fired.
  3. A special form directive is fired which performs a special form submission using AJAX.
  4. After the AJAX is performed (Angular doesn't take care of the AJAX) then a callback is fired and the directive calls the $scope.onAfterSubmit function which sets the location.

The problem is that after setting the location the nothing happens. I've tried setting the location param to / as well... Nope. I've also tried not submitting the form. Nothing works.

I've tested to see if the code reaches the onAfterSubmit function (which it does).

My only thought is that somehow the scope of the function is changed (since its called from a directive), but then again how can it call onAfterSubmit if the scope changed?

Here's my code

var Ctrl = function($scope, $location, $http) {
  $http.get('/resources/' + $params.id + '/edit.json').success(function(data) {
    $scope.resource = data;
  });

  $scope.onAfterSubmit = function() {
    $location.path('/').replace();
  };
}
Ctrl.$inject = ['$scope','$location','$http'];

Can someone help me out please?

T J
  • 42,762
  • 13
  • 83
  • 138
matsko
  • 21,895
  • 21
  • 102
  • 144
  • 1
    possible duplicate of [Angular $location.path not working](http://stackoverflow.com/questions/19359553/angular-location-path-not-working) – Jim G. Nov 06 '14 at 01:53
  • Keep in mind that this was created a year before that one. – matsko Nov 06 '14 at 05:13
  • Right and with the benefit of an extra year, the other one has a more precisely correct accepted answer. – Jim G. Nov 06 '14 at 06:35
  • 1
    @JimG. this is not a duplicate, this question is 4 years ago, the one you link, is 2 years ago. – Castro Roy Aug 16 '16 at 21:24

9 Answers9

303

I had a similar problem some days ago. In my case the problem was that I changed things with a 3rd party library (jQuery to be precise) and in this case even though calling functions and setting variable works Angular doesn't always recognize that there are changes thus it never digests.

$apply() is used to execute an expression in angular from outside of the angular framework. (For example from browser DOM events, setTimeout, XHR or third party libraries).

Try to use $scope.$apply() right after you have changed the location and called replace() to let Angular know that things have changed.

T J
  • 42,762
  • 13
  • 83
  • 138
F Lekschas
  • 12,481
  • 10
  • 60
  • 72
  • 4
    To get a better idea of how $apply works with angular and how to work around it's issues, here's a link for that http://www.yearofmoo.com/2012/10/more-angularjs-magic-to-supercharge-your-webapp.html#apply-digest-and-phase – matsko Oct 16 '12 at 20:30
  • I've used this approach with the jQM angular connecter, it works great, but I'm using `$location.search()` in a factory when creating the app, and I've got no `$scope` to use apply on. What would I do in that case? – Pete Oct 19 '12 at 11:57
  • 2
    @Skeater You can simply inject the root scope and operate on it like so: factory('xyz', ['$rootScope', function($rootScope) {...}]) – F Lekschas Oct 23 '12 at 20:12
  • Thanks Flex, I've worked that out now... seems obvious in the end. I should have thought about it for longer but it's so easy to just look on Stack Overflow – Pete Oct 23 '12 at 20:20
  • Here's some more info about a working $location change. http://www.yearofmoo.com/2012/10/more-angularjs-magic-to-supercharge-your-webapp.html#apply-digest-and-phase – matsko Oct 26 '12 at 16:38
  • You can also use `$scope = angular.element(document).scope();` if you're unable to get a `$scope` variable injected. – matsko Nov 02 '12 at 04:11
  • 1
    @Flek can you provide a code example to help make this answer more usefl? Obviously a lot of people are finding it to be correct and helfpul, but I am not seeing the situation. – netpoetica Sep 06 '13 at 14:04
  • 4
    can wrap your callback with $timeout and the scope will be properly applied – JasonS Sep 24 '13 at 05:56
  • 7
    Like JasonS said, use $timeout(function() { //apply changes here }); This has two advantages: 1) You don't need access to $scope. 2) You don't have to worry about $apply alredy running. For The 2nd reason, using $timeout is often the preferred approach. – ArneHugo Aug 22 '14 at 08:19
  • 1
    This worked for me as well. Just wanted to provide another example of how to do this: [$location.path after a certain timeout does not work](http://stackoverflow.com/a/22148845/1667461) – escapedcat Sep 15 '14 at 08:38
  • `apply()` fixed it on the iPad, but broke it on the Desktop with: `Error: [$rootScope:inprog] $apply already in progress`. `timeout()` worked for both. It feels wrong, but at least it works for now :P – escapedcat Sep 15 '14 at 09:07
  • Wow, I just bumped into this with Angular 1.5. After a call to a 3rd party canvas-to-gif library, the `$location.path()` failed to work in my callback. Following it with `$scope.$apply()` did the trick. Thanks mate. – trailing slash Jun 24 '16 at 22:58
  • Using `$scope.$applyAsync( function() { // changes here })` also works perfectly for me, and avoids potential conflict with digests/apply already in progress. – Pierre-Adrien Apr 10 '18 at 09:23
56

Instead of $location.path(...) to change or refresh the page, I used the service $window. In Angular this service is used as interface to the window object, and the window object contains a property location which enables you to handle operations related to the location or URL stuff.

For example, with window.location you can assign a new page, like this:

$window.location.assign('/');

Or refresh it, like this:

$window.location.reload();

It worked for me. It's a little bit different from you expect but works for the given goal.

Bob Kaufman
  • 12,864
  • 16
  • 78
  • 107
Paulo Oliveira
  • 2,411
  • 1
  • 31
  • 47
28

I had this same problem, but my call to $location was ALREADY within a digest. Calling $apply() just gave a $digest already in process error.

This trick worked (and be sure to inject $location into your controller):

$timeout(function(){ 
   $location...
},1);

Though no idea why this was necessary...

Ross R
  • 8,853
  • 7
  • 28
  • 27
  • 5
    Nooo this is a bad idea. Just check for phase incase it's within a digest and then you can skip the apply. Angular will catch up with it and change the URL on its own: https://coderwall.com/p/ngisma – matsko Apr 04 '14 at 15:53
  • Matsco - That makes sense, but I tried that and $$phase already === '$digest', so angular thought it was within a digest, but it never caught up and updated the location. (And note - this had worked in an earlier iteration of my code, so I wonder if there is an angular bug where it isn't catching the change and running another digest in certain instances). I think the $timeout is saying, 'hey - even though I'm in a digest, run another when this ones over even if I don't think its necessary'). Regardless, this worked and $apply (with checking for $$phase) did not. – Ross R Apr 04 '14 at 19:19
  • 3
    timeout seems like a dirty hack to me. If your href contains "#", the $location.path() doesnt work as expected. Just remove entirely href="#" on your a tag or just set to href="" and you wouldnt need to use $timeout – Shankar ARUL Jun 10 '14 at 16:07
  • 7
    $$phase is a private variable that you should not attempt to access because it might change in a new angularjs version. – Blaise Jul 02 '14 at 11:54
  • 7
    Using $timeout might be a hack, but using $$phase is an even bigger hack. Generally, do not view or touch anything prefixed with $$. – nilskp Sep 15 '14 at 04:20
  • 3
    After about 10 hours of random things breaking... this solution worked for me! – Shakeel May 05 '15 at 21:25
  • 1
    @sarul Thanks a bunch, that was what was tripping up my noError state...requires the hash in the HTML call, doesn't work with it in the $location call...go figure. -C§ – CSS Sep 02 '15 at 18:16
6

I had to embed my $location.path() statement like this because my digest was still running:

       function routeMe(data) {                
            var waitForRender = function () {
                if ($http.pendingRequests.length > 0) {
                    $timeout(waitForRender);
                } else {
                    $location.path(data);
                }
            };
            $timeout(waitForRender);
        }
Post Impatica
  • 14,999
  • 9
  • 67
  • 78
  • 1
    Thanks for the function, very useful! – Bill Effin Murray Jul 27 '16 at 21:21
  • for me the problem was we were using angular-block-ui and it was preventing the update of the address bar location. we decorated the $http request with a bool that our requestFilter picked up so that block-ui didn't trigger on that particular call, and then everything was fine. – matao Nov 23 '17 at 07:19
2

In my case, the problem was the optional parameter indicator('?') missing in my template configuration.

For example:

.when('/abc/:id?', {
    templateUrl: 'views/abc.html',
    controller: 'abcControl'
})


$location.path('/abc');

Without the interrogation character the route obviously would not change suppressing the route parameter.

Víctor Hugo
  • 233
  • 1
  • 10
1

If any of you is using the Angular-ui / ui-router, use:$state.go('yourstate') instead of $location. It did the trick for me.

Elferone
  • 364
  • 3
  • 17
0

This wroks for me(in CoffeeScript)

 $location.path '/url/path'
 $scope.$apply() if (!$scope.$$phase)
Fangxing
  • 5,716
  • 2
  • 49
  • 53
0

In my opinion many of the answers here seem a little bit hacky (e.g. $apply() or $timeout), since messing around with $apply() can lead to unwanted errors.

Usually, when the $location doesn't work it means that something was not implemented the angular way.

In this particular question, the problem seems to be in the non-angular AJAX part. I had a similiar problem, where the redirection using $location should take place after a promise resolved. I would like to illustrate the problem on this example.

The old code:

taskService.createTask(data).then(function(code){
            $location.path("task/" + code);
        }, function(error){});

Note: taskService.createTask returns a promise.

$q - the angular way to use promises:

let dataPromises = [taskService.createTask(data)];
        $q.all(dataPromises).then(function(response) {
                let code = response[0];
                $location.path("task/" + code);
            }, 
            function(error){});

Using $q to resolve the promise solved the redirection problem.

More on the $q service: https://docs.angularjs.org/api/ng/service/$q

iwan_gu
  • 43
  • 3
-8
setTimeout(function() { $location.path("/abc"); },0);

it should solve your problem.

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
Akshay
  • 1
  • 1
    Running a non-setTimeout is a very bad idea. And, as @matsko said above, $timeout is not the best idea. – gonzofish Sep 03 '14 at 01:22