AngularJS: Multiple views with routing without losing scope

All we need is an easy explanation of the problem, so here it is.

I’m trying to implement a classic list/details UI. When clicking an item in the list, I want to display an edit form for that item while still displaying the list. I’m trying to work around Angular’s 1-view-per-page limitation and decided to do it by having all URLs routed to the same controller/view. (Perhaps this is the root of my problem and I’m open to alternatives.)

Routing:

$routeProvider
    .when('/list', { templateUrl: '/Partials/Users.html', controller: UserController })
    .when('/edit/:UserId', { templateUrl: '/Partials/Users.html', controller: UserController })
    .otherwise({ redirectTo: '/list' });

The view (/Partials/Users.html):

<!-- List of users -->
<div ng-repeat="user in Users">
    <a href="*/edit/{{ user.Id }}" rel="nofollow noreferrer noopener">Edit {{ user.Name }}</a>
</div>

<!-- Edit form -->
<div>
    {{ SelectedUser.Name }}
</div>

Controller:

function UserController($scope, $routeParams) {
    // the model for the list
    $scope.Users = GetUserListFromService();

    // the model for the edit form
    if ($routeParams.UserId != null)
        $scope.SelectedUser = GetUserFromService($routeParams.UserId);
}

Problems:

  1. When clicking an edit link, the controller is reinstantiated with a new scope, so I have to re-init the Users list. (In a more complex example I could have input from the user stored bound to the model and this would also get lost.) I’d prefer to persist the scope from the previous route.
  2. I’d prefer to use a separate controller (or, as many other Angular developers have complained, the ability to have multiple displayed views!) but that leads to the same issue of losing scope.

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

Try using ui-router: http://github.com/angular-ui/ui-router.

They have nested views and easier state management than angular default routing 🙂

Method 2

Multiple views are not supported in core AngularJS. You can use this library for this purpose which supports any amount of nested views on the page, where each level is configured independently with its own controller and template:

http://angular-route-segment.com

It is much simpler to use than ui-router. Sample config may look like this:

$routeSegmentProvider.

when('/section1',          's1.home').
when('/section1/prefs',    's1.prefs').
when('/section1/:id',      's1.itemInfo.overview').
when('/section1/:id/edit', 's1.itemInfo.edit').
when('/section2',          's2').

segment('s1', {
    templateUrl: 'templates/section1.html',
    controller: MainCtrl}).

within().

    segment('home', {
        templateUrl: 'templates/section1/home.html'}).

    segment('itemInfo', {
        templateUrl: 'templates/section1/item.html',
        controller: Section1ItemCtrl,
        dependencies: ['id']}).

    within().

        segment('overview', {
            templateUrl: 'templates/section1/item/overview.html'}).

        segment('edit', {
             templateUrl: 'templates/section1/item/edit.html'}).

        up().

    segment('prefs', {
        templateUrl: 'templates/section1/prefs.html'}).

    up().

segment('s2', {
    templateUrl: 'templates/section2.html',
    controller: MainCtrl});

Method 3

I’ve found Angular Multi View to be a godsend for this scenario. It lets you preserve scope as the route changes and lets multiple controllers share the same route without nesting your views.

I recommend Angular Multi View if you have more than 2 views on your page. Otherwise, when using ui-router, nesting multiple views gets messy really fast.

Method 4

I came up with the same problem and I personnaly don’t like plugins when they aren’t absolutely unavoidable. I just moved singleton part to a service.

In my case there are :id[/:mode] routes and I want to react different way if user changes just mode or id too. Thus, I have to know previous id.

So, there is a service with activate method which updates its state. And the scope is reinitialized every time with the following code.

module.controller('MyController', ['$scope', '$routeParams', 'navigator', function($scope, $routeParams, navigator) {
    var id = null;
    var mode = null;

    if (typeof($routeParams.id)!='undefined')
    {
        id = $routeParams.id;
    }

    if (typeof($routeParams.mode)!='undefined')
    {
        mode = $routeParams.mode;
    }

    navigator.activate(id, mode);

    $scope.items = navigator.items;
    $scope.activeItem = navigator.activeItem;
    $scope.modes = navigator.modes;
    $scope.activeMode = navigator.activeMode;
}]);

In activate method I can compare id to the singleton’s activeItem.id and react differently.

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply