rje
rje

Reputation: 6428

Angularjs: Callback to parent controller

Please consider the following code: it has a directive myItem with isolate scope. Each item will display a button that will call delete() on the directive controller. I'd like this to trigger a refresh in the outer controller (AppController). But of course the refresh() function can not be found, because of the isolated scope.

   <html>
    <body ng-app="question">
        <div ng-cloak ng-controller="AppController">
            <my-item ng-repeat="item in list" data="list">
            </my-item>
            <input type="text" maxlength="50" ng-model="new_item" />
            <button ng-click="add(new_item)">+</button>
        </div>
        <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.min.js"></script>
        <script>
         (function () {
             var app;

             app = angular.module('question', []);


             app.controller('AppController', [
                 '$scope', '$http', function ($scope, $http) {
                     $scope.list = [];

                     function refresh(){
                         $http.get('/api/items').then(
                             function(response){
                                 $scope.list = response.data;
                             }
                         );
                     }

                     $scope.add = function(item){
                         $http.post('/api/items', { item: item }).then(refresh);
                     };

                     refresh();
                 }
             ]);

             app.directive('myItem', function() {
                 return {
                     scope: {
                         item: '=data',
                     },
                     // directive template with delete button
                     template: '{{ item }} <button ng-click="delete(item)">-</button>',
                     restrict: 'E',
                     // directive controller with delete function
                     controller: [ '$scope', '$http', function($scope, $http) {
                         $scope.delete = function (card) {

// This is where it goes wrong! refresh does not exist
                             $http.delete('/api/items' + card.id).then(refresh);
                         }
                     }]
                 };
             });
         })();
        </script>
    </body>
</html>

One thing I could do is add ng-change to the myItem directive, but that would involve requiring ngModelController which seems overkill.

Other things I can think of:

  1. Add something like onchange: '@' to the scope attribute of the directive, then set onchange = refresh in the html. Call the onchange expression instead of refresh inside the delete function. But this feels like I'm re-implementing ng-change?

  2. Add require: '^AppController' to the directive. Then I guess I could call refresh on the parent controller directly. That seems like it violates loose coupling.

  3. Don't use isolate scope at all. That would mean we inherit from the parent scope and refresh is available. But then my directive implicitly assumes that the scope will hold an item. Which also violates loose coupling, but in an implicit way.

So my question is: which is the correct way to let the parent controller know it should refresh its contents?

Upvotes: 0

Views: 2014

Answers (1)

S.Klechkovski
S.Klechkovski

Reputation: 4045

IMO, the first way would be the best way. The directive receives a function callback from outside which is executed by the directive when necessary. Like this the two directives are loosely coupled. It's similar to ng-change which is an attribute that is used by ng-model directive.

Example: Directive

app.directive('myItem', function() {
    return {
        restrict: 'E',
        scope: {
            item: '=data',
            myItemDeleteCallback: '&myItemDeleteCallback'
        },
        template: '{{ item }} <button ng-click="delete(item)">-</button>',
        controller: [ '$scope', '$http', function($scope, $http) {
            $scope.delete = function (card) {
                // This is where it goes wrong! refresh does not exist
                $http.delete('/api/items' + card.id).then(function () {
                    $scope.myItemDeleteCallback();
                });
            }
        }]
    };
});

Usage: Controller

app.controller('AppController', ['$scope', '$http', function ($scope, $http) {
    $scope.list = [];

    $scope.refresh = function (){
        $http.get('/api/items').then(
            function(response){
                $scope.list = response.data;
            }
        );
    };

    $scope.add = function(item){
        $http.post('/api/items', { item: item })
            .then($scope.refresh);
    };

    refresh();
}]);

Usage: Template

<div ng-cloak ng-controller="AppController">
    <my-item my-item-delete-callback="refresh()" ng-repeat="item in list" data="list">
    </my-item>
    <input type="text" maxlength="50" ng-model="new_item" />
    <button ng-click="add(new_item)">+</button>
</div>

Upvotes: 2

Related Questions