Angular: Looking for an explanation on custom dire

2019-08-02 03:04发布

问题:

I copied a custom directive that watches for changes on form file inputs.

angular.module('customDirective', [])
.directive('ngFileInputChange', function() {
    return {
        restrict: 'A',
        link: function (scope, element, attrs) {
            var onChangeHandler = scope.$eval(attrs.ngFileInputChange);
            element.bind('change', onChangeHandler);
        }
    };
});

Then I use it in the template like so:

<input ng-model="vm.image" ng-file-input-change="vm.base64Test" ...>

And this works fine.

However, this doesn't work:

<input ng-model="vm.image" ng-file-input-change="vm.base64Test()" ...>

Note the () at the end of base64Test(). With a generic ng-click, the parenthesis are fine, or even required (how else would I pass arguments to the function?) but with the custom directive, parenthesis cause an error.

I only have a vague understanding of what scope.$eval(attrs.ngFileInputChange) is actually doing, so maybe I could make some change in that?

EDIT:

I should also add that I need access to some scope variables. For example, if I didn't have to use the custom directive, I would write something like this in my controller:

vm.base64Test(associatedData){
    console.log(associatedData);    
}

And then:

<input ng-change("vm.base64Test(vm.associatedData)">

But ng-change doesn't watch file input contents and the custom directive doesn't allow arguments (other than event), so I'm stuck.

回答1:

Firstly, if you're planning on calling a method from a directive's API (so to speak), you need to identify it as a callback:

scope: {
    'onChange': '&ngFileInputChange'
}

This simply means you're defined/exposing a way to declare a callback when the scope.onChange gets triggered. It also means you don't need to $parse or $eval since the isolate scope can handle that for you.

Next you can trigger that callback much like you're already doing:

element.bind('change', function(evt){
    $scope.$apply(function(){
        var payload = { $data:<whatever-you-pull-from-your-file-change-evt> }
        $scope.onChange(payload)
    }) 
});

One thing to note is that I'm calling the callback within the $scope.$apply. Because you're listening on non-angular events (e.g. onChange), you need to notify angular a change has occurred outside of the $digest cycle.

Where you've declared your directive, in your actual callback (probably defined on your view controller) you can pass the payload to the method like so:

<input ng-file-input-change="vm.doSomething($data)">


回答2:

When passing a function to a directive you usually need to pass the function pointer, so the directive can call the function later with arbitrary arguments.

In this case, your directive is evaluating the attribute to a function pointer (that's what the $eval is doing), and then binding it as a change handler to the element the directive is attached to. You don't need (or want) to have parenthesis.

There's nothing wrong with your code as it is currently. I have a directive which does the exact same thing, it works great! (and when your function vm.base64Test is called you will get the standard arguments for a change event on an input.) So you can have vm.base64Test be:

function(event) {
  console.log(event);
  // your other code here
  // event.target will give you the input element the code was called on
  // event.target.files will give you an array of the files selected by the user
}