In a custom directive, what functionality does `co

2019-09-12 09:11发布

问题:

In trying to get a grasp on creating my own AngularJS directives, I have an example that does everything I need, but realize that in borrowing from various examples, I now can create functionality for the directive's view in both the controller as well as the link.

It seems that I could get rid of the controller all together and just put everything into link, or is there something that I can do with the controller that I can't do with link?

http://jsfiddle.net/edwardtanguay/gxr49h96/6

.directive('itemMenu', function () {

    var controller = function ($scope) {
        var vm = this;
        vm.addItem = function () {
            $scope.add();
            vm.items.push({
                'kind': 'undefined',
                    'firstName': 'Joe',
                    'lastName': 'Newton',
                    'age': Math.floor(Math.random() * 60) + 20
            });
        };

        // DOES THE SAME AS THE FUNCTION DEFINED BELOW IN LINK        
        //        $scope.convertToInternal = function(item) {
        //            item.internalcode = 'X0000';
        //            item.kind = 'internal';
        //        };        
    };

    return {
        restrict: 'A',
        scope: {
            item: '=',
            add: '&'
        },
        controller: controller,
        controllerAs: 'vm',
        bindToController: true,
        template: '<div ng-include="getTemplateUrl()"></div>',
        link: function (scope, element, attrs) {
            scope.getTemplateUrl = function () {
                switch (scope.item.kind) {
                    case 'external':
                        return 'itemMenuTemplateExternal';
                    case 'internal':
                        return 'itemMenuTemplateInternal';
                    default:
                        return 'itemMenuTemplateUndefined';
                }
            };

            scope.convertToInternal = function(item) {
                item.internalcode = 'X0000';
                item.kind = 'internal';
            };
        },
    };

})

回答1:

You may find a lot of watery rants about controller vs link, most of them contain the information from $compile service documentation.

Answering the question directly,

  • controllers from other modules/files can be plugged into the directive via Angular DI with controller: 'Controller'

  • controller can be injected with dependencies while link has fixed arguments list of and gets by with directive's dependencies

  • controller kicks in before link, so it can prepare the scope for linking or recompile the element on some condition ASAP

  • controller function has this, its code appearance complies to other OOP-like ES5 code, and the methods can be easily transferred between other code parts, e.g. service service or third-party code

  • as the result, controllers are suited to be defined as ES2015 or TS classes.

  • directive's controller can be required by child directive and provides convenient one-way interaction between those two

  • controller makes use of bindToController: true + controllerAs: 'vm' and implements $scope.vm recipe (particularly useful to fight JS prototypal inheritance) while keeping this syntax

  • bindToController object value provides attribute bindings for inherited scope: true scope, no more $attr.$observe

  • bindToController object value provides further granularity for isolated scope. If there are certain attributes that should be bound to the controller and accessed with require, it can be done now

Which code goes to controller and which to link is more delicate question.

It is conventional to use Angular controllers as view models, as in MVVM (hence the controllerAs: 'vm' convention), so if there is a job for controllers (i.e. binding the services to scope values or setting up scope watchers) give it to them, leave the rest to link.

Since $attrs, $element and $transclude local dependencies should be injected into controller explicitly, the one may consider it a sign to skip them ($scope should be injected too for $scope.$ methods, but we will just ignore this fact).

There are some non-religious concerns about the job that should be done by link and not by controller. required controllers aren't available in controller itself, so this kind of directive interaction takes place in link. Since controller launches at earlier compilation phase than link, bound attribute values won't be interpolated yet, so the code that depends on these scope values goes to link. The same applies to other DOM-related code, it goes to link for a reason.

It is mostly the matter of proper code style and not real necessity. As of the code in the example, all scope stuff is controller-friendly, I don't think that link should be there at all.