Skip to content

Nested States & Nested Views

Tim Kindberg edited this page Aug 28, 2013 · 47 revisions

◄ Back (State Manager)     Next (Multiple Named Views) ►

States can be nested within each other. You can specify nesting in several ways:

You can use dot syntax to infer your hierarchy to the $stateProvider. Below, contacts.list becomes a child of contacts.

$stateProvider
  .state('contacts', {})
  .state('contacts.list', {});

Alternately, you can specify the parent of a state via the parent property.

$stateProvider
  .state('contacts', {})
  .state('list', {
    parent: 'contacts'
  });

Object-based States

If you aren't fond of using string-based states, you can also use object-based states. The name property goes in the object and the parent property must be set on all child states, like this:

var contacts = { 
    name: 'contacts',  //mandatory
    templateUrl: 'contacts.html'
}
var contactsList = { 
    name: 'list',      //mandatory
    parent: contacts,  //mandatory
    templateUrl: 'contacts.list.html'
}

$stateProvider
  .state(contacts)
  .state(contactsList)

You can usually reference the object directly when using other methods and property comparisons:

$state.transitionTo(states.contacts);
$state.current === states.contacts;
$state.includes(states.contacts)

Nested States & Views

When the application is in a particular state—when a state is "active"—all of its ancestor states are implicitly active as well. Below, when the "contacts.list" state is active, the "contacts" state is implicitly active as well, because it's the parent state to "contacts.list".

Child states will load their templates into their parent's ui-view.

$stateProvider
  .state('contacts', {
    templateUrl: 'contacts.html',
    controller: function($scope){
      $scope.contacts = [{ name: 'Alice' }, { name: 'Bob' }];
    }
  })
  .state('contacts.list', {
    templateUrl: 'contacts.list.html'
  });

function MainCtrl($state){
  $state.transitionTo('contacts.list');
}
<!-- index.html -->
<body ng-controller="MainCtrl">
  <div ui-view></div>
</body>
<!-- contacts.html -->
<h1>My Contacts</h1>
<div ui-view></div>
<!-- contacts.list.html -->
<ul>
  <li ng-repeat="contact in contacts">
    <a>{{contact.name}}</a>
  </li>
</ul>

What Do Child States Inherit From Parent States?

Child states DO inherit the following from parent states:

Nothing else is inherited (no controllers, templates, url, etc).

Inherited Resolved Dependencies

Child states will inherit resolved dependencies from parent state(s), which they can overwrite. You can then inject resolved dependencies into the controllers and resolve functions of child states.

Warning: Feature is in progress. See #73. Use ResolveService workaround for now.

$stateProvider.state('parent', {
      resolve:{
         resA:  function(){
            return {'value': 'A'};
         }
      },
      controller: function($scope, resA){
          $scope.resA = resA.value;
      }
   });
   .state('parent.child', {
      resolve:{
         resB: function(resA){
            return {'value': resA.value + 'B'};
         }
      },
      controller: function($scope, resA, resB){
          $scope.resA2 = resA.value;
          $scope.resB = resB.value;
      }

Inherited Custom Data

Child states will inherit data properties from parent state(s), which they can overwrite.

$stateProvider.state('parent', {
      data:{
         customData1:  "Hello",
         customData2:  "World!"
      }
   })
   .state('parent.child', {
      data:{
         // customData1 inherited from 'parent'
         // but we'll overwrite customData2
         customData2:  "UI-Router!"
      }
   });

$rootScope.$on('$stateChangeStart', 
function(event, toState, toParams, fromState, fromParams){ 
    var greeting = toState.data.customData1 + " " + toState.data.customData2;
    console.log(greeting);

    // Would print "Hello World!" when 'parent' is activated
    // Would print "Hello UI-Router!" when 'parent.child' is activated
})

Scope Inheritance by View Hierarchy Only

Keep in mind that scope properties only inherit down the state chain if the views of your states are nested. Inheritance of scope properties has nothing to do with the nesting of your states and everything to do with the nesting of your views (templates).

It is entirely possible that you have nested states whose templates populate ui-views at various non-nested locations within your site. In this scenario you cannot expect to access the scope variables of parent state views within the views of children states.

Abstract States

An abstract state can have child states but can not get activated itself. An 'abstract' state is simply a state that can't be transitioned to. It is activated implicitly when one of its descendants are activated.

Some examples of how you might use an abstract state are:

  • To prepend a url to all child state urls.
  • To insert a template with its own ui-view(s) that its child states will populate.
    • Optionally assign a controller to the template. The controller must pair to a template.
    • Additionally, inherit $scope objects down to children, just understand that this happens via the view hierarchy, not the state hierarchy.
  • To provide resolved dependencies via resolve for use by child states.
  • To provide inherited custom data via data for use by child states or an event listener.
  • To run an onEnter or onExit function that may modify the application in someway.
  • Any combination of the above.

Abstract State Usage Examples:

To prepend url to child state urls

$stateProvider
    .state('contacts', {
        abstract: true, 
	url: '/contacts',

        // Note: abstract still needs a ui-view for its children to populate.
        // You can simply add it inline here.
        template: '<ui-view/>'
    })
    .state('contacts.list', {
	// url will become '/contacts/list'
        url: '/list'
	//...more
    })
    .state('contacts.detail', {
	// url will become '/contacts/detail'
        url: '/detail',
	//...more
    })

To insert a template with its own ui-view for child states to populate

$stateProvider
    .state('contacts', {
        abstract: true,
        templateURL: 'contacts.html'
    )
    .state('contacts.list', {
        // loaded into ui-view of parent's template
        templateUrl: 'contacts.list.html'
    })
    .state('contacts.detail', {
        // loaded into ui-view of parent's template
        templateUrl: 'contacts.detail.html'
    })
<!-- contacts.html -->
<h1>Contacts Page</h1>
<div ui-view></div>

Combination

Shows prepended url, inserted template, paired controller, and inherited $scope object.

Full Plunkr Here: http://plnkr.co/edit/gmtcE2?p=preview

$stateProvider
    .state('contacts', {
        abstract: true,
        url: '/contacts',
        templateUrl: 'contacts.html',
        controller: function($scope){
            $scope.contacts = [{ id:0, name: "Alice" }, { id:1, name: "Bob" }];
        }    		
    })
    .state('contacts.list', {
        url: '/list',
        templateUrl: 'contacts.list.html'
    })
    .state('contacts.detail', {
        url: '/:id',
        templateUrl: 'contacts.detail.html',
        controller: function($scope, $stateParams){
          $scope.person = $scope.contacts[$stateParams.id];
        }
    })
<!-- contacts.html -->
<h1>Contacts Page</h1>
<div ui-view></div>
<!-- contacts.list.html -->
<ul>
    <li ng-repeat="person in contacts">
        <a ng-href="#/contacts/{{person.id}}">{{person.name}}</a>
    </li>
</ul>
<!-- contacts.detail.html -->
<h2>{{ person.name }}</h2>

◄ Back (State Manager)     Next (Multiple Named Views) ►