PlantTheIdea
PlantTheIdea

Reputation: 16359

Multiple nested states AngularJS ui-router

Forgive if this is a silly question, but I am new to AngularJS, and for the life of me I cannot figure out why this nested view setup is not operating as expected.

I have a top-level <div>:

<div class="grid" ui-view></div>

Which has a module populating its view:

angular.module('planttheidea',[
    'ui.router',
    'planttheidea.header',
    'planttheidea.mainContent',
    'planttheidea.footer'
])
    .config(['$stateProvider','$urlRouterProvider',function($stateProvider,$urlRouterProvider){
        $stateProvider
            .state('base',{
                url:'',
                views:{
                    '':{
                        templateUrl:'app/layout.view.html',
                        controller:'LayoutCtrl'
                    }
                }
            });

        $urlRouterProvider.otherwise('/');
    }])
    .controller('LayoutCtrl',function LayoutCtrl($scope,$stateParams){
        var layout = this;
    }); 

I then have the contents of that layout view:

<header ui-view="header" class="header menubar col-p-100" ng-controller="HeaderCtrl"></header>
<main ui-view="main-content" class="main-content grid col-p-100" ng-controller="MainContentCtrl"></main>
<footer ui-view="footer" class="footer col-p-100" ng-controller="FooterCtrl"></footer>

And right now I have the header and footer with their own modules:

angular.module('planttheidea.header',[
    'planttheidea.header.menu'
])
    .config(['$stateProvider','$urlRouterProvider',function($stateProvider){
        $stateProvider
            .state('base.header',{
                url:'',
                views:{
                    'header@base':{
                        templateUrl:'app/components/header/header.view.html',
                        controller:'HeaderCtrl as layoutHeaderCtrl'
                    }
                }
            });
    }])
    .controller('HeaderCtrl',function HeaderCtrl($scope,$stateParams){
        var layoutHeaderCtrl = this;
    });

angular.module('planttheidea.footer',[])
    .config(['$stateProvider','$urlRouterProvider',function($stateProvider){
        $stateProvider
            .state('base.footer',{
                url:'',
                views:{
                    'footer@base':{
                        templateUrl:'app/components/footer/footer.view.html',
                        controller:'FooterCtrl as layoutFooterCtrl'
                    }
                }
            });
    }])
    .controller('FooterCtrl',function FooterCtrl($scope,$stateParams){
        var layoutFooterCtrl = this;
    });

You can see they are nearly identical. The header module is working perfectly (it even has its own nested ui-view for the menu, which works just fine), however the footer code is not routing the ui-view. What's even wackier is that if I copy-and-paste the footer code into the header module (using just the base.header state), it works as desired:

angular.module('planttheidea.header',[
    'planttheidea.header.menu'
])
    .config(['$stateProvider','$urlRouterProvider',function($stateProvider){
        $stateProvider
            .state('base.header',{
                url:'',
                views:{
                    'header@base':{
                        templateUrl:'app/components/header/header.view.html',
                        controller:'HeaderCtrl as layoutHeaderCtrl'
                    },
                    'footer@base':{
                        templateUrl:'app/components/footer/footer.view.html',
                        controller:'FooterCtrl as layoutFooterCtrl'
                    }
                }
            });
    }])
    .controller('HeaderCtrl',function HeaderCtrl($scope,$stateParams){
        var layoutHeaderCtrl = this;
    })
    .controller('FooterCtrl',function FooterCtrl($scope,$stateParams){
        var layoutFooterCtrl = this;
    });

I have validated the code is being loaded correctly (I did a console.log() in the controller of the footer just to make sure it was running at all), so I am thinking it must be the segregation into the two modules. Do I need to declare all the layout views in the same state, or is there a way to break them up into separate modules as I have done here? I must believe its possible because I did so with the nested menu module, however that was nested whereas the footer is in parallel, so I'm worried that may be the issue.

Any assistance would be most appreciated, but do keep in mind I am in the process of learning AngularJS, so while working code would be awesome (obviously), an explanation of why would be even more awesome.

Upvotes: 0

Views: 2501

Answers (1)

Jacob Carter
Jacob Carter

Reputation: 711

So there are a few problems in here that I would correct first and see if that fixes your issue.

Lets assume you have a DOM layout like this:

<div ui-view>
    <div ui-view="view1"></div>
</div>

Then your routes would look like this (it will be the same even if it is split into different modules as long as you inject the other modules into the master one - which you are)

.config(['$stateProvider','$urlRouterProvider',function($stateProvider){
        $stateProvider
            .state('base',{
                url:'/',
                templateUrl:'template.html',
                controller:'Ctrl as ctrl'
            })
            .state('base.view1',{
                url:'/view1',
                views:{
                    'view1':{
                        templateUrl:'template.html',
                        controller:'Ctrl2 as ctrl2'
                    }
                }
            });
    }])

Some things to notice:

Because my base state is going directly into a ui-view with no value i don't have a view object with an empty string, i just exclude it altogether.

Also my base.view1 has a url assigned to it, as it is a different state.

Because my ui-view="view1" is inside the ui-view of my base state I don't need to claim the view as view1@base below is a scenario where I would need to do that.

<div ui-view="main"></div>


.config(['$stateProvider','$urlRouterProvider',function($stateProvider){
        $stateProvider
            .state('base',{
                url:'/',
                views : {
                    'main' : {
                        templateUrl:'template.html',
                        controller:'Ctrl as ctrl'
                    }
                }

            })
            .state('base.view1',{
                url:'/view1',
                views : {
                    'main@base' : {
                        templateUrl:'template.html',
                        controller:'Ctrl as ctrl'
                    }
                }

            });
    }])

In this example I want base.view1 to "takeover", so to speak, the view that base was in.

I don't really understand why you have a header and footer as different states. Perhaps this is because I don't have the big picture of your project, but it seems out of the norm. Usually you would include your header and footer as part of the parent state and then have your main-content area switch out with different nested states.

Hope this made sense, let me know if I can clarify more.

Upvotes: 1

Related Questions