39

Given an HTML file like so:

<html>
<header ui-view="header"></header>
<div class="main" ui-view></div>
<footer ui-view="footer"></footer>
</html>

How would one create a layout state that fills the "header" with a header template, the footer with a footer template, and then allow child states to fill the empty ui-view?

I suppose the empty ui-view could also be named something like ui-view="main".

bkaid
  • 51,465
  • 22
  • 112
  • 128
CMCDragonkai
  • 6,222
  • 12
  • 56
  • 98

6 Answers6

79

try this, practically your header and footer are static templates but you can add the controllers in case you need to add some dynamic functionality to it, the header and the footer will be included by default since the route is '', so try that out:

 app.config(['$stateProvider', function($stateProvider){
        $stateProvider
        .state('root',{
          url: '',
          abstract: true,
          views: {
            'header': {
              templateUrl: 'header.html',
              controller: 'HeaderCtrl'
            },
            'footer':{
              templateUrl: 'footer.html',
              controller: 'FooterCtrl'
            }
          }
        })
        .state('root.home', {
          url: '/',
          views: {
            'container@': {
              templateUrl: 'homePage.html'
            }
          }
        })
        .state('root.other', {
          url: '/other',
          views: {
            'container@': {
              templateUrl: 'other.html'
            }
          }
        });    

    }]);

Edit: to me the best place to set the views should be in the index.html and something like this code:

<header>
    <div ui-view="header"></div>
</header>
<div ui-view="container">
</div>
<footer id="mainFooter" ui-view="footer">
</footer>
pedrommuller
  • 15,741
  • 10
  • 76
  • 126
32

One way is to make a global 'root' state. So, each and every state will be it's child. Like root.pages, root.pages.edit, root.settings, etc. Then you can provide default templates for header and footer.

Another way, different approach, that I use personally, is to use ng-include for header and footer.

<body ng-controller="AppCtrl">
  <div id="header" ng-include="'header.tpl.html'"></div>
  <div class="main_container" ui-view>
  </div>
</body>

Btw. I use seperate controller in header.tpl.html, that is a child of main AppCtrl.:

<div id="header" ng-controller="HeaderCtrl"> ....
Maxim Demkin
  • 1,215
  • 11
  • 13
  • 2
    That's a really good idea. The reason why a "layout" or "root" state is useful is when inside your app, your "headers" or "footers" may change. Such as between the internal account management vs the outside presentation site. Although in this root state, is it an abstract state, and I'm guessing that it does not have a url property right? (I also like the AppCtrl, I've been using the app.run(function(){}) as a root controller. – CMCDragonkai Mar 02 '14 at 04:12
  • Yes, root state could be an abstract state. Though, abstract state should have it's URL. This can be used to prepend an url for children states. But for root state it can be kept as '/'. Here is a detailed info about abstract states in ui-router: https://github.com/angular-ui/ui-router/wiki/Nested-States-%26-Nested-Views#wiki-abstract-states As for controller, you can make your main App controller as the controller of that root state. It will work. – Maxim Demkin Mar 02 '14 at 06:48
  • I would have a Home state url being '/'. So I don't think my abstract state can have a url in this case. – CMCDragonkai Mar 02 '14 at 07:03
  • 2
    You have no idea how many pages I read concerning nested views, named views/pages, ui-router tutorials, etc... before your post made me realize that a simple ng-include would do just fine. Thanks, you effectively put a pillow between my head and the wall it was banging against. – oberger Feb 22 '15 at 00:23
  • I am currently doing what @MaximDemkin has suggested above. However it creates an abstract state of the header and footer. Any idea how to make header and footer have concrete state? – 0_0 Sep 16 '16 at 16:35
21

There is actually a very easy way to do that.

1. Create a layout state

$stateProvider
  .state('master', {
    abstract: true,
    views: {
      layout: {
        templateUrl: '/layouts/master.html',
      }
    }
  });

2. Use unnamed view state inside the layout

<!-- layouts/master.html -->
<div ui-view></div>

3. Create a view state

$stateProvider
  .state('home', {
    url: '/',
    templateUrl: '/views/home.html',
    parent: 'master',
  });

4. Use named layout state as root state

<!-- home.html -->
<body ui-view="layout"></body>
M K
  • 9,138
  • 7
  • 43
  • 44
  • 1
    On a slightly related note, I'm leaning towards this approach to be able to share controller code between mobile and desktop apps, and to serve either a desktop or mobile template, in case anyone wondering similar things stumbles upon this comment. – Fissio Jan 11 '16 at 14:19
  • @Fissio You should try to share code trough services and not trough controllers. But I think it's fine for template helpers, etc. – M K Jan 11 '16 at 16:11
  • 1
    Yeah, that's what I meant. Different templates for mobile and desktop, but same controllers, since 95% of the controller functionality is shared. – Fissio Jan 11 '16 at 22:04
  • 1
    Best answer here, using it as a templated minimizes the need for writing extra directives. – BenNov Apr 09 '16 at 09:50
  • @MK confused! would you please provide a Codepen/Fiddle? – Iman Mahmoudinasab Mar 18 '17 at 16:43
4

Based on the answer by Jack.the.ripper, I created this solution.

Casus: I have a slight variation that I actually want 2 layouts. One Public and one Private. In Meteor with Blaze and Iron Router there was a nice solution that you could just define which master template will be used for a certain route. This I have now been able to set up thanks to Jack!

NOTE: Code will be in Jade and Coffee and it's a Meteor + Angular project. Use http://js2.coffee/ to convert to Javascript.

# the ROUTER part
#
angular.module( 'myApp' ).config(

  ( $urlRouterProvider, $stateProvider, $locationProvider ) ->
    $locationProvider.html5Mode true

    $stateProvider
      .state( 'private',
        url: ''
        abstract: true
        views:
          'app':
            templateUrl: 'client/views/layouts/privateLayout.html'
      )

      .state( 'public',
        url: ''
        abstract: true
        views:
          'app':
            templateUrl: 'client/views/layouts/publicLayout.html'
      )

      .state( 'private.home',
        url: '/'
        views:
          "container@private":
            templateUrl: 'client/views/home/home.html'
      )

      .state( 'public.login',
        url: '/login'
        views:
          "container@public":
            templateUrl: 'client/views/session/login.html'
      )
      $urlRouterProvider.otherwise '/'
 )

This is the index file where the app view is defined that will utilise the abstract state defined in the router.

head
meta(name="viewport" content="width=device-width, initial-scale=1")
base(href="/")

body(layout="column")
   div(ui-view="app" layout="column" flex)

Then the Private Layout with its container view.

div(layout="column" flex)
  div(ng-controller="AppCtrl" layout="row" flex)

  //- some private Layout stuff happening here....
  md-content(flex layout-padding)
     div(ui-view="container" layout="column")

and finally the Public Layout with its container view

div.public-layout(layout="column" flex)
   div(ui-view="container" layout="column" flex)

With this setup I can set the login page to use the abstract public layout by stating in the view of this route that it should uses views container@public, meaning from Public view, use the view Container. In this view load the login.html.

Same goes for the home page, this one loads the container@private meaning the Container view of the Private view.

This seems to work like a charm.

Thanks very much Jack, and also the author of the answer on Angular UI Router - Nested States with multiple layouts which helped me to get towards the final solution.

Cheers

Community
  • 1
  • 1
Mattijs
  • 3,265
  • 3
  • 38
  • 35
1

Similar to jack.the.ripper's way, you could also do it the way it works for me.

app.config(function($stateProvider, $urlRouterProvider) {
  $stateProvider
    .state('root', {
      /* The Root State */
      views: {
        '': {
          abstract: true,
          templateUrl: 'master.html',
          controller: 'mainController'
        },
        'header@root': {
          templateUrl: 'header.html',
          controller: 'headerController',
        },
        'footer@root': {
          templateUrl: 'footer.html',
          controller: 'footerController'
        },
    },
   })
    .state('root.index', {
      /* The Index State */
      url: '/',
      views: {
        'content': {
          templateUrl: 'index.html',
          controller: 'indexController'
        }
      },
    .state('root.other', {
      /* The Other State */
      url: '/',
      views: {
        'content': {
          templateUrl: 'other.html',
          controller: 'otherController'
        }
      },
    });
});

In our index.html we will only have a <ui-view></ui-view>

The master.html will look like

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

Why I chose this approach, is I don't have to create a separate global controller, and my mainController will be the global controller.

0

Instead of using routes at all for the header and footer, I would use Angular components now that they are available in 1.5x.

It is way more flexible and you don't have to deal with root.whatever or ngInclude. I go into it in more detail here: https://stackoverflow.com/a/41093339/2416007, but essentially you:

1. Create Component

(function () {
'use strict';
angular
    .module('layout')
    .component('layoutHeader', {
        templateUrl: 'layout/header.html',
        bindings: {},
        controller: Controller
    });

Controller.$inject = [];
function Controller() {
    var ctrl = this;

    initialize();

    ////////////////////

    function initialize(){

    }

}
}());

2. Add it to your index.html page or similar

<layout-header></layout-header>

3. Same for footer

<layout-footer></layout-footer>

4. The result in the body is

<layout-header></layout-header>
<main ui-view></main>
<layout-footer></layout-footer>
Community
  • 1
  • 1
Justin
  • 582
  • 9
  • 24
  • components are not really the same as using ui-router. ui-router will allow you to pick templates/controller configurations based off url. Using Named Views as above will allow for swapping templates based on $state – Kieran May 17 '17 at 05:46