71

Without using a service or constructing watchers in the parent controller, how would one give children states access to the main controller's $scope.

  .state("main", {
      controller:'mainController',
      url:"/main",
      templateUrl: "main_init.html"
  })  
  .state("main.1", {
      controller:'mainController',
      parent: 'main',
      url:"/1",
      templateUrl: 'form_1.html'
  })  
  .state("main.2", {
      controller:'mainController',
      parent: 'main',
      url: "/2",
      templateUrl: 'form_2.html'
  })  

I'm not able to access the mainController scope in child state--or rather I'm getting another instance of that scope--not what I want. I feel I'm missing something simple. There is a shared data config option in the state object but I'm not sure if this should be used for something like this.

Ashish Kamble
  • 2,555
  • 3
  • 21
  • 29
sjt003
  • 2,407
  • 5
  • 24
  • 39
  • Childs have access to the parent. Can you replicate your problem in a plunkr? – tymeJV Dec 29 '14 at 21:57
  • 1
    That's a good example, but how would I get it working with controller as syntax instead of $scope? – Paul Erdos Sep 10 '15 at 15:45
  • I wonder; I am trying to do the same thing, but fear that I may have to inject `$scope` into the controller, even though I use `controller as` syntax (and not use `$scope` in the controller's code - just provide it to ui-router). – Mawg says reinstate Monica Feb 25 '20 at 06:36

5 Answers5

108

I created working plunker, showing how to use $scope and UI-Router.

The state definition is unchanged:

$stateProvider
    // States
 .state("main", {
      controller:'mainController',
      url:"/main",
      templateUrl: "main_init.html"
  })  
  .state("main.1", {
      controller:'mainController',
      parent: 'main',
      url:"/1",
      templateUrl: 'form_1.html'
  })  
  .state("main.2", {
      controller:'mainController',
      parent: 'main',
      url: "/2",
      templateUrl: 'form_2.html'
  })  

But each state can have different controller. Why? because each view of each state gets new instance of defined controller. So while we have mainController like the one below, we can be sure, that if we navigate to state 'main.2' it will be instantiated twice.

controller('mainController', function ($scope) {
  $scope.Model = $scope.Model || {Name : "xxx"};
})

But what we can see here, is that we check if $scope.Model already exsits... and if not (Parent state) we instantiate it with new intance {Name : "xxx"}.

Well, what I am saying is: only parent state will init the $scope.Model. All others will get that already filled. How? Well here is the answer:

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, as stated in the documentation. Because our child views are nested in the parent view, the scope is inherited.

Understanding Scopes

In AngularJS, a child scope normally prototypically inherits from its parent scope.
...

Having a '.' in your models will ensure that prototypal inheritance is in play.

// So, use
<input type="text" ng-model="someObj.prop1"> 
// rather than
<input type="text" ng-model="prop1">.

And that's it. We get inheritance from UI-Router views and angular scopes, and because we smartly used a reference type (Model), i.e. do have '.' dot in ng-model definition - we can share data now

NOTE: having dot '.' in the ng-model="Model.PropertyName simply means, that there is a reference object Model {} with some property: PropertyName

Check the working example here

Eric Smekens
  • 1,602
  • 20
  • 32
Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
  • 9
    This is extremely informative. Thank you for taking the time to explain this in an ordered manner--this level of distillation does not go unnoticed. I reconstructed the stateProvider.state object and my templates per your advice and everything worked the way I wanted it to work: scope inherited down the chain--I feel silly for not thinking of this basic priciple when constructing the views--thank you again I wish all answers on SO were as well thought-out as this. – sjt003 Dec 30 '14 at 16:04
  • Radim, your help on SO is greatly appreciated! – James Drinkard Nov 13 '15 at 16:15
  • @Radim Kohler I have problem with login states. Could you please help me with my question: http://stackoverflow.com/questions/34690439/unable-to-change-the-name-of-login-button-using-ng-show/34690959#34690959 – kittu Jan 09 '16 at 14:12
  • Who know becouse if i remove "|| {} ;" inside the mainController it doesn't works? – Silvio Troia Dec 03 '16 at 11:01
  • @Radim Kohler is there any way you can help me with this https://stackoverflow.com/questions/58126482/change-an-angularjs-nested-template-at-run-time – S Dra Sep 27 '19 at 01:08
15

You can get the whole scope through $rootScope. If you need just part of the scope, ui-router has a custom data feature.

Here's how to do a multi-step form. I needed the routes to contain info for about their steps in the flow.

First, I have some routes with UI-router:

  // Sign UP routes
  .state('sign-up', {
    abstract: true,
    url: '/sign-up',
    controller: 'SignupController',
    templateUrl: 'sign-up/index.html',
  })
  .state('sign-up.start', {
    url: '-start',
    templateUrl: 'sign-up/sign-up.start.html',
    data: { step: 0, title: 'Welcome to Mars!', },
  })
  .state('sign-up.expertise', {
    url: '-expertise',
    templateUrl: 'sign-up/sign-up.expertise.html',
    data: { step: 1, title: 'Your Expertise'},
  })

Notice:

  • the data element in each route.
  • The abstract state has SignupController. That's the only controller for this multi-step form. The abstract isn't required, but makes sense for this use case.

SignupController.js

angular.module('app').controller('SignupController', function($scope, $state) {
  $scope.state = $state;
});

Here we get ui-router's $state and put it on $scope

Here is the main template 'sign-up/index.html',:

<h2>{{state.current.data.title}}</h2>

<div>This is a multi-step-progress control {{state.current.data.step}}</div>

<form id="signUpForm" name="signUpForm" novalidate>
  <div ui-view></div>
</form>

The child templates can be whatever they like.

Michael Cole
  • 15,473
  • 7
  • 79
  • 96
9

The idea is that you use scope in parent->child inheritance:

 .state("main", {
      controller:'mainController',
      abstract: true,
      url:"/main",
      templateUrl: "main_init.html"
  })  
  .state("main.1", {
      controller:'mainController1',
      parent: 'main',
      url:"/1",
      templateUrl: 'form_1.html'
  })  
  .state("main.2", {
      controller:'mainController2',
      parent: 'main',
      url: "/2",
      templateUrl: 'form_2.html'
  })  

Than the usage is simple, you have 3 controllers, one is shared (mainController) and each view has it's own.

Ben Diamant
  • 6,186
  • 4
  • 35
  • 50
8

If you are using nested views just dont write any other Controller. By this way they will share same Controller Data.

.state("main", {
            url: "/main",
            templateUrl: "templates/Ders",
            controller: "DersController as DersC"
       }).state("main.child1", {
            url: "/child1",
            templateUrl: "templates/Ders/child1"
       }).state("main.child2", {
            url: "/child2",
            templateUrl: "templates/Ders/child2"
        })
Bahtiyar Özdere
  • 1,818
  • 17
  • 21
  • 3
    What if i want the child1Controller and child2Controller for child1 and child2 respectively? – John Feb 06 '17 at 18:58
1

Isn't the simplest solution to group shared variables into a service you can access in every controller ? ...

hugsbrugs
  • 3,501
  • 2
  • 29
  • 36
  • 1
    It is a simple solution, but wouldn't be amazing like (1) how views can access parent controllers (cleanly using `controller as`) and (2) how parent controllers can get injected into directives (cleanly using `require`), if controllers could have dependent controllers instances injected into them? – Langdon Aug 05 '15 at 16:12