AngularJS ui-router $ state.go(' ^')只更改地址栏中的URL,但不加载控制器

时间:2014-01-23 13:19:39

标签: javascript angularjs angular-ui-router

我正在尝试使用angularjs ui-router创建一个“Todo App”。它有两列:

  • 第1栏:Todos列表
  • 第2栏:Todo详情或Todo编辑表格

在保存待办事项后的编辑和创建控制器中,我想重新加载列表以显示相应的更改。问题:在创建或更新Todo时调用$state.go('^')后,浏览器中的URL会更改回/api/todo,但不执行ListCtrl,即不调用$scope.search,因此未检索Todo列表(包含已更改的项目),第2列中的第一个Todo的详细信息也未显示(而是显示为空白)。

我甚至试过$state.go('^', $stateParams, { reload: true, inherit: false, notify: false });,没有运气。

如何进行状态转换,以便控制器最终执行?

来源:

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('^');
        });
    };
};

3 个答案:

答案 0 :(得分:5)

我会举例说明(草稿)如何将edit嵌套到detail中。好吧,首先让我们修改模板。

Detail模板包含详细信息的完整定义。此外,它现在包含属性ui-view="editView"。这将确保编辑将从可见性角度“替换”详细信息 - 而编辑范围将继承所有详细信息设置。这就是 ui-router

的强大功能
<section ui-view="editView">
  <!-- ... here the full description of the detail ... -->
</section>

所以,其次,让我们将编辑 state移动到详细信息

// keep detail definition as it is
.state('todo.details', {
    url: '/{id:[0-9]*}',
    views: {
        'detailsColumn': {
            controller: 'DetailsCtrl',
            templateUrl: '/_todo_details.html'
        }
    }
})
// brand new definition of the Edit
.state('todo.details.edit', { // i.e.: url for detail like /todo/details/1/edit
    url: '/edit',
    views: {
        'editView': {    // inject into the parent/detail view
            controller: 'EditCtrl',
            templateUrl: '/_todo_edit.html'
        }
    }
})

通过调整 statetemplate映射,我们确实有很多。现在我们可以充分利用ui-router的全部权力。

我们将在DetailCtrl 上定义一些方法(请记住,在继承编辑状态下可用)

var DetailsCtrl = function ($scope, $stateParams, Todos) {

    $scope.id =  $stateParams.id // keep it here

    // model will keep the item (todos) and a copy for rollback
    $scope.model = {
        todos : {},
        original : {},
    }

    // declare the Load() method

    $scope.load = function() {
        Todos
          .get({ id: $stateParams.id })
          .then(function(response){

              // item loaded, and its backup copy created
              $scope.model.todos = response.data;
              $scope.model.original = angular.copy($scope.model.todos);

          });
    };

    // also explicitly load, but just once,
    // not auto-triggered when returning back from Edit-child
    $scope.load()
};

好的,现在应该很清楚,我们确实有modelmodel.todos及其备份model.original

编辑控制器可以有两个操作:Save()Cancel()

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

    // ATTENTION, no declaration of these, 
    // we inherited them from parent view !
    //$scope.id ..     // we DO have them
    //$scope.model ...

    // the save, then force reload, and return to detail
    $scope.save = function () {
        Todos
           .update({ id: id })
           .then(function(response){

              // Success
              $scope.load(); 
              $state.go('^');
           },
           function(reason){

             // Error
             // TODO 
           });
    };

    // a nice and quick how to rollback
    $scope.cancel = function () {
         $scope.model.todos = Angular.copy($scope.model.original);
         $state.go('^');
    };
};

这应该给出一些想法,如何在父/子状态之间导航并强制重新加载

注意事实上,我使用的是lo-dash _.cloneDeep()而不是Angular.copy(),但两者都应该有效

答案 1 :(得分:5)

非常感谢RadimKöhler指出$scope是遗传的。通过2次小改动,我设法解决了这个问题。看下面的代码,我评论了我添加了额外的行。现在它就像一个魅力。

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.
        });
    };
};

答案 2 :(得分:3)

我可能遇到过类似的问题,我采用的方法是使用$location.path(data.path).search(data.search);重定向页面,然后在控制器中捕获$ locationChangeSuccess事件。换句话说,我使用$location.path(...).search(...)作为$state.go(...),然后捕获$ locationChangeSuccess事件,当路由匹配并调用控制器之前发生位置更改时将触发该事件。

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, todo.details) {
    /*here is where i would make the change*/        
    $scope.$on('$locationChangeSuccess', function () {
          $scope.search();
          $route.reload();
    });

        $scope.todos = [];

        $scope.search = function () {
            Todos.query(function (data) {
                $scope.todos = $scope.todos.concat(data);
            });
        };

    $scope.search();
};

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

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

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

    $scope.save = function () {
        Todos.update({ id: id }, $scope.todo, function () {
           //here is where I would make a change
                $location.path('todo.details').search($stateParams);
        });
    };
};

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

    $scope.save = function () {
        Todos.save($scope.todo, function () {
           //here is where I would make a change
                $location.path('todo.details');
        });
    };
};

$ locationChangeSuccess事件在匹配路由并调用控制器之前发生