JustAMartin
JustAMartin

Reputation: 13753

How to correctly interact with a view which is managed from another controller?

The question might seem too vague but I could not think of a better way to describe the idea, so I'll try to explain it in details.

I have MasterController attached to <html> tag of my SPA application. This MasterController contains all the logic and models for controlling the following UI elements:

While the first two items on this list can be managed through detection of current ui-router state (through its $stateChangeSuccess event), the last two (username and buttons) are somewhat problematic, especially the buttons.

I can manage the button actions using $broadcast, so every controller can be notified about clicks on any button. But the tricky part here is that the buttons might be needed in different combinations - one page might need all of them, and another one might need none.

Let's say, ui-router loads some CustomersController. At that point MasterController receives $stateChangeSuccess event and by default hides all the buttons.

But now, how does CustomersController tell to MasterController that CustomersController will need two specific buttons from the very beginning?

Theoretically, I could use $emit from CustomersController to send an event to MasterController, but it somehow feels ugly. Events are meant for, well, events and not for sending requests like "hey, MasterController, if you are somewhere up the scope, can you please show the following buttons?".

Of course, I might be wrong and maybe there is some way to use Angular event system to manage this scenario in clean way.

What came to my mind is that maybe in the $stateChangeSuccess event I could somehow detect if there are currently any listeners for my button click events and then I could hide buttons which do not have any listeners attached, but I'm not sure how to do it, and I'm not sure whether it will work as expected - whether old listeners will be detached when ui-router recreates the view with another controller.

Upvotes: 1

Views: 72

Answers (3)

JustAMartin
JustAMartin

Reputation: 13753

Today I got a tricky idea based on @Diego Castaño Chillarón 's answer. I thought - but is it possible to use ui-router to swap controller of existing view and will it rebind also the $scope? And will I still be able to replace inner parts of the loaded view?

It turned out that it is doable! Now I don't have to control the common view fragments from the master control, and I don't need also to inherit or duplicate them - I just switch the controller to the required one through ui-router.

Like this:

$stateProvider
    .state("customers", {
            url: "^/customers",
            views: {
                "controller": {
                    controller: "CustomerController as cntrlr"
                },                                    
                "page@customers": // <- this is important, absolute name required for ui-router to find nested view
                {
                    templateUrl: "customers"
                }
            }
        }) // other routes follow in the same manner

And my HTML looks like this:

<div id="routes-root" ui-view="controller">

  <div id="content-header-buttons">
      <button type="button" ng-click="master.toggleFilter()">Filter data</button>
      <button type="button" ng-click="cntrlr.exportClicked()">Export</button>
      <button type="button" ng-click="cntrlr.createNewClicked()">Create</button>
   </div>
    <div id="view-content" ui-view="page"></div>
</div>

As you see, I left master controller to control only visibility for filters block, which won't change.

But controller itself is attached to #routes-root element, preserving inner content, and ui-router (or Angular) is smart enough to attach $scope and cntrlr variable to the loaded controller. And then I load inner view into #view-content, which also gets attached to the already loaded controller.

Upvotes: 0

Kevin Dre&#223;ler
Kevin Dre&#223;ler

Reputation: 447

If you are just nesting controllers, their corresponding scopes actually make use of prototypical inheritance. So you could just define a function $scope.configureButtons in your MasterController and call this function from the $scope in your nested CustomerController.

If Controllers are not nested you would probably need to resort to $rootScope.$broadcast for setting up your buttons.

Upvotes: 1

Why not just simply using diferent controllers for each view? Maybe generalize a bit the CustomerController and extend it (specialize it) for every combination of buttons you need. Using the $stateChangeSuccess feels like avoiding polymorphism to me.

Upvotes: 0

Related Questions