同一元素中的多个指令对可见性进行操作

Multiple directives in same element operating on visibility

我一直在努力解决以下情况。 我有一个自定义指令 authorize,我在其中传递组名。如果当前用户在他的个人资料中有这个组,那么该元素将可见,否则该元素将被隐藏。 示例:

<button class="btn btn-default" role="button"
        ng-click="myVm.edit()"
        authorize="{{myVm.groupName}}"><!--groupName = "accountants"-->
    <span class="fa fa-edit" aria-hidden="true"></span> Edit
</button>

和我在 typescript authorize.ts 中使用 link 函数的原始指令(因为我在 DOM 上操作)

namespace app.blocks.directives {
    "use strict";

    class AuthorizeDirective implements ng.IDirective {

        public restrict: string = "A";

        public replace: boolean = true;

        constructor(private $compile: ng.ICompileService, private authService: services.IAuthService) {
        }

        public static factory(): ng.IDirectiveFactory {
            const directive = ($compile: ng.ICompileService, authService: services.IAuthService) =>
                new AuthorizeDirective($compile, authService);
            directive.$inject = [
                "$compile",
                "app.services.AuthService"
            ];
            return directive;
        }

        public link(scope: ng.IScope, instanceElement: ng.IAugmentedJQuery, instanceAttributes: ng.IAttributes): void {
            let groupName: string = (<any>instanceAttributes).authorize;
            let element = angular.element(instanceElement);
            let hasGroup: boolean = this.authService.hasGroup(groupName);
            element.attr("ng-show", String(hasGroup));
            //remove the attribute, otherwise it creates an infinite loop.
            element.removeAttr("authorize");
            this.$compile(element)(scope);
            }
        }
    }

    angular
        .module("app.blocks.directives")
        .directive("authorize", AuthorizeDirective.factory());
}

这工作正常,如果 authService returns false 因为用户不属于该组(即:"accountants"),则按钮被隐藏。

当我的 DOM 元素也有 ng-showng-hide 指令时出现问题。示例:

<button class="btn btn-default" role="button"
        ng-hide="myVm.isDeleted"
        ng-click="myVm.edit()"
        authorize="{{myVm.groupName}}">
    <!--groupName = "accountants"-->
    <span class="fa fa-edit" aria-hidden="true"></span> Edit
</button>

myVm.isDeleted = true 似乎覆盖了我的指令的结果并且显示了 DOM 元素(当它不应该因为用户不属于我的指定组 authorize指令)。

我意识到指令中有一些优先级(默认情况下 0),当两个指令具有相同的优先级时,它们将根据文档按字母顺序执行。 This post 对理解这一点很有帮助。

所以我在这里有一些选择:

  1. 让我的授权指令评估 ng-hideng-show 中的条件以进行计算(即:如果 ng-hide 表示应该显示该元素但用户没有特定的组,则应隐藏该元素)。 我无法在指令 link 的函数 中找到访问 myVm.isDeleted 的方法。如果有人知道我会对这种方法感到满意。

  2. 让我的 authorize 指令在任何其他指令之前执行,并依赖 angular 稍后根据 ng-showng-hide 确定可见性(即:如果我的 authorize 指令确定该元素应该被隐藏,因为用户不属于给定的组,那么它应该转换 DOM 元素并使其成为 ng-show="false" 例如,以便 angular 稍后隐藏元素。这种方法似乎不起作用, DOM 似乎是正确的,我可以看到按钮有 ng-show="false" 但出于某种原因,我仍然在屏幕上看到按钮 所以就好像 Angular 不知道它必须隐藏该元素。有趣的是,如果我移动到另一个选项卡,然后我回到同一个选项卡(重新加载视图并重新执行指令)然后它工作正常。发生了什么事?

我选择了选项 2,这是似乎可以正常操作 DOM 的代码,但是 Angular 之后不应用 ng-show 指令,因此结果并不像预期。

public priority: number = 999; //High priority so it is executed BEFORE ng-show directive

public link(scope: ng.IScope, instanceElement: ng.IAugmentedJQuery, instanceAttributes: ng.IAttributes): void {
    let groupName: string = (<any>instanceAttributes).authorize;
    let element = angular.element(instanceElement);
    let ngShow: string = (<any>instanceAttributes).ngShow;
    let ngHide: string = (<any>instanceAttributes).ngHide;
    let hasGroup: boolean = this.authService.hasGroup(groupName);
    let ngHideValue = ngHide ? "!" + ngHide : "";
    let ngShowValue = ngShow ? ngShow : "";
    //if hasGroup, use whatever ng-show or ng-hide value the element had (ng-show = !ng-hide).
    //if !hasGroup, it does not matter what value the element had, it will be hidden.
    if (hasGroup) {
        element.attr("ng-show", (ngShowValue + ngHideValue) || "true");
    } else {
        element.attr("ng-show", "false");
    }
    element.removeAttr("ng-hide");
    //remove the attribute, otherwise it creates an infinite loop.
    element.removeAttr("authorize");
    this.$compile(element)(scope);
}

我认为,鉴于您的 authorize 指令基本上只是控制它所放置的元素是否显示,您应该将其逻辑移出到您注入控制器的服务中,并且让 ng-hide 控制元素是否按设计显示。

这对于后来的开发人员来说更容易理解 - 没有人愿意深入研究各个指令以查找调用服务器的各种零散代码,然后您的按钮看起来像这样:

<button class="btn btn-default" role="button"
    ng-hide="myVm.isDeleted || !myVm.isAuthorized(myVm.groupName)"
    ng-click="myVm.edit()">
    <span class="fa fa-edit" aria-hidden="true"></span> Edit
</button>

简洁易读。