2

I am attempting to design the routing and nested views in my app several levels deep in order to replace parts of the app based on a route. I'm able to load the login page, and it seems the layout view loads as well, however, I can't seem to get the nested <ui-view /> tags to work. Can someone please tell me how I'm doing this wrong, or if there is a more idiomatic way in Angular to accomplish the same functionality.

app.js

(function() {
  'use strict';



 angular
    .module('webApp', [
      'ui.router',
    ])
    .config(config)
    .run(run);

  config.$inject = ['$stateProvider', '$urlRouterProvider'];

  function config($stateProvider, $urlRouterProvider, ngClipProvider) {

    $urlRouterProvider.otherwise('/');

    $stateProvider
      .state('app', {
        abstract: true,
        url: '/',
        template: '<ui-view/>'
      })
      .state('app.login', {
        templateUrl: 'views/login.html',
        controller: 'LoginCtrl as login',
        url: ''
      })
      .state('app.main', {
        url: 'room',
        templateUrl: 'views/layout.html'
      })
      .state('app.main.foo', {
        url: '',
        views: {
          'header@app.main': {
            templateUrl: 'partials/header.html',
            controller: 'HeaderCtrl as header'
          },
          'sidebar@app.main': {
            templateUrl: 'partials/sidebar.html',
            controller: 'SidebarCtrl as sidebar'
          },
          'main@app.main': {
            templateUrl: 'partials/main.html',
            controller: 'MainCtrl as main'
          },
          'subHeader': {
            templateUrl: '<div><div ui-view="bottomHeader"></div></div>',
            controller: 'SubHeaderCtrl',
            controllerAs: 'subHeader'
          },
          'subSidebar': {
            templateUrl: '<div><div ui-view="bottomSidebar"></div></div>',
            controller: 'SubSidebarCtrl',
            controllerAs: 'subSidebar'
          },
          'bottomHeader': {templateUrl: '<div>FOO</div>'},
          'bottomSidebar': {templateUrl: '<div>BAR</div>'}
        },
        resolve: {
          isAuthenticated: ['Auth', function(Auth) {
            return Auth.isAuthenticated();
          }]
        }
      })
      .state('app.main.foo.bar', {
        url: '/:id',
        views: {
          'main@': {
            templateUrl: 'partials/main.html'
          },
          'mainOne@': {
            templateUrl: 'partials/main-one.html',
            controller: 'MainOneCtrl as mainOne'
          },
          'mainTwo@': {
            templateUrl: 'partials/main-two.html',
            controller: 'MainTwoCtrl as mainTwo'
          },
          'mainThreee@': {
            templateUrl: 'partials/main-three.html',
            controller: 'MainThreeCtrl as mainThree'
          }
        }
      });
  }

  run.$inject = ['Auth'];

  function run(Auth) {
    Auth.stateChangeError();
    Auth.loginSuccess();
    Auth.loginFailure();
    Auth.checkSession();
  }

}());

index.html

<!DOCTYPE html>

<html lang="en" ng-app="webApp">
  <head>
    <title>FooBar</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <link rel="stylesheet" href="css/main.css" />
    <script src="/js/all.js"></script>
  </head>

  <body ui-view>
  </body>

</html>

layout.html

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

<div class="app">
 <div class="getting-started">
 </div>

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

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

header.html

<header class="header">
  <div ui-view="subHeader"></div>

  <div class="trigger-button">
    <button class="trigger">
      <i class="icon-account"></i>
    </button>
  </div>
</header>

main.html

<div>
  <div ui-view="mainOne"></div>
  <div ui-view="mainTwo"></div>
  <div ui-view="mainThree"></div>
</div>

sidebar.html

<div>
  <div ui-view="subSidebar"></div>
</div>
evkline
  • 1,451
  • 3
  • 16
  • 34

1 Answers1

5

There is a working plunker, showing your scenario

I've made few changes in your state definition:

$stateProvider
  .state('app', {
    abstract: true,
    url: '/',
    template: '<ui-view/>'
  })
  .state('app.login', {
    templateUrl: 'views/login.html',
    controller: 'LoginCtrl',
    controllerAs: 'login',
    url: ''
  })

We are using controller and controllerAs syntax

  .state('app.main', {
    url: 'room',
    templateUrl: 'views/layout.html'
  })

No absulte naming, we target our parent, relative is enough, more readable

  .state('app.main.foo', {
    url: '',
    views: {
      'header': {
        templateUrl: 'partials/header.html',
        controller: 'HeaderCtrl',
        controllerAs: 'header',
      },
      'sidebar': {
        templateUrl: 'partials/sidebar.html',
        controller: 'SidebarCtrl',
        controllerAs: 'sidebar',
      },
      'main': {
        templateUrl: 'partials/main.html',
        controller: 'MainCtrl',
        controllerAs: 'main',
      }
    },
    resolve: {
      isAuthenticated: ['Auth', function(Auth) {
        return Auth.isAuthenticated();
      }]
    }
  })

main state is in our parent, do not redefine it

  .state('app.main.foo.bar', {
    url: '/:id',
    views: {
      'mainOne': {
        templateUrl: 'partials/main-one.html',
        controller: 'MainOneCtrl',
        controllerAs: 'mainOne',
      },
      'mainTwo': {
        templateUrl: 'partials/main-two.html',
        controller: 'MainTwoCtrl',
        controllerAs: 'mainTwo',
      },
      'mainThreee': {
        templateUrl: 'partials/main-three.html',
        controller: 'MainThreeCtrl',
        controllerAs: 'mainThree',
      }
    }
  });

Check it here

Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
  • Hey Radim, thanks so much for the detailed answer and plunker, I really appreciate it. I'm still working on tweaking my code to make it work. Currently, on successful login the route doesn't change from the '/' root route, im sure I missed something and will try again. The reason I have these nested views is because I want the controllers nested at the /:id level to be created/destroyed on route change to refresh the data for the given route. Do you think this is the idiomatic approach? – evkline Apr 04 '15 at 06:03
  • Radim, if a ui-view were nested in 1 of the main-#.html partials, how would it be accessed from the app.main.foo state? – evkline Apr 04 '15 at 06:15
  • 1
    As far as I understand... main-#.html are views inside of the `'app.main.foo.bar'` - and that means that it cannot be accessed from `'app.main.foo'`. But it could be accessed from the 'bar'. I extended the plunker and check ths super subview `'mainThreeeSub@app.main.foo.bar'` which is inside of super child **'bar'**. Hope it helps. The best you can do is to play with and read this: https://github.com/angular-ui/ui-router/blob/master/sample/app/contacts/contacts.js – Radim Köhler Apr 04 '15 at 06:49
  • Radim, sorry to keep bothering you but I still can't seem to get the ui-views to load their templates... I've updated my question to better portray what I'm trying to achieve. The subHeader & subSidebar views are in the same state as their parent and have their own nested ui-views as well. – evkline Apr 04 '15 at 07:23
  • It is more for another question, but there is updated plunker http://plnkr.co/edit/E1PyI1lYZQzL5j5sjFa0?p=preview, working as you'd expected. But I feel that place in this *question & answer* becomes to be to really small ... to show all resp. the latest adjustments ... hope it helps – Radim Köhler Apr 04 '15 at 07:47
  • Radim, thanks again for all your help, got everything working! – evkline Apr 04 '15 at 19:36