Dominik Barann
Dominik Barann

Reputation: 685

Angular call directive from controller

I have the following directive for showing a popup to confirm an execution of a function on click.

Now i would like to use it within my controller to show a popup if the properties of an object has been changed and the user wants to change the location without saving the object before. Is that possible?

angular.module('app.confirm', [
    'ui.bootstrap',
    'template/modal/confirm.html',
])

.controller('ConfirmModalController', ['$scope', '$modalInstance', function($scope, $modalInstance) {
    $scope.confirm = function() {
        $modalInstance.close();
    };

    $scope.cancel = function() {
        $modalInstance.dismiss('cancel');
    };
}])

.directive('confirm', ['$modal', function($modal) {
    return {
        restrict: 'A',
        scope: {
            confirm: '&',
            title: '@confirmTitle',
            message: '@confirmMessage',
            confirmButtonText: '@confirmButtonText',
            cancelButtonText: '@cancelButtonText'
        },
        link: function(scope, element, attributes) {
            element.bind('click', function() {
                var modal= $modal.open({
                    controller: 'ConfirmModalController',
                    templateUrl: 'template/modal/confirm.html',
                    size: 'sm',
                    scope: scope
                });

                modal.result.then(function() {
                    scope.confirm();
                }, function() {
                    // Modal dismissed
                });
            });
        }
    };
}]);

angular.module('template/modal/confirm.html', []).run(['$templateCache', function($templateCache) {
    $templateCache.put(
        'template/modal/confirm.html',
        '<div class="modal-header" ng-show="title">' + 
            '<strong class="modal-title">{{title}}</strong>' + 
        '</div>' + 
        '<div class="modal-body">' +
            '{{message}}' + 
        '</div>' + 
        '<div class="modal-footer">' +
            '<a href="javascript:;" class="btn btn-link pull-left" ng-click="cancel()">{{cancelButtonText}}</a>' + 
            '<button class="btn btn-danger" ng-click="confirm()">{{confirmButtonText}}</button>' +
        '</div>'
    );
}]);

You can use it like that:

<button 
    confirm="delete(id)" 
    confirm-title="Really?"
    confirm-message="Really delete?"
    confirm-button-text="Delete"
    cancel-button-text="Cancel"
    class="btn btn-danger"
>
    Delete
</button>

Upvotes: 6

Views: 25329

Answers (2)

mccainz
mccainz

Reputation: 3497

N0-$watch solutions:

1.
Provide your controller with a callback which receives an exposed interface from your directive. Your controller grabs the interface and uses it in script however it desires. Simple and can be implemented on any existing directive.

Plnkr for interface callback

  app.directive("simpleDialog",function(simpleDialog){
    return{
      template:"<button ng-click='open()'>open from directive</button>",
      scope:{
        onInit : "&onInit"
      },
      link: function(scope){
        scope.open = simpleDialog.open;
        scope.onInit({interface:{open:scope.open}});
      }
    }
  });

Much more complicated but an excellent pattern...

2.
If you wish to make a directive which also has a programmable interface then I suggest implementing the core of the directive as a provider. You could then implement your directive based on the provider and in cases where you wanted to access the same functionality completely through script, you could operate directly on the provider by injecting it into your controller.

This is the implementation strategy followed by ngDialog

Also, as you are are creating a confirmation dialog you would find this pattern useful as your open method could return a promise which can be resolved or rejected by your dialog, allowing your controller to respond based on the promise.

PLNKR DEMO

<!DOCTYPE html>
<html>

  <head>
    <script data-require="angular.js@*" data-semver="1.3.0" src="//code.angularjs.org/1.3.0/angular.js"></script>
    <link rel="stylesheet" href="style.css" />
    
  </head>

  <body ng-controller="myCtrl">
  
    <h1>Exposing a  Directive interface to a controller</h1>
    <div simple-dialog on-init="initCallback(interface)"></div>
    <p><button ng-click="open()">Open from controller</button></p>
    <p><button ng-click="open2()">Open from Exposed interface</button></p>
    
    <script>
      
      
      var app = angular.module("app",[]);
      
      app.controller("myCtrl",function(simpleDialog,$scope){
        $scope.open = simpleDialog.open;
        $scope.open2 = function(){
          this.interface.open();
        }
        $scope.initCallback = function(interface){
          this.interface = interface;
        }
      });
      
    
      app.provider("simpleDialog",function(){
        
        this.$get = function(){
          
          var publicMethods={
            open:function(){
              alert("Impelment Dialog Here");
            }
          }
          
          return publicMethods;
          
        }
      });
      
      app.directive("simpleDialog",function(simpleDialog){
        return{
          template:"<button ng-click='open()'>open from directive</button>",
          scope:{
            onInit : "&onInit"
          },
          link: function(scope){
            scope.open = simpleDialog.open;
            scope.onInit({interface:{open:scope.open}});
          }
        }
      });
      
      angular.bootstrap(document,["app"]);
      
    </script>
  </body>
</html>

Upvotes: 7

JoseM
JoseM

Reputation: 4302

You can watch for a change in a scope property in your directive.

For example, add a confirm-show-when

<button 
    confirm="delete(id)" 
    ...
    confirm-show-when="state.showConfirmDialog"
    ...
>
    Delete
</button>

Add it to your directive definition

.directive('confirm', ['$modal', function($modal) {
    return {
        restrict: 'A',
        scope: {
            confirm: '&',
            title: '@confirmTitle',
            message: '@confirmMessage',
            confirmButtonText: '@confirmButtonText',
            cancelButtonText: '@cancelButtonText',
            showWhen: '=confirmShowWhen'
        },
        link: function(scope, element, attributes) {
            var showModal = function() {
                var modal= $modal.open({
                    controller: 'ConfirmModalController',
                    templateUrl: 'template/modal/confirm.html',
                    size: 'sm',
                    scope: scope
                });

                modal.result.then(function() {
                    scope.confirm();
                }, function() {
                    // Modal dismissed
                    // set showWhen back to false
                    scope.showWhen = false;
                });
            };
            element.bind('click', showModal);
            scope.$watch('showWhen', function(newVal) {
                if (newVal) {showModal()};
            });
        }
    };
}]);

And in your controller just set the showConfirmDialog to true when you want to show it.

// controller code
// set up the state object so we use the 'dot' notation
$scope.state = { showConfirmDialog: false };
// other controller code
if (userWantsToDelete) {
    $scope.state.showConfirmDialog = true;
}

Upvotes: 0

Related Questions