Skylude
Skylude

Reputation: 504

Calling passed in function inside AngularJS directive

I am creating a drag directive to add an event listener to the dragstart event. I want to pass a function from my controller to the directive. When I add the event listener it will not invoke the passed in function.

Here is my controller:

angular.module('testApp').controller('testCtrl', [testCtrl]);

function testCtrl() {
    var vm = this;
    vm.dragStart = dragStart;

    function dragStart(e){
        alert('drag started!', e);
    }

}

Here is my directive:

angular.module('testApp').directive('testDraggable', function(){
    var directive = {
        scope: {
            dragStart: '&',
        },
        restrict: 'A',
        link: link
    };

    function link(scope, element, attrs){
        var dragStartCallback = function(event){
            alert('dragStartCallback!');
            scope.dragStart({e: event});
        }

        element[0].addEventListener('dragstart', dragStartCallback, false);
    }
    return directive;
});

The issue I am having is that the dragStartCallback function is called but the inner scope.dragStart function is never called. I've read about mapping the parameters which is what I'm doing and it is still failing. In the dragStartCallback I am getting the event passed in properly as well. If there is a better way to go about doing this any advice would be appreciated.

Thanks in advance for any of your input. There is a JS Fiddle here: http://jsfiddle.net/6sk4dbre/

Upvotes: 2

Views: 1066

Answers (3)

j.wittwer
j.wittwer

Reputation: 9497

There is a problem with using the name dragStart in your directive scope binding. Change it to something else, and your code will work.

var directive = {
    scope: {
        ds: '&',
    },

http://jsfiddle.net/wittwerj/7vnxjxsq/

As pointed out by @miensol, this is due to a change documented in the Migrating from 1.0 to 1.2 guide:

Directives cannot end with -start or -end
This change was necessary to enable multi-element directives. The best fix is to rename existing directives so that they don't end with these suffixes.

Apparently this applies to attributes as well.

Upvotes: 2

miensol
miensol

Reputation: 41678

As stated by @j.wittwer if you change the name of scope binding i.e.

scope: {
  dragStart:'&startDragging'
}

and then use it in directive as start-dragging="vm.startDrag(e)" it will work.

This is related to a feature introduced to ng-repeat (in version 1.2) where you can define header body and footer in repeated elements using ng-repeat-start and ng-repeat-end. You can find more about this syntax in documentation.

It seems rather unfortunate that this change that affects all directives (and attributes) in angular (at least in version 1.2.1) was only mentioned in migration guide and is really easy to miss especially if you don't know what to look for.

It's interesting however to know why this behaviour is present int angular.

The culprit code can be found in collectDirectives (line 5600 in version 1.2.1) where you can see:

var directiveNName = ngAttrName.replace(/(Start|End)$/, '');
if (ngAttrName === directiveNName + 'Start') {
    attrStartName = name;
    attrEndName = name.substr(0, name.length - 5) + 'end';
    name = name.substr(0, name.length - 6);
}

In current source on github mentioned code is changed to:

var directiveNName = ngAttrName.replace(/(Start|End)$/, '');
if (directiveIsMultiElement(directiveNName)) {
    if (ngAttrName === directiveNName + 'Start') {

so the behaviour in question will only affect directives having multiElement: true which is a change introduced in this pull request.

Upvotes: 2

m.e.conroy
m.e.conroy

Reputation: 3538

@j.wittwer is right that it had to do with the name of the element attribute dragStart but its not because of what the name is, its because for some reason Angular doesn't like hyphenated attributes while passing functions to directive attributes. This only happens for passing parent controller functions to a directive. If you make it dragstart instead of drag-start it would work fine, of course then your directive needs to have:

scope : {
    ds : '&dragstart'
}

http://jsfiddle.net/6sk4dbre/5/

Upvotes: 1

Related Questions