自定义ng-if指令angularjs

时间:2017-12-10 14:08:21

标签: javascript angularjs

我正在尝试创建一个类似于 ng-if 指令的指令,所以我想抓住它的功能。我创建了一个这样的指令:

(function () {
    'use strict';

    angular.module('sapphire.directives').directive('ifScreensize', directive);

    function directive(ngIfDirective, $window) {
        var ngIf = ngIfDirective[0];

        return {
            controller: 'IfScreensizeController',
            prority: 1,
            scope: {
                options: '=ifScreensize'
            },
            link: function (scope, element, attrs, controller) {
                scope.$watch('options', function (options) {
                    controller.handle(element, options, ngIf);
                });

                var window = angular.element($window)
                window.bind('resize', function () {
                    $timeout(function () {
                        controller.handle(element, scope.options, ngIf);
                    }, 500);
                });
            }
        };
    };
})();

然后控制器看起来像这样:

(function () {
    'use strict';

    angular.module('sapphire.directives').controller('IfScreensizeController', controller);

    function controller(ifScreensizeService) {
        this.handle = ifScreensizeService.handle;
    };
})();

最后,服务看起来像这样:

(function () {
    'use strict';

    angular.module('sapphire.directives').service('ifScreensizeService', service);

    function service($window) {
        return {
            handle: handle
        };

        //////////////////////////////////////////////////

        function handle(element, options, ngIf) {
            var window = angular.element($window),
                width = $window.innerWidth,
                value = true;

            switch (options.operator) {
                case '>':
                    value = options.width >= width;
                    break;
                case '>=':
                    value = options.width > width;
                    break;
                case '<':
                    value = options.width < width;
                    break;
                case '<=':
                    value = options.width <= width;
                    break;
                default:
                    break;
            }

            ngIf.link.apply(ngIf, value);
        };
    };
})();

问题是,当我尝试使用该指令时,我收到一个错误:

  

TypeError:在非对象上调用CreateListFromArrayLike

哪个位于ngIf.link.apply(ngIf, value);行。 有人能告诉我我需要做些什么来使指令工作吗?

2 个答案:

答案 0 :(得分:0)

.apply采用数组。尝试以ngIf.link.apply(ngIf, [value]);

的方式调用它

答案 1 :(得分:0)

好的,所以我使用实际的ng-if directive代码来创建我的指令。 所以,我把指令改为:

angular.module('sapphire.directives').directive('ifScreensize', directive);

function directive($timeout, $window) {
    return {
        controller: 'IfScreensizeController',
        multiElement: true,
        transclude: 'element',
        priority: 600,
        terminal: true,
        restrict: 'A',
        $$tlb: true,
        bindToController: {
            options: '=ifScreensize'
        },
        link: function (scope, element, attrs, controller, transclude) {
            scope.$watch('options', function (options) {
                controller.handle(element, attrs, transclude);
            });

            var window = angular.element($window)
            window.bind('resize', function () {
                $timeout(function () {
                    controller.handle(element, attrs, transclude);
                }, 500);
            });
        }
    };
};

我更改了控制器以模仿 ng-if 指令,如下所示:

angular.module('sapphire.directives').controller('IfScreensizeController', controller);

function controller($animate, $compile, ifScreensizeService) {
    var self = this;        
    var block, childScope, previousElements;

    self.handle = function handle($element, $attr, $transclude) {
        var value = ifScreensizeService.evaulate(self.options);

        console.log(value);

        if (value) {
            if (!childScope) {
            $transclude(function(clone, newScope) {
                childScope = newScope;
                clone[clone.length++] = $compile.$$createComment('end ngIf', $attr.ngIf);
                // Note: We only need the first/last node of the cloned nodes.
                // However, we need to keep the reference to the jqlite wrapper as it might be changed later
                // by a directive with templateUrl when its template arrives.
                block = {
                clone: clone
                };
                $animate.enter(clone, $element.parent(), $element);
            });
            }
        } else {
            if (previousElements) {
                console.log(previousElements);
                previousElements.remove();
                previousElements = null;
            }
            if (childScope) {
                childScope.$destroy();
                childScope = null;
            }
            if (block) {
                previousElements = ifScreensizeService.getBlockNodes(block.clone);
                $animate.leave(previousElements).done(function(response) {
                    if (response !== false) previousElements = null;
                });
                block = null;
            }
        }
    };
};

那里的大部分代码都可以在 ng-if 指令中找到。我只是略微修改它以使用我的指令。 需要注意的一点是,在原来的 ng-if 指令中,它会调用我们无法访问的getBlockNodes,所以我将其添加到我的服务中:

angular.module('sapphire.directives').service('ifScreensizeService', service);

function service($window) {
    var slice = [].slice;
    return {
        evaulate: evaulate,
        getBlockNodes: getBlockNodes
    };

    //////////////////////////////////////////////////

    function evaulate(options) {
        var window = angular.element($window),
            width = $window.innerWidth,
            value = true;

        switch (options.operator) {
            case '>':
                value = width >= options.width;
                break;
            case '>=':
                value = width > options.width;
                break;
            case '<':
                value = width < options.width;
                break;
            case '<=':
                value = width <= options.width;
                break;
            default:
                break;
        }

        console.log(options, width, value);

        return value;
    };

    function getBlockNodes(nodes) {
        // TODO(perf): update `nodes` instead of creating a new object?
        var node = nodes[0];
        var endNode = nodes[nodes.length - 1];
        var blockNodes;

        for (var i = 1; node !== endNode && (node = node.nextSibling); i++) {
            if (blockNodes || nodes[i] !== node) {
                if (!blockNodes) {
                    console.log(nodes);
                    blockNodes = angular.element(slice.call(nodes, 0, i));
                }
                blockNodes.push(node);
            }
        }

        return blockNodes || nodes;
    };
};

最后一个警告是这一行:

blockNodes = angular.element(slice.call(nodes, 0, i));

在原来的 ng-if 中,实际上是:

blockNodes = jqLite(slice.call(nodes, 0, i));

我努力让这个工作,但基本上方法 jqLit​​e 实际上正在进行angular.element()调用。 切片方法将无效,除非您执行我在服务顶部执行的var slice = [].slice;

我希望这有助于其他人:)