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

时间:2021-12-09 18:04:56

I am trying to create a "Todo App" with angularjs ui-router. It has 2 columns:

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

  • Column 1: list of Todos
  • 第1列:Todos列表
  • Column 2: Todo details or Todo edit form
  • 第2列:Todo详细信息或Todo编辑表单

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

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

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

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

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

3 个解决方案

#1


5  

I would give an example (a draft) of HOW TO nest edit into detail. Well, firstly let's amend the templates.

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

The Detail template, contains full definition of the detail. Plus it now contains the attribute ui-view="editView". This will assure, that the edit, will "replace" the detail from the visibility perspective - while the edit scope will inherit all the detail settings. That's the power of ui-router

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

<section ui-view="editView">
  <!-- ... here the full description of the detail ... -->
</section>

So, secondly let's move the edit state, into the detail

所以,其次让我们将编辑状态移动到细节中

// 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'
        }
    }
})

Having this adjusted state and template mapping, we do have a lot. Now we can profit from the ui-router in a full power.

有了这个调整后的状态和模板映射,我们确实有很多。现在我们可以充分利用ui-router获利。

We'll define some methods on a DetailCtrl (remember, to be available on the inherit Edit state)

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

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

OK, it should be clear now, that we do have a model with the item model.todos and its backup model.original.

好了,现在应该很清楚,我们确实有一个带有item.todos项目及其备份模型的模型。原文。

The Edit controller could have two actions: Save() and Cancel()

编辑控制器可以有两个操作: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('^');
    };
};

That should give some idea, how to navigate between parent/child states and forcing reload.

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

NOTE in fact, instead of Angular.copy() I am using lo-dash _.cloneDeep() but both should work

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

#2


5  

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.

非常感谢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.
        });
    };
};

#3


3  

I might have faced a similar problem the approach i took was to use $location.path(data.path).search(data.search); to redirect the page then in the controller I caught the $locationChangeSuccess event. I other words I use the $location.path(...).search(...) as apposed to $state.go(...) then caught the $locationChangeSuccess event which will be fired when the location changes occurs before the route is matched and the controller invoked.

我可能遇到了类似的问题,我采用的方法是使用$ 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');
        });
    };
};

the $locationChangeSuccess event occurs before the route is matched and the controller invoked

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

#1


5  

I would give an example (a draft) of HOW TO nest edit into detail. Well, firstly let's amend the templates.

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

The Detail template, contains full definition of the detail. Plus it now contains the attribute ui-view="editView". This will assure, that the edit, will "replace" the detail from the visibility perspective - while the edit scope will inherit all the detail settings. That's the power of ui-router

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

<section ui-view="editView">
  <!-- ... here the full description of the detail ... -->
</section>

So, secondly let's move the edit state, into the detail

所以,其次让我们将编辑状态移动到细节中

// 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'
        }
    }
})

Having this adjusted state and template mapping, we do have a lot. Now we can profit from the ui-router in a full power.

有了这个调整后的状态和模板映射,我们确实有很多。现在我们可以充分利用ui-router获利。

We'll define some methods on a DetailCtrl (remember, to be available on the inherit Edit state)

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

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

OK, it should be clear now, that we do have a model with the item model.todos and its backup model.original.

好了,现在应该很清楚,我们确实有一个带有item.todos项目及其备份模型的模型。原文。

The Edit controller could have two actions: Save() and Cancel()

编辑控制器可以有两个操作: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('^');
    };
};

That should give some idea, how to navigate between parent/child states and forcing reload.

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

NOTE in fact, instead of Angular.copy() I am using lo-dash _.cloneDeep() but both should work

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

#2


5  

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.

非常感谢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.
        });
    };
};

#3


3  

I might have faced a similar problem the approach i took was to use $location.path(data.path).search(data.search); to redirect the page then in the controller I caught the $locationChangeSuccess event. I other words I use the $location.path(...).search(...) as apposed to $state.go(...) then caught the $locationChangeSuccess event which will be fired when the location changes occurs before the route is matched and the controller invoked.

我可能遇到了类似的问题,我采用的方法是使用$ 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');
        });
    };
};

the $locationChangeSuccess event occurs before the route is matched and the controller invoked

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