Adam Szabo Adam Szabo - 2 months ago 33
AngularJS Question

AngularJS ui-router $state.go('^') only changing URL in address bar, but not loading controller

I am trying to create a "Todo App" with angularjs

ui-router
. It has 2 columns:


  • Column 1: list of Todos

  • Column 2: Todo details or Todo edit form



In the Edit and Create controller after saving the Todo I would like to reload the list to show the appropriate changes. The problem: after calling
$state.go('^')
when the Todo is created or updated, the URL in the browser changes back to
/api/todo
, but the ListCtrl is not executed, i.e.
$scope.search
is not called, hence the Todo list (with the changed items) is not retrieved, nor are the details of the first Todo displayed in Column 2 (instead, it goes blank).

I have even tried
$state.go('^', $stateParams, { reload: true, inherit: false, notify: false });
, no luck.

How can I do a state transition so the controller eventually gets executed?

Source:

var TodoApp = angular.module('TodoApp', ['ngResource', 'ui.router'])
.config(function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/api/todo');

$stateProvider
.state('todo', {
url: '/api/todo',
controller: 'ListCtrl',
templateUrl: '/_todo_list.html'
})
.state('todo.details', {
url: '/{id:[0-9]*}',
views: {
'detailsColumn': {
controller: 'DetailsCtrl',
templateUrl: '/_todo_details.html'
}
}
})
.state('todo.edit', {
url: '/edit/:id',
views: {
'detailsColumn': {
controller: 'EditCtrl',
templateUrl: '/_todo_edit.html'
}
}
})
.state('todo.new', {
url: '/new',
views: {
'detailsColumn': {
controller: 'CreateCtrl',
templateUrl: '/_todo_edit.html'
}
}
})
;

})
;

TodoApp.factory('Todos', function ($resource) {
return $resource('/api/todo/:id', { id: '@id' }, { update: { method: 'PUT' } });
});

var ListCtrl = function ($scope, $state, Todos) {
$scope.todos = [];

$scope.search = function () {
Todos.query(function (data) {
$scope.todos = $scope.todos.concat(data);
$state.go('todo.details', { id: $scope.todos[0].Id });
});
};

$scope.search();
};

var DetailsCtrl = function ($scope, $stateParams, Todos) {
$scope.todo = Todos.get({ id: $stateParams.id });
};

var EditCtrl = function ($scope, $stateParams, $state, Todos) {
$scope.action = 'Edit';

var id = $stateParams.id;
$scope.todo = Todos.get({ id: id });

$scope.save = function () {
Todos.update({ id: id }, $scope.todo, function () {
$state.go('^', $stateParams, { reload: true, inherit: false, notify: false });
});
};
};

var CreateCtrl = function ($scope, $stateParams, $state, Todos) {
$scope.action = 'Create';

$scope.save = function () {
Todos.save($scope.todo, function () {
$state.go('^');
});
};
};

Answer

Huge thanks for Radim Köhler for pointing out that $scope is inherited. With 2 small changes I managed to solve this. See below code, I commented where I added the extra lines. Now it works like a charm.

var TodoApp = angular.module('TodoApp', ['ngResource', 'ui.router'])
    .config(function ($stateProvider, $urlRouterProvider) {
        $urlRouterProvider.otherwise('/api/todo');

        $stateProvider
            .state('todo', {
                url: '/api/todo',
                controller: 'ListCtrl',
                templateUrl: '/_todo_list.html'
            })
            .state('todo.details', {
                url: '/{id:[0-9]*}',
                views: {
                    'detailsColumn': {
                        controller: 'DetailsCtrl',
                        templateUrl: '/_todo_details.html'
                    }
                }
            })
            .state('todo.edit', {
                url: '/edit/:id',
                views: {
                    'detailsColumn': {
                        controller: 'EditCtrl',
                        templateUrl: '/_todo_edit.html'
                    }
                }
            })
            .state('todo.new', {
                url: '/new',
                views: {
                    'detailsColumn': {
                        controller: 'CreateCtrl',
                        templateUrl: '/_todo_edit.html'
                    }
                }
            })
        ;

    })
;

TodoApp.factory('Todos', function ($resource) {
    return $resource('/api/todo/:id', { id: '@id' }, { update: { method: 'PUT' } });
});

var ListCtrl = function ($scope, $state, Todos) {
    $scope.todos = [];

    $scope.search = function () {
        Todos.query(function (data) {
            $scope.todos = $scope.todos(data); // No concat, just overwrite
            if (0 < $scope.todos.length) { // Added this as well to avoid overindexing if no Todo is present
                $state.go('todo.details', { id: $scope.todos[0].Id });
            }
        });
    };

    $scope.search();
};

var DetailsCtrl = function ($scope, $stateParams, Todos) {
    $scope.todo = Todos.get({ id: $stateParams.id });
};

var EditCtrl = function ($scope, $stateParams, $state, Todos) {
    $scope.action = 'Edit';

    var id = $stateParams.id;
    $scope.todo = Todos.get({ id: id });

    $scope.save = function () {
        Todos.update({ id: id }, $scope.todo, function () {
            $scope.search(); // Added this line
            //$state.go('^'); // As $scope.search() changes the state, this is not even needed.
        });
    };
};

var CreateCtrl = function ($scope, $stateParams, $state, Todos) {
    $scope.action = 'Create';

    $scope.save = function () {
        Todos.save($scope.todo, function () {
            $scope.search(); // Added this line
            //$state.go('^'); // As $scope.search() changes the state, this is not even needed.
        });
    };
};
Comments