user3562751
user3562751

Reputation: 263

How to invoke multiple functions/chained calls using ng-click

Hi I have a button that fires off two functions like so. Is there a way for the updateDetails to run only after changeColor has finished? The updateDetails is currently pulling an old configuration with the old color, and not the changed color.

ng-click="changeColor(color.code); updateDetails()"

Here is the view:

<div ng-controller="ColorsCtrl">
  <div ng-controller="HeaderCtrl">
    <div byo-header>
    </div>

    <div ng-repeat="color in colors" class="row">
      <div class="small-12 columns">
            <div ng-controller="ButtonsController">
                <div class="button-group">
              {{ isSelected(color.code) }}
                <button ng-click="changeColor(color.code); updateDetails()" type="radio" class="button" ng-model="selectedColor" btn-radio="color.code">{{color.name}}</button>
            </div>
            </div>  
      </div>
    </div>
    <br/>
    <br/>
    <div class="row">
        <div class="small-4 small-offset-4 columns">
        <!-- Proceed to packages if available -->
            <a ng-if="hasPackage" href="#/packages/{{modelCode}}" class="button">Packages</a>
        <!-- Proceed to options if packages not available -->
        <a ng-if="!hasPackage" href="#/options/{{modelCode}}" class="button">Options</a>
        </div>
    </div>    
  </div>
</div>

Here is changeColor() in the ColorsCtrl:

$scope.changeColor = function(newColorCode){
        //swap previous selected color with new
        configuratorService.addOption($scope.modelCode, newColorCode, function(configuration){
            $scope.configuration = configuration;               
        });

    }

  }

Here is updateDetails in the HeaderCtrl

    $scope.updateDetails = function(){
        $scope.summaryDetails = getSummaryDetails();
    }

Upvotes: 3

Views: 1412

Answers (2)

PSL
PSL

Reputation: 123739

Technically you could write inline provided changeColor returns promise and inturn your service call must return a promise (which is better compared to traditional passing around callbacks anyways).

First change your service to return a promise, an example:-

 function configuratorService($http){
      this.addOption = function(modelCode, colorCode){
          return $http.post("Addoption", {modelCode:modelCode, colorCode:colorCode})
                 .then(function(response){ 
                   //logic to get configuration or whatever
                   return response.data
          });
     }
  }

In ColorsCtrl:

$scope.changeColor = function(newColorCode){
     //return promise   
    return configuratorService.addOption($scope.modelCode, newColorCode)
         .then( function(configuration){
            $scope.configuration = configuration;     
            /*You could even return the data by doing the following*/          
            //return $scope.configuration = configuration;
        });
    }
 }

In HeaderCtrl:

$scope.updateDetails = function(){//<-- if you are returning data from changeColor you could even use it here
    $scope.summaryDetails = getSummaryDetails();
}

and finally in your view:

ng-click="changeColor(color.code).then(updateDetails)"

An example Demo

angular.module('app', []).controller('HeaderCtrl', function($scope) {
  $scope.updateDetails = function() {
    //check the console
    console.log('UpdateDetails');
    $scope.summaryDetails = "Summary Details after option changeColor";
  }
}).controller('ColorsCtrl', function($scope, configuratorService) {
  $scope.changeColor = function(newColorCode) {
    //swap previous selected color with new
    return configuratorService.addOption().then(function(configuration) {
      //check the console
      console.log('Option Added');
      $scope.configuration = configuration;
    });
  }
}).service('configuratorService', function($timeout) {
  this.addOption = function() {
    //Justa proxy for an ansyn operation and return call
    return $timeout(function() {
      return "Color Updated"
    }, 1000);
  }
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">

  <div ng-controller="ColorsCtrl">
    <div ng-controller="HeaderCtrl">

      <button ng-click="changeColor().then(updateDetails)">Update</button>
      {{summaryDetails}}
    </div>
  </div>

There are many other options and better ways to do it. One provided in the TymeJV answer, the issue there is that your controllers becomes tightly coupled with one another and could affect re-usability and you would need to do extra mocks to add the non existent method updateDetails while testing ColorsCtrl.

While this approach in my answer has its own problem because now your view needs to know something (that the method returns promise) which it should not. There are other ways to handle this scenario in a better way: One simple approach is to use eventing or a pub/sub pattern which notifies other controller that something has happened here do whatever you want to do. Such kind of design will make it more loosely coupled and more reusable and testable.

You can make use of angular event bus itself, like $broadcast or $emit as per applicability combined with $on ,to perform this task or you could create a pub/sub design yourself.

With simple eventing you would just do this:-

In ColorsCtrl:

    $scope.changeColor = function(newColorCode){
        configuratorService.addOption($scope.modelCode, newColorCode, function(configuration){
            $scope.configuration = configuration;
            //BroadCast an event, this will notify any child scopes that has subscribed to this event.   
            $scope.$broadCast("Configuration_Udpated");                           
          });
       }
   }

In HeaderCtrl subscribe to an event and register updateDetails method:

//....
$scope.updateDetails = function(){
    $scope.summaryDetails = getSummaryDetails();
}


//...
$scope.$on("Configuration_Udpated", $scope.updateDetails)

Side note:- Try to make use of the promises rather than passing callbacks to the service. You can find a lot of articles on the web regarding promise patterns.

Upvotes: 4

tymeJV
tymeJV

Reputation: 104775

Since changeColor is an async operation - no, you can't wait inline. You'd have to call the function in the callback:

$scope.changeColor = function(newColorCode){
    //swap previous selected color with new
    configuratorService.addOption($scope.modelCode, newColorCode, function(configuration){
        $scope.configuration = configuration;  
        $scope.updateDetails();             
    });
}

If this function is used else where and you don't always want to call update, pass a flag param:

$scope.changeColor = function(newColorCode, shouldUpdate){
    //swap previous selected color with new
    configuratorService.addOption($scope.modelCode, newColorCode, function(configuration){
        $scope.configuration = configuration;  
        if (shouldUpdate) $scope.updateDetails();             
    });
}

ng-click="changeColor(color.code, true);"

Upvotes: 6

Related Questions