AngularJS Paging with $location.path but no ngView reload
Asked Answered
S

7

39

My single page application loads a home page and I want to display a series of ideas. Each of the ideas is displayed in an animated flash container, with animations displayed to cycle between the ideas.

Ideas are loaded using $http:

$scope.flash = new FlashInterface scope:$scope,location:$location

$http.get("/competition.json")
  .success (data) ->
    $scope.flash._init data

However, to benefit from history navigation and UX I wish to update the address bar to display the correct url for each idea using $location:

$location.path "/i/#{idea.code}"
$scope.$apply()

I am calling $apply here because this event comes from outwith the AngularJS context ie Flash. I would like for the current controller/view to remain and for the view to not reload. This is very bad because reloading the view results in the whole flash object being thrown away and the preloader cycle beginning again.

I've tried listening for $routeChangeStart to do a preventDefault:

$scope.$on "$routeChangeStart", (ev,next,current) ->
  ev.preventDefault()
$scope.$on "$routeChangeSuccess", (ev,current) ->
  ev.preventDefault()

but to no avail. The whole thing would be hunky dory if I could figure out a way of overriding the view reload when I change the $location.path.

I'm still very much feeling my way around AngularJS so I'd be glad of any pointers on how to structure the app to achieve my goal!

Sibel answered 14/9, 2012 at 10:23 Comment(3)
I'm late but I just encountered a similar issue. Is there a reason you couldn't put the ideas outside the ng-view directive and give them their own controller, which interacts with the controllers inside ng-view via scope inheritance? I'm mostly asking because this is how I solved my own problem and am wondering if it has any shortcomings, or if I should switch to the accepted answer.Cumberland
If I were solving this problem again today, I'd be very tempted to use ui-router with its hierarchical view capability. I'm not sure that would solve the specific URL problem I had here though, would need more research been a while...!Sibel
OK, thanks for pointing me in that direction, and thanks for responding to such a late comment!Cumberland
E
94

Instead of updating the path, just update query param with a page number.

set your route to ignore query param changes:

....
$routeProvider.when('/foo', {..., reloadOnSearch: false})
....

and in your app update $location with:

...
$location.search('page', pageNumber);
...
Elude answered 18/9, 2012 at 16:29 Comment(3)
Thanks for the tip, Igor. I'd spotted and discounted this approached previously because I'd prefer URL's like '/idea/Acas' rather than '/foo?idea=Acas'. It does, however, have the virtue of working and in the world of pragmatists working trumps perfect!Sibel
Perfect, thanks a lot of this tip. Also I want to notice that if you want to detect the parameter changing you can do it with : $scope.$on("$routeUpdate", function(){ // The search param has changed })Synaeresis
This is the answer! All these other answer I find deal with messing around with $route and $state, this is straight forward and gets the job done!Meunier
S
31

From this blog post:

by default all location changes go through the routing process, which updates the angular view.

There’s a simple way to short-circuit this, however. Angular watches for a location change (whether it’s accomplished through typing in the location bar, clicking a link or setting the location through $location.path()). When it senses this change, it broadcasts an event, $locationChangeSuccess, and begins the routing process. What we do is capture the event and reset the route to what it was previously.

function MyCtrl($route, $scope) {
    var lastRoute = $route.current;
    $scope.$on('$locationChangeSuccess', function(event) {
        $route.current = lastRoute;
    });
}
Spears answered 14/9, 2012 at 17:16 Comment(8)
Thanks for this, it sounds exactly like the solution I was looking for. I've gone through the blog article and had a good play. Unfortunately it doesn't seem to work. I tried placing the code in $routeChangeStart to see if that would make a difference but to no avail. Is there perhaps some witchcraft that I'm missing?Sibel
Another idea: try setting reloadOnSearch=false, docs.angularjs.org/api/ng.$routeProvider See also github.com/angular/angular.js/pull/1151Spears
This is a terrible, terrible hack... but it works. And until Angular provides some way to decouple the location update event from a reload (unfortunately preventing default prevents the url change outright) it's what we're using. And yes, fwiw, reloadOnSearch=false works great if what you need to change is limited to the search params. Our issue was with the hash for bookmarkable tabs.Dogberry
@mark Thanks for this, I was working on an irrelevant project and this made my program work. Thanks!Antione
It looks like this applies to all location changes. So if there are specific links that you want to go through angular routing, they will be broken. Also, the back button will no longer work.Heldentenor
If you only want to do this as a one off call the remove handler in the event eg var removeNoReloadFn = $scope.$on('$locationChangeSuccess', function (e) { ... removeNoReloadFn(); });Gonroff
Just implemented this and works smoothly. As hacky and terrible as it might sound, the implementation was short and didn't break anything (as I feared).Advert
I was stuck in this problem for hours! Thank you so much!Heiress
A
3

My solution was to use the $routeChangeStart because that gives you the "next" and "last" routes, you can compare them without the need of an extra variable like on $locationChangeSuccess.

The benefit is being able to access the "params" property on both "next" and "last" routes like next.params.yourproperty when you are using the "/property/value" URL style and of course use $location.url or $location.path to change the URL instead of $location.search() that depends on "?property=value" URL style.

In my case I used it not only for that but also to prevent the route to change is the controller did not change:

$scope.$on('$routeChangeStart',function(e,next,last){
  if(next.$$route.controller === last.$$route.controller){
    e.preventDefault();
    $route.current = last.$$route;
    //do whatever you want in here!
  }
});

Personally I feel like AngularJS should provide a way to control it, right now they assume that whenever you change the browser's location you want to change the route.

Antiparticle answered 11/2, 2014 at 10:43 Comment(1)
This does not work for me when trying to use this within a controller in AngularJS 1.2.6. I think it is related to $routeChangeStart being a broadcast which is not preventable: https://mcmap.net/q/408979/-event-preventdefault-not-working-for-routechangestart-in-angularjs-appPrettify
O
1

You should be loading $location via Dependency Injection and using the following:

$scope.apply(function () {
    $location.path("yourPath");
}

Keep in mind that you should not use hashtags(#) while using $location.path. This is for compability for HTML5 mode.

Orbicular answered 15/9, 2012 at 12:16 Comment(1)
Thanks for the tip. I think I'm loading $location correctly IndexController = ($scope,$http,$location,$route) -> and I've updated the apply code to be as you suggest. I'm not exactly sure of what difference that makes though, could you elaborate a little?Sibel
W
0

The $locationChangeSuccess event is a bit of a brute force approach, but I found that checking the path allows us to avoid page reloads when the route path template is unchanged, but reloads the page when switching to a different route template:

var lastRoute = $route.current;
$scope.$on('$locationChangeSuccess', function (event) {
    if (lastRoute.$$route.originalPath === $route.current.$$route.originalPath) {
        $route.current = lastRoute;
    }
});

Adding that code to a particular controller makes the reloading more intelligent.

Edit: While this makes it a bit easier, I ultimately didn't like the complexity of the code I was writing to keep friendly looking URL's. In the end, I just switched to a search parameter and angular handles it much better.

Warnerwarning answered 24/3, 2014 at 15:57 Comment(0)
M
0

I needed to do this but after fussing around trying to get the $locationChange~ events to get it to work I learned that you can actually do this on the route using resolve.

$routeProvider.when(
    '/page',
    {
        templateUrl : 'partial.html',
        controller : 'PageCtrl',
        resolve : {
            load : ['$q', function($q) {
                var defer = $q.defer();

                if (/*you only changed the idea thingo*/)
                    //dont reload the view
                    defer.reject('');
                //otherwise, load the view
                else
                    defer.resolve();

                return defer.promise;
            }]
        }
    }
);
Misconceive answered 14/12, 2015 at 3:7 Comment(0)
D
0

With AngularJS V1.7.1, $route adds support for the reloadOnUrl configuration option.

If route /foo/:id has reloadOnUrl = false set, then moving from /foo/id1 to /foo/id2 only broadcasts a $routeUpdate event, and does not reload the view and re-instantiate the controller.

Dday answered 13/1, 2020 at 0:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.