10

I'm using angularJS to build a simple single page application using AJAX, but I'm running into a problem when users use the native back button.

angular.module('myApp', ['ionic', 'myApp.controllers', myApp.services])

.config(function ($routeProvider, $locationProvider) {

    $routeProvider.when('/home', {
       templateUrl: 'templates/feed.html',
       controller: 'MenuCtrl',
       reloadOnSearch: false
   });

   $routeProvider.when('/checkin/view/:id', {
       templateUrl: 'templates/checkin.html',
       controller: 'MenuCtrl'
    });

    $routeProvider.otherwise({
        redirectTo: '/home'
    });


     $locationProvider.html5Mode(true)

})

UPDATE: moved $http out of controls per feedback, and suggestions:

And my services.js file:

angular.module('myApp.services', [])

 .factory('FeedService', ['$http', function($http){
   var state = {};

  var loadFeed = function(){
       $http({
           method: 'GET',
           url: 'http://api.example',
         }).success(function(data){
        // With the data succesfully returned, call our callback
           state = data.response;
           console.log(data);
        }).error(function(){
           alert("error");
       });
   };

   loadFeed();

   return {
       getState: function(scope){
           return state;
       }
    };
}])

And my controller.js file:

angular.module('myApp.controllers', [])

.controller('MenuCtrl', function($scope, $http, $location, FeedService) { 
    // feedback per second answer
    $scope.items  = FeedService.getState();

})

.controller('CheckinCtrl', function($scope, $routeParams) {
    $scope.checkin_id = $routeParams.id;
    console.log($routeParams.id);
});

My main index.html is setup like this:

 <body ng-app="untappd">
   <div ng-view></div>
 </body>

This works great from navigation from a feed (/home), to a check-in page (/checkin/view/:id) but, once the user hits the native back button on the browser or using window.history.back(), code in the controller is called again, which makes the AJAX call to pull down the user's friend feed. Should I be using ng-show instead of using ng-view for this use case, and create individual "pages" for each content I want to show? I was concerned with the performance, but it appears that I can't get data to persist in each ng-view without it having to re-call the templates. Any guidance here?

UPDATE: I have changed this to incorporate the Best Practices, by adding the $http request at the services level, instead of the controller level. I still run in to the issue of the getState request being fired, before the the AJAX of the $http is complete, thus having an empty state, and nothing to render the $scope.

gregavola
  • 2,519
  • 5
  • 30
  • 47
  • 1
    You could put the call in a service, cache the response and avoid repetitive calls. – Sebastian May 04 '14 at 15:23
  • But how would that work when you want to actually load the page fresh? I would only want to provide a cache result, if the back button is used or pressed. Is there any events I can hook into to determine how the page was navigated to? – gregavola May 04 '14 at 15:26
  • Can't you give the user control over this (e.g. refresh button)? Or expire the data in your service after a given time? – Sebastian May 04 '14 at 15:30
  • 1
    You need to clean up the code before you worry about the issue you're having. Controllers should not be doing $http calls, that is the purpose of a service (services are singletons, controllers are not and go out of scope). Controllers should be used ONLY for setting (writing) to scope. Properly separate the components. Take a look at https://google-styleguide.googlecode.com/svn/trunk/angularjs-google-style.html – mortsahl May 04 '14 at 15:33
  • @mortsahl I am new with AngularJS, so I just did a simple prototype and didn't flesh everything out. Thanks for the feedback, and I will look forward to doing that for a production ready version. – gregavola May 04 '14 at 17:45
  • Prototypes too frequently become production code ... do it correctly from the beginning and you won't have to worry about extensive refactoring. – mortsahl May 04 '14 at 17:47
  • @mortsahl Thanks - but it was not needed here. While I appreciate your feedback and your advise, calling someone out for not building to "spec" while they are learning the language, isn't called for. A simple, "Here is your answer, and you might want to change this", would have perfect here. – gregavola May 04 '14 at 17:54
  • Giving you the answer doesn't help you learn. I gave you the advice and resources you need to learn. – mortsahl May 04 '14 at 17:56
  • @mortsahl this has been updated on the post to move to services.js – gregavola May 04 '14 at 22:15

3 Answers3

5

Yep, the Back button and the the Refresh button are a real pain. You have two choices:

  1. You keep things simple and just allow your state to be fetched for each location change. This treats a user triggered back button click or a refresh as any normal location change. Data re-fetching can be mitigated by http caching.

  2. You maintain your own state on the client and restore it when required, possibly using SessionStorage to keep things clean.

I chose the latter option and it all works just fine for me. See these self answered questions for detailed code examples.

Community
  • 1
  • 1
biofractal
  • 18,963
  • 12
  • 70
  • 116
1

Use a service to keep a singleton state, which exposes functions to get/reload/etc. Here is a very simple example, just to get you started:

.factory('FeedService', ['$http', function($http){
    var state = {};

    var loadFeed = function(){
        $http.get('http://api.example.com').then(function(result){
            state.feed = result.items;                
        });
    };

    // load the feed on initialisation
    loadFeed();

    return {
        getState: function(){
            return state;
        }
    };
}])

.controller('MenuCtrl', ['$scope', 'FeedService', function($scope, FeedService) {        
    // assign the feed container to any scope that you want to use for view
    $scope.cont = FeedService.getState();
})

Again, this is very basic, and is simply showing you how you can use a service to store a persistent state between routes.

Matt Way
  • 32,319
  • 10
  • 79
  • 85
  • This make sense to me - but how those feed get initialized at start up? Even if I add angular.module('myApp', ['ionic', 'myApp.controllers', 'myApp.services']) to the app.js file, loadFeed() never get's called. – gregavola May 04 '14 at 17:53
  • The other piece that I struggle with here, is that loadFeed() get's called, but getState is returned before the AJAX call is completed. – gregavola May 04 '14 at 18:09
  • Are you using promises? – mortsahl May 04 '14 at 23:56
  • @mortsahl I've updated my my code above with the services piece as you suggested, any guides or advice on adding promises to that example? – gregavola May 05 '14 at 01:14
  • $http returns promises for starters. `loadFeed()` gets called as soon as the service is injected somewhere. A container is used to make sure there are no scope binding issues. – Matt Way May 05 '14 at 05:33
0

if your parameter 'id' in the route contains any character that is being decoded during the http request (like ' ' => '%20') then your route will not work. try to perform encodeURIComponent() on your string before using it for the routing.

silver
  • 1,633
  • 1
  • 20
  • 32