【问题标题】:Multiple directives in same element operating on visibility同一元素中的多个指令对可见性进行操作
【发布时间】:2017-03-17 18:30:30
【问题描述】:

我一直在努力解决以下情况。 我有一个自定义指令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 中使用链接函数的原始指令(因为我在 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 返回 false 因为用户不属于该组(即:"accountants"),则隐藏按钮。

当我的 DOM 元素也有 ng-show 或 ng-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-hide 或 ng-show 中的条件以便计算(即:如果 ng-hide 说应该显示元素但用户没有特定组,那么该元素应该被隐藏)。 我在指令链接的函数中找不到访问myVm.isDeleted 的方法。如果有人知道我会对这种方法感到满意。

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

我选择了选项 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);
}

【问题讨论】:

  • 一个明显的解决方法是将我的按钮包装在一个 div 中,并在 div 本身上应用 authorize 指令,让按钮中的 ng-hide 不会在按钮本身上产生多个指令的冲突.但我想为我的问题找到一个解决方案,并理解为什么 angular 不会重新计算 ng-show="false"

标签: angularjs typescript angularjs-directive ng-show angularjs-controlleras


【解决方案1】:

我认为你的 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>

很好读,而且简单。

【讨论】:

  • 我同意你的观点,依靠ng-hide 或ng-show 根据服务的结果来做他们知道的事情(隐藏或显示元素)似乎是一个更好、更易读的解决方案,但它不能解决在指令的link 函数中访问 vm 属性或在我的指令完成后重新计算 DOM 的问题,因为我给了它更高的优先级,所以我不能将它标记为已解决:-)
  • 我敢肯定,如果你想搞乱DOM预编译,你不能在链接中进行
  • 嗯...那你有一些问题。据我所见,您的 vm 属性 应该 已经可见,因为没有隔离范围。如果在您的解决方案中您需要在编译之前更改 DOM,则不能在链接函数中执行此操作 - 您必须在 compile 函数中执行此操作,如 Angular docs 中所述
  • 感谢您的回复。关于 vm 是可见的(我应该提到这一点),它是。它在注入范围内,因此我可以使用scope.vm 访问它。我没有使用这种方法,因为在某些视图中控制器的名称可能是 vm,但在其他一些视图中它可能是 otherVm,所以我想有一些通用的方法来访问它。
  • 很高兴为您提供帮助 :) 虽然我仍然认为您最好的选择是使用 ng-hide,但如果您确实需要在 vm 上传递信息以用于您的指令,您可以尝试使用另一个属性在你的指令应该引用的范围内存储属性的名称。
猜你喜欢
  • 1970-01-01
  • 2016-01-27
  • 1970-01-01
  • 2011-07-08
  • 1970-01-01
  • 2020-03-31
  • 2011-06-17
  • 2014-07-23
  • 2013-06-04
相关资源
最近更新 更多