Arturo Rojas
Arturo Rojas

Reputation: 181

ion-list does not refresh after $state.go is called

I am learning AngularJS and ionic framework using this tutorial:

http://www.htmlxprs.com/post/12/tutorial-on-using-parse-rest-api-and-ionic-framework-together

Everything works well up until the point when I create a new item in the createTodo state and then call $state.go('todos') to go back to my items list, here is the code for the create Todo Controller:

.controller('TodoCreateController', ['$scope', 'Todo', '$state', function($scope, Todo, $state) {
    $scope.todo = {};

    $scope.create = function() {
        Todo.create({content: $scope.todo.content}).success(function(data) {
            $state.go('todos', {}, {reload: true});
        });
    }
}])

Here is the code for the item list controller:

.controller('TodoListController', ['$scope', 'Todo', '$state', function($scope, Todo) {
    Todo.getAll().success(function(data) {
        $scope.items = data.results;
    });

    $scope.deleteItem = function(item) {
        Todo.delete(item.objectId);
        $scope.items.splice($scope.items.indexOf(item), 1);
    };
}])

Here are the states configured

.config(function($stateProvider) {
    $stateProvider.state('todos', {
        url: '/todos',
        controller: 'TodoListController',
        templateUrl: 'views/todos.html'
    }).state('createTodo', {
        url: '/todo/new',
        controller: 'TodoCreateController',
        templateUrl: 'views/create-todo.html'
    });
})

When the application starts, the method of the TodoListController is invoked thanks to the last line added and the end of the .run method in the main app.js (or at least thats my understanding):

.run(function($ionicPlatform, $state) {
    $ionicPlatform.ready(function() {

        if(window.cordova && window.cordova.plugins.Keyboard) {
            cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
        }
        if(window.StatusBar) {
            StatusBar.styleDefault();
        }
    });

    $state.go('todos');
})

My problem is that, as soon as the new item is created and I invoke $state.go('todos') from the TodoCreateController, it takes me back to the item list but the new item is not there and the method of the TodoListController is never invoked, therefore leaving the list outdated.

How can I refresh the list in the 'todos' state after a new item has been created?

Upvotes: 6

Views: 10379

Answers (4)

hgoebl
hgoebl

Reputation: 13007

I've encountered this and similar problems and it turned out that ionic view caching often was the reason for not calling the controller code when returning to a cached view.

Possible Solution

When separating controller code into one-time-initialization and code running when view is entered, you typically end with controllers looking like this:

// one-time initialization
$scope.updateList = function() {
    Todo.getAll().success(function(data) {
        $scope.items = data.results;
    });
};

$scope.$on('$ionicView.beforeEnter', function () {
    $scope.updateList();
});

This should solve your problem without having to trigger events or adding params or data to the route.

Alternative

It is possible to disable view caching in different ways:

  • add cache: false to the state definition
  • add cache-view="false" to the html <ion-view ...>
  • disable caching with $ionicConfigProvider.views.maxCache(0); (wouldn't recommend this)

Pitfall when using Sub-States

Not in your case, but if editing/creating rows in Sub-States (e.g. states todo for the list and todo.create for creating a new todo), there is an additional problem coming from ui-router:

When navigating from todo.create to state todo, your super-state todo won't be "executed", because you're already in this state when coming from todo.create. This is a common problem and can be solved by one of the more complicated solutions provided in other answers to this post.

Upvotes: 3

pitxon_net
pitxon_net

Reputation: 104

This did the trick for me:

$state.transitionTo('new-state', $state.$current.params, {reload: true});

Upvotes: 2

Arturo Rojas
Arturo Rojas

Reputation: 181

I found a rather elegant solution:

Inside my TodoListController I define an event listener like this:

$rootScope.$on('todo:listChanged', function() {
    $scope.updateList();
});

then comes the updateList() method

$scope.updateList = function() {
    Todo.getAll().success(function(data) {
        $scope.items = data.results;
    });
};

And finally in the TodoCreateController I $emit the event upwards as soon as the item is created and right before changing state

$scope.create = function() {
    Todo.create({content: $scope.todo.content}).success(function(data) {
        $scope.$emit('todo:listChanged');
        $state.go('todos');
    });
};

And voila! The list updates accordingly and I can now use the same event if I delete or update a list item

Upvotes: 12

Jacob Carter
Jacob Carter

Reputation: 711

My suggestion:

In your config add:

$stateProvider.state('todos', {
    url: '/todos',
    controller: 'TodoListController',
    templateUrl: 'views/todos.html',
    params : {
        updated : false
    }
})

Read about params with ui-router here

Then in your $state.go of your create todo I would change it to this:

$scope.create = function() {
    Todo.create({content: $scope.todo.content}).success(function(data) {
        $state.go('todos', {updated: true});
    });
}

In your todo controller I would add this

if($stateParams.updated == true){
    Todo.getAll().success(function(data) {
        $scope.items = data.results;
    });
    $stateParams.updated = false;
}

This way rather than counting on the reload to run the function you are explicitly looking for a value that will trigger the reload. You can also do the same thing using the data object rather than the params object and set/retrieve values via:

$state.get('createTodo').data.updated = true;
if($state.current.data.updated == true);

Hope this helps!

Another Thought

add a state change listener to the mix:

$rootScope.$on('$stateChangeSuccess', function(e, toState, toParams, fromState, fromParams) {
    if(fromState.name === 'createTodo' && toParams.updated == true){
        Todo.getAll().success(function(data) {
            $scope.items = data.results;
        });
        $stateParams.updated = false;
    }
}

Upvotes: 2

Related Questions