16

I am pretty new to the AngularUI Router and would like the use it for the following scenario:

The layout common to all pages includes a top navbar containing a menu with buttons on the right and a content section filling the space below. The page has several pages that I map to UI Router states (page1, page2, ...). Each page can have its own menu items and its own content. The menu needs to share the scope with the content since they interact (e.g. the save button submits the form in the content, it should only be enabled if the form is valid).

mockup

The HTML roughly looks like this:

<body>
    <nav class="...">
        <h1>my site</h1>
        <div>MENU SHOULD GO HERE</div>
    </nav>
    <div class="row">
        <div class="column ...">
            CONTENT SHOULD GO HERE
        </div>
    </div>        
</body>

Right now, I am using two parallel views and two controllers for each state. But this way, the two scopes/controllers cannot interact.

So how would you accomplish that?

2 Answers 2

22

$scope is not the model, its a reference to a model, glue in between the data & the view. If $scopes in two, or more, controllers need to share one model/state/data use a singleton object instance by registering a angular service. That one service/factory can be injected into as many controllers as you like, and then everything can work off that one source of truth.

Heres a demo of 1 factory linking $scopes in navbar & body with ui-router http://plnkr.co/edit/P2UudS?p=preview (left tab only)

ui-router (viewC is navbar):

$stateProvider
.state('left', {
  url: "/",
  views: {
    "viewA": {
      controller: 'LeftTabACtrl',
      template: 'Left Tab, index.viewA <br>' +
                '<input type="checkbox" ng-model="selected2.data" />' +
                '<pre>selected2.data: {{selected2.data}}</pre>'
    },
    {...},
    "viewC": {
      controller: 'NavbarCtrl',
      template: '<span>Left Tab, index.viewC <div ui-view="viewC.list"></div>' +
                '<input type="checkbox" ng-model="selected.data" />' +
                '<pre>selected.data: {{selected.data}}</pre></span>'
    }
  }
})

Factory & Controllers:

app.factory('uiFieldState', function () {
    return {uiObject: {data: null}}
});

app.controller('NavbarCtrl', ['$scope', 'uiFieldState', '$stateParams', '$state',
    function($scope, uiFieldState, $stateParams, $state) {
        $scope.selected = uiFieldState.uiObject;
    }
]);

app.controller('LeftTabACtrl', ['$scope', 'uiFieldState', '$stateParams', '$state',
    function($scope, uiFieldState, $stateParams, $state) {
        $scope.selected2 = uiFieldState.uiObject;
    }
]);

As you can see, the factory object {uiObject: {data: null}} is injected into the controller with uiFieldState & then its simply $scope.selected = uiFieldState.uiObject; for connecting the factory to the scope ng-model="selected.data" .`

Sign up to request clarification or add additional context in comments.

2 Comments

I ended up doing exactly that. I realized that I lacked the knowledge about how to connect parts of an AngularJS app in general (so the question is actually not tied to UI router so much).
I just wanted the data so I tried setting app.factory('uiFieldState', function () { return {data: null}}) and $scope.data=uiFieldState.data. This, of course, didn't work, as an object is needed to pass data between scopes like this.
5

You should use:

$on and $emit

The emit controller, that sends the data.

angular.module('MyApp').controller('MyController', ['$scope', '$rootScope', function ($scope, $rootScope){

  $rootScope.$emit('SomeEvent', data);
}]);

An example of how to implement $rootScope a safe way so it destroys and fixes stuff after use:

angular
.module('MyApp')
.config(['$provide', function($provide){
    $provide.decorator('$rootScope', ['$delegate', function($delegate){

        Object.defineProperty($delegate.constructor.prototype, '$onRootScope', {
            value: function(name, listener){
                var unsubscribe = $delegate.$on(name, listener);
                this.$on('$destroy', unsubscribe);
            },
            enumerable: false
        });


        return $delegate;
    }]);
}]);

And the controller with the data that should be treated.

angular.module('MyApp')
.controller('MySecondController', ['$scope', function MyController($scope) {

        $scope.$onRootScope('SomeEvent', function(event, data){
            console.log(data);
        });
    }
]);

You could pass in $rootScope instead of using the $scopes method $onRootScope that we defined in the config. However, this is not a recommended way of using $emit and $onRootScope.

Instead of using $emit, you could always use $broadcast. This would however give you very huge performance issues as your app grows. Since it bubbles the data through all controllers.

If your controllers are parents and child to each other, they don't have to use the $rootScope.

Here is and example of the difference between $emit and $broadcast: jsFiddle

And their is really performance differences:

enter image description here

4 Comments

Thanks for the comprehensive response! Is there another way using UI router so that I end up with the same scope for both views? That would be more convenient than using events on the root scope...
If you send in the scope as the data variable as shown, the data will bind back to the other one. For example in the index file, you can have a mehtod called "sendUserData({{userData}}" inside a ng-click directive. In the controller, you have the $scope.sendUserData = function(data){$rootScope.$emit('LoadUser', data);} and from the second controller, you just bind the data to your new, current scope: $scope.$onRootScope('LoadUser', function(event, data){$scope.user = data;} The data will be bound.
Consider marking it as an answer if you think it helped you.
I realized that I lacked the knowledge about how to connect parts of an AngularJS app in general. Your answer is interesting and gave me some insight into events and the difference between emit and broadcast. Thanks. The other answer just better solved my problem.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.