21

is it possible to write an interceptor for ng-click? I have a button or a link that leads to a deletion of an object in the backend. I'd like to have a confirmation dialog (modal) by just adding an attribute to the button/link. E.g.:

<a href="#" ng-click="deleteIt(id)" confirmation-needed>Delete</a>

Is this possible with AngularJS? Is there a better approach do solve this issue?

EDIT The deleteIt method resides in different controllers.

Thx

Soccertrash
  • 1,830
  • 3
  • 28
  • 48

4 Answers4

50

I've put an example directive in:

http://plnkr.co/edit/GJwK7ldGa9LY90bMuOfl?p=preview

I achieve it by creating a directive:

  1. with a higher priority than ngClick, so that it gets called before ngClick,
  2. making that terminal so that it doesn't call ngClick.
  3. listening to click events, and then evaluating the ngClick value if the message is ok.

As a bonus, you can pass in your own message, such as:

<a href="#" ng-click="deleteIt(id)" 
    confirmation-needed="Really Delete?"
        >Delete with custom message</a>

The code looks like this:

app.directive('confirmationNeeded', function () {
  return {
    priority: 1,
    terminal: true,
    link: function (scope, element, attr) {
      var msg = attr.confirmationNeeded || "Are you sure?";
      var clickAction = attr.ngClick;
      element.bind('click',function () {
        if ( window.confirm(msg) ) {
          scope.$eval(clickAction)
        }
      });
    }
  };
});
starball
  • 20,030
  • 7
  • 43
  • 238
Piran
  • 7,180
  • 1
  • 24
  • 37
  • This is close how I would have done it too.. But, here is another plunk that works pretty much the same except it doesnt care whether you have an ng-click or not.. http://plnkr.co/edit/Cwu15Z8U6ok0MMF8p6Ks?p=preview – ganaraj Apr 25 '13 at 12:27
  • 1
    @ganaraj: That doesn't work when I click Cancel, because JQLite's `createEventHandler` continues to iterate through all events bound, and doesn't stop the second `bind('click',...)` from being called. (My browser: Chrome Version 26.0.1410.65 on Mac OSX 10.6.8) – Piran Apr 26 '13 at 10:02
  • 5
    This ALSO prevents all other directives from working, i.e. if you add an ng-show to the same tag... – samson Aug 16 '13 at 17:41
  • @samson Agreed - elegant solution but creates other problems. I ended up using an isolated scope with a custom '&' binding. – georgiosd Sep 13 '13 at 18:21
  • @georgiosd I've posted my approach below, which is similar to this one, but (I think) much simpler. – samson Sep 13 '13 at 19:06
  • I've posted another answer below that allows you to keep ng-click functionality and your own scope (without going terminal). – Dustin May 17 '14 at 04:07
10

I've given up on getting Piran's answer to work as I'd like it to. Instead, I've started just replacing ngClick with my own directive:

.directive('confirmClick', function() {
    return {
        restrict: 'A',
        link: function(scope, elt, attrs) {
            elt.bind('click', function(e) {
                var message = attrs.confirmation || "Are you sure you want to do that?";
                if (window.confirm(message)) {
                    var action = attrs.confirmClick;
                    if (action)
                        scope.$apply(scope.$eval(action));
                }
            });
        },
    };
})

This directive doesn't even have its own scope, which simplifies combining it with other directives significantly. It takes everything it needs directly from the attrs. It's used like so:

<span ng-show="editing" confirm-click="delete()" confirmation="delete confirmation message goes here">some text</span>

The delete() function must exist somewhere higher up in the $scope chain. I'm sure this could be improved by taking a closer look at how ng-click is implemented, but I haven't gotten around to it yet!

samson
  • 1,152
  • 11
  • 23
5

I played with this for a long time, but the async nature of Angular makes it tough to count on every directive playing nicely together. Then, I saw @tosh's answer, which got me thinking.

I think this combines several advantages: Create a Directive and Prepend ngClick

So, just add this directive to your app, and then add the "confirm-click" attribute to your link, as well as the confirmClick() function to your ngClick.

Link:

<a href="#" ng-click="confirmClick() && deleteIt(id)" confirm-click>Delete</a>

Directive:

.directive('confirmClick', function() {
    return {
        link: function (scope, element, attrs) {
            // setup a confirmation action on the scope
            scope.confirmClick = function(msg) {
                // msg can be passed directly to confirmClick('are you sure?') in ng-click
                // or through the confirm-click attribute on the <a confirm-click="Are you sure?"></a>
                msg = msg || attrs.confirmClick || 'Are you sure?';
                // return true/false to continue/stop the ng-click
                return confirm(msg);
            }
        }
    }
})

This is very similar to injecting the standard confirm() dialog, but because you have it in a directive, you can customize how you choose to run your confirm dialog (maybe a pretty modal, instead of a window dialog).

**** BONUS ANSWER ****

I played with this more and integrated a solution that uses a modal window as the confirmation, rather than the default window. Here's the complete solution: https://stackoverflow.com/a/23718694/1135826

Community
  • 1
  • 1
Dustin
  • 846
  • 8
  • 13
3

You can inject $window into your controller so that you can use $window.confirm from your scope, then:

<a href="#" ng-click="confirm('message') && deleteIt(id)">Delete</a>
Tosh
  • 35,955
  • 11
  • 65
  • 55
  • Thanks, @tosh! This put me on the right track for what I needed to do. I posted another answer below that takes the same concept but couples it with a directive, so I can customize my confirm dialog. – Dustin May 17 '14 at 04:15