AngularJS Directive-Controller Two Way Communication

jonah | May 14, 2015 in Development

How to communicate between AngularJS Directive & Controller without using $broadcast?

We as developers usually come across situations where we have to call a method defined in the AngularJS Directive from the Controller or vice versa.

The easy way out will be to use $broadcast on the $scope or the $rootScope, which work’s quite well. But overusing $broadcast becomes kind of an anti pattern and may lead to performance issues and as the app becomes larger you may not know from where all the calls are going out.

Here’s how I would go about it:

I have a CustomerService which has two methods

  1. get: Fetches the list of customer by calling a Web API & return’s a promise.
  2. save: Save’s a customer by passing the customer to the Web API & return’s a promise.
function CustomerService($http, $q) {
    // The get method
    this.get = function () {
        var deffered = $q.defer();
        $http.get("/api/data").success(function (result) {
            deffered.resolve(result);
        });
        return deffered.promise;
    };
 
    // The save method
    this.save = function (customer) {
        var deffered = $q.defer();
        $http.post("/api/data").success(function (result) {
            deffered.resolve(result);
        });
        return deffered.promise;
    }
}

Then a controller called CustomerController.

function CustomerController($scope, CustomerService) {
    // The accessor object passed to the directive
    $scope.accessor = {};
 
    // Function which fetches the customer list
    var getCustomers = function () {
        CustomerService.get()
           .then(function (result) {
             $scope.customerList = angular.copy(result);
        });
    };
 
    // The function is invoked
    getCustomers();
 
    // Open's the modal
    $scope.openWindow = function () {
        $scope.window.open();
    };
 
    // Call's the saveCustomer method attached to the accessor object by the directive
    $scope.saveCustomer = function () {
        // This method return's a $promise which on success reload's the customer list
        $scope.accessor.saveCustomer().then(function (result) {
            getCustomers();
        });
    };
};​

As you can see there is an object called accessor which is defined on the $scope, this object will be bound to the addCustomer directive which will attach saveCustomer function to the object & the controller can call that function.

<div data-ng-controller="CustomerController">
    <h3>Customers</h3>
 
    <!-- Button which open's the window -->
    <button class="btn btn-primary" data-ng-click="openWindow()">Add Customer</button>
 
    <!-- Display's the customer list -->
    <div class="row" data-ng-repeat="customer in customerList">
        <div class="col-md-2">
            {{customer.Id}}
        </div>
        <div class="col-md-4">
            {{customer.FirstName}}
        </div>
        <div class="col-md-4">
            {{customer.LastName}}
        </div>
    </div>
 
    <!-- Modal containing the directive -->
    <div kendo-window="window" style="display:none;">
        <!-- The directive which has the customer create form & the accessor object is passed -->
        <add-customer data-accessor="accessor"></add-customer>
 
        <div class="modal-footer">
            <button class="btn btn-primary" data-ng-click="saveCustomer()">Ok</button>
            <button class="btn btn-default" data-ng-click="closeWindow()">Cancel</button>
        </div>
    </div>
</div>

In this controller’s view we display the list of customers & we have a modal which has the addCustomer directive inside it.

So basically what I want to achieve is that on click of saveCustomer() button of the modal, The ‘accessor’ object’s method will be called which post’s the customer object to the Web API & return’s a promise after which the custom list will be refreshed.

The directive AddCustomerDirective definition & it’s template markup.

function AddCustomerDirective(CustomerService) {
    return {
        restrict: 'E',
        scope: {
            accessor: '='
        },
        templateUrl: "/Templates/addCustomer.html",
        link: function ($scope, elem, attr) {
            // Customer object which is binded to the form
            $scope.customer = { Id: 0, FirstName: "", LastName: "" };
 
            // Attaches the saveCustomer function which call's the service method
            // Returns a promise
            $scope.accessor.saveCustomer = function () {
                return CustomerService.save($scope.customer);
            };
        }
    };
};
<form>
    <div class="row">
        <div class="col-md-12">
            <label>First Name</label>
            <input type="text" data-ng-model="customer.FirstName" class="form-control" />
        </div>
        <div class="col-md-12">
            <label>Last Name</label>
            <input type="text" data-ng-model="customer.LastName" class="form-control" />
        </div>
    </div>
</form>​

This pattern can also be used the other way around i.e. communicating with the controller from the directive.