'Communicate two views with one controller in AngularJS

I'm using UI-Router for Angular and I have separated views for my app: sidebar and main. Now i need to change some class in main view after some action that does in sidebar view.

So, this is my code:

config

.state('app.area', {
    url: '/area/:areaId',
    views: {
        '@': {
            template: require('./app/generic/genericWithSidebar.html'),
            controller: 'AreaCtrl'
        },
        '[email protected]': {
            template: require('./app/area/_area.html'),
            controller: 'AreaCtrl',
            controllerAs: 'CTRL',
        },
        '[email protected]': {
            template: require('./app/area/_sidebar.html'),
            controller: 'AreaCtrl',
            controllerAs: 'CTRL',
        }
    },

controller

class AreaCtrl {
    constructor($scope) {
        "ngInject";

        this.$scope = $scope;
        this.$scope.descriptionIsActive = false;
    }

    showAreaDescription() {
        this.$scope.descriptionIsActive = !this.$scope.descriptionIsActive;
    }
}

export default AreaCtrl;

and views for sidebar and main

// sidebar view
<span ng-click="CTRL.showAreaDescription()">show more</span>
// main view
<div ng-class="{'active': CTRL.descriptionIsActive}"></div>

I need to communicate between views, not controllers, i have one controller.



Solution 1:[1]

The "correct" solution depends on what it is that changed in your main view that causes a change in the navigation.

As I just answered here, it is generally a bad sign if you need controllers to "talk" to each other. This often means that you should have a service that takes care of the data/state that you want to bind to in both views.

If, however, your change is really just a global cosmetic navigational thing (I can not think of an example but I do not want to say that thats impossible), a "global" NavigaitonController (on your body for example) might be correct. I doubt it though.

So my suggestion is: Think about what data causes the change, handle the state of that data in its own service and bind to that service property where you need it.

Solution 2:[2]

You could define your controller outside the views option so that it will only load once on state load.

stateProvider.state("app.area",{
   url:'/app.area',
   templateUrl: 'app_area_frame.html',
   controller:'AreaCtrl',
   controllerAs: 'CTRL',
   views:{
      'main':{
          template: require('./app/area/_area.html')
      },
      'sidebar':{
          template: require('./app/area/_sidebar.html')
      }
   }

})

Nested views for UI-router docs

Solution 3:[3]

State is meant to be encapsulated by services, so in fact, if you have state that should be shared across the app, you should create a service.

Alternatively, you can have a controller in a higher scope, say you have a controller for the app, which can then be inherited by the sidebar and main page child scopes.

Solution 4:[4]

Maybe this answer is not the exact answer for your particular question. But from a architecture's view you should not use nested views to handle main view with sidebar.

Because this sidebar should be same for all states. So if you are using nested views you should specify your sidebar each time. Also you should write the code for sidebar in every states. That is not a recommended way to design your application.

I strongly recommend you to go through this Q&A.

Then how to design?

Make your sidebar as a simple template and use ng-include to render your footer part.

<footer ng-include="'/sidebar.html'" ng-controller="sidebarController"></footer>

As you can see there is a dedicated controller for your sidebar and you don't need to duplicate your code to each main controller.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Community
Solution 2 Glorfindel
Solution 3 SamMorrowDrums
Solution 4 Community