scope and controller instantiation with ui router

eNddy picture eNddy · Mar 17, 2015 · Viewed 32.3k times · Source

I am confused about when controllers get instantiated. Also, how do controllers gets instantiated when nesting states. I might be confused how scope gets attached to view and controller, that is, if every view gets its own controller and scope or do they share the same scope.

Can someone please explain when controllers get instantiated? Under nested routes do all the views share one controller and scope? What happens when I switch states and go back to a state does another controller get instantiated?

Below are my routes(config file ):

.config (googleAnalyticsCordovaProvider, $stateProvider, $urlRouterProvider, IdleProvider, KeepaliveProvider) ->

   $stateProvider

  .state('app', {
    url: '/app',
    abstract: true,
    templateUrl: 'templates/menu.html',
    controller: 'AppController'
  })

  .state('app.pincode', {
    url: '/pincode',
    views: {
      menuContent: {
        templateUrl: 'templates/pincode-yield.html',
        controller: 'PincodeController'
      }
    }
  })

  .state('app.pincode.create', {
    url: '/create',
    views: {
      pincode: {
        templateUrl: 'templates/pincode-create.html',
        controller: 'PincodeController'
      }
    }
  })

  .state('app.pincode.pincodeLogin', {
    url: '/login',
    views: {
     pincode: {
        templateUrl: 'templates/pincode-login.html',
        controller: 'PincodeController'
      }
    }
  })

  .state('app.pincode.settings', {
    url: '/settings',
    views: {
      pincode: {
        templateUrl: 'templates/settings.html',
        controller: 'PincodeController'
      }
    }
  })

Answer

Radim Köhler picture Radim Köhler · Mar 18, 2015

To get even more detailed answers, we can/should observe the source code and check the documentation. Let me try to explain all three questions (and also cite from code and doc).

1. When do controllers get instantiated?

Here we can observe the code of the ui-view directive:

[$ViewDirective.$inject = \['$state', '$injector', '$uiViewScroll', '$interpolate'\];][1]

Controllers are related to views. Those views, which are defined inside of a .state() as the views object:

.state('...', {
  // The view definition
  views : {
    '' : {
      template: ...
      controller: ...
      resolve: ..
    }
  },
  resolve: ...
}

So, whenever is view (the ui-view) filled with settings defined inside of a state view, it acts almost as a standard, but enhanced directive.

1) Template is found,
2) Resolves are resolved
...
x) Controller is instantiated...

View targets (ui-view directives) could use names, and could be filled by different states in the hierarchy.

It could mean, that there could be a content inside of one view (e.g. title), defined by parent as well as replaced by child

// parent
.state('parent', {
  views : {
    '' : {...} // the main parent view, with ui-view="title"
    'title@parent' : { ...} // here we go and fill parent's ui-view="title"
  },
  ...
}

// child
.state('parent.child', {
  views : {
    'title' : { ...} // here we change the parent's target ui-view="title"
  },
  ...
}

The above state definition will (whenever we transition among these two states) do:

  • The $state.go('parent') - the view (template, controller...) defined in 'title@parent' : { ...} will be injected into target ui-view="title" and instantiated as described above

  • The $state.go('parent.child') - almost the same, just the view will be taken from child state/view defintion 'title' : { ...}. That will replace the content of the ui-view="title" and will be instantiated as described above

This will be happening every time we do go from parent to child and from child to parent.

2. Under nested routes do all the views share one controller and scope?

A simple answer is NO, there is no common sharing.

In fact, each controller has its own scope, the one which is created from parent view scope. Firstly the documentation:

What Do Child States Inherit From Parent States?

...

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.

So, whenever is our controller (well the view with template, controller...) injected into parent's target ui-view="..." it gets inherited scope:

newScope = scope.$new();

That in a nutshell means that JS objects (e.g. scope.Model = {}) can be shared among child and parent.

$scope.Model.id = 1; // will refer to the same id in both parent & child

However, basic Javascript types are not passed by reference, and so their values are not automatically synchronised between scopes:

// set in parent
$scope.id = 1;
// in child after inherted still === 1
$scope.id = 2; // now 2 for a child, different value in parent - still === 1

It's worth reading more about prototypical inheritance here:
What are the nuances of scope prototypal / prototypical inheritance in AngularJS?

3. What happens when I switch states and go back to a state - does another controller get instantiated?

It depends.

If the parent sub view (remember ui-view="title" above) is replaced by child view, and then it is re-created (transitioning from child to parent) - such controller wil be re-initialized (discussed above).

But when we speak about the main parent view (usually unnamed), which represents the parent (For example the unnamed view below with controller 'ParentMainCtrl')

.state('parent', {
  views : {
    '' : {  //  // the main parent view
      controller: 'ParentMainCtrl',
    }
    'title@parent'
    'tooltip@parent'
  },

Then we can be sure that such controller is NOT re-instantiated. It lives during the lifetime of all its children, plus a parent's one (no child state selected).

To re-load this view/controller, we have to use an option reload

$state.go(to, params, options)

... options Options object. The options are:

  • ...
  • reload - {boolean=false}, If true will force transition even if the state or params have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd use this when you want to force a reload when everything is the same, including search params.

Hope that helps a bit. For further information, check out these resources: