Nested views with nested states in AngularJS

I am trying to do nested states, but something is wrong and I cannot figure out why.

I have these states in my angular app:

/client (list clients)
/client/:id (show client)
/client/new (new client)

      

And now I am trying to do:

/client/:id/task (list clients tasks)
/client/:id/task/new (create new task for this client)
/client/:id/task/:idTask (show the client task)

      

All states work, but states task

do not change content.

My index.html with ui-view "main"

:

<section id="container">
    <header></header>
    <sidebar></sidebar>
    <section class="main-content-wrapper" ng-class="{'main':collapse}">
        <section id="main-content">
            <div ui-view="main"></div>
        </section>
    </section>
</section>

      

My client.tpl.html with ui-view "content":

<div class="row">
    <div class="col-md-12">
        <ul class="breadcrumb">
            <li><a href ui-sref="home">Home</a></li>
            <li class="active">Clients</li>
        </ul>
    </div>
</div>
<div ui-view="content"></div>

      

My application states:

$stateProvider
    .state('/', {
        url: '/',
        templateUrl: '/app/application/application.tpl.html',
        abstract: true
    })

    // CLIENT
    .state('client', {
        url: '/client',
        abstract: true,
        views: {
            'main': {
                templateUrl: '/app/client/client.tpl.html',
                controller: 'ClientController'
            }
        }
    })
    .state('client.list', {
        url: '/list',
        views: {
            'content': {
                templateUrl: '/app/client/client.list.tpl.html',
                controller: 'ClientListController'
            }
        }
    })
    .state('client.new', {
        url: '/new',
        views: {
            'content': {
                templateUrl: '/app/client/client.new.tpl.html',
                controller: 'ClientNewController'
            }
        }
    })
    .state('client.show', {
        url: '/:id',
        views: {
            'content': {
                templateUrl: '/app/client/client.show.tpl.html',
                controller: 'ClientShowController',
            }
        }
    })

      

Task status

    // TASKS
    .state('client.details', {
        url: '/:idClient',
        abstract: true,
        views: {
            'content': {
                templateUrl: '/app/task/task.tpl.html',
                controller: 'TaskController'
            }
        }
    })
    .state('client.details.task', {
        url: '/task',
        views: {
            'content': {
                templateUrl: '/app/task/task.list.tpl.html',
                controller: 'TaskListController'
            }
        }
    })
    .state('client.details.task.new', {
        url: '/new',
        views: {
            'content': {
                templateUrl: '/app/task/task.new.tpl.html',
                controller: 'TaskNewController'
            }
        }
    })
    .state('client.details.task.show', {
        url: '/:idTask',
        views: {
            'content': {
                templateUrl: '/app/task/task.show.tpl.html',
                controller: 'TaskShowController'
            }
        }
    });

      

So when I click to navigate to:

/client
/client/:id
/client/new

      

Everything works fine, content change, but when I click to go to:

/client/:id/task
/client/:id/task/:idTask
/client/:id/task/new

      

The content does not change, in fact the content becomes empty.


UPDATE 1

The link to the task list is in the sidebar, the sidebar is a directive:

Directive

.directive('sidebar', [function () {
    return {
        restrict: 'E',
        replace: true,
        templateUrl: '/common/partials/sidebar.html'
    };
}])

      

Template:

<aside class="sidebar" ng-class="{'sidebar-toggle':collapse}" ng-controller="SidebarController as sidebar">
    <div id="leftside-navigation" class="nano">
        <ul class="nano-content">
            <li class="active">
                <a href ui-sref="home"><i class="fa fa-dashboard"></i><span>Home</span></a>
            </li>
            <li class="sub-menu">
                <a href ng-click="toggle()">
                    <i class="fa fa-users"></i>
                    <span>Clients</span>
                    <i class="arrow fa fa-angle-right pull-right"></i>
                </a>
                <ul style="height: {{height}}px; overflow: hidden;">
                    <li ng-repeat="client in session.clients">
                        <a href ui-sref="client.details.task({id:client.id})">{{client.name}}</a>
                    </li>
                </ul>
            </li>
        </ul>
    </div>
</aside>

      

Link in ui-sref

:/client/10/task


+3


source to share


1 answer


The solution is surprisingly simple, but the concept can be a little tricky.

So, the definition of the state should be like this:

The state of the client root does not change. It injects its view into ui-view="main"

root states (index.html)

// CLIENT
.state('client', {
    ...
    views: {
        'main': {
            templateUrl: '/app/client/client.tpl.html',
            ...
        }
    }
})

      

We now have first level children. They will target ui-view="content"

their parent (client and his template introduced in ui-view="main"

)

.state('client.list', {
    views: {
        'content': {
    ....
})
.state('client.new', {
    url: '/new',
    views: {
        'content': {
    ...
})
...

      

So everything is working so far. Below is the change. Let's try to add our templates back to ui-view="content"

- good. But this is not defined in our parent. It is in the state grand-parent - a Client

. So we'll skip one level. We must use absolute naming for targeting the name of the name

// TASKS
.state('client.details.task', {
    views: {
        // wrong
        'content': {
        // correct
        'content@client': {         
})
.state('client.details.task.new', {
    views: {
        // wrong
        'content': {
        // correct
        'content@client': {         
    }
})
...

      



It should be clear now. If not, maybe it might help a little. Level 1 children will work even with this definition of a condition

.state('client.list', {
    views: {
        // working
        'content': {
        // also working
        'content@client': {
    ....
})

      

Because we just used an absolute name - where it's done for us out of the box (syntactic sugar). For a more detailed explanation, please see the documentation:

View Names - Relative and Absolute Names

small cite:

Behind the scenes, each view is assigned an absolute name that follows the schema , where viewname is the name used in the view directive and the state name is the absolute name of the state, for example. contact.item. You can also write name names in absolute syntax. viewname@statename

For example, the previous example could also be written as:

.state('report',{
    views: {
      'filters@': { },
      'tabledata@': { },
      'graph@': { }
    }
})

      

+2


source







All Articles