有条件地应用指令

160

我正在使用Material 2添加md-raised-button。我希望仅在特定条件成立时应用此指令。

例如:

<button md-raised-button="true"></button>

另一个例子: 我在 Plunker 中创建了一个基本的动态响应式表单。 我正在使用响应式表单的 formArrayName 指令来处理控件数组。 我想仅在特定条件为真时应用 formArrayName 指令,否则不添加 formArrayName 指令。

这里是一个Plunker链接


是的,md-raised-button是一个属性指令(https://material.angular.io/components/component/button)。 - user2899728
应用条件来使用指令可能会使AoT失去意义,因为除非应用程序正在运行,否则您将无法编译模板。 - Reactgular
17个回答

107

如果您只需要添加一个属性以触发CSS规则,则可以使用以下方法:(这不会动态创建/销毁指令)

<button [attr.md-raised-button]="condition ? '' : null"></button>

将其应用于您的plunker:分支

更新:

如何使用condition ? '' : null作为值:

当为空字符串时(''),它变为attr.md-raised-button = "",当为null时,该属性将不存在。

更新: plunker更新:分支 (版本问题已解决,请注意该问题最初是基于angular 4的)


1
所以,对于那些不想创建HTML元素以使他们的指令工作并且正在使用ng-container的人,我只是采用了传递一个[enabled] =“booleanVar”的方法。 - Ben Taliadoros
7
在Angular 6中对我不起作用。 Plunker示例无法通过加载屏幕。 根据我的阅读,这种方法可能适用于HTML属性,但对于Angular指令则不起作用。 最初的问题涉及来自ng材料库的Angular指令。 - JeffryHouser
@RezaRahmati 它仍在工作。如所请求,请参见此处 - Aldracor
@Aldracor 谢谢,顺便说一下,问题是关于Angular指令而不是简单的属性。请参见此链接:https://stackblitz.com/edit/angular-conditional-directive-2004 - Reza
15
谢谢你的留言,它适用于HTML属性,但不适用于作为属性的Angular指令。 - Reza
显示剩余11条评论

80

我不知道您是否可以根据条件应用指令,但是一个解决方法是有2个按钮,并根据条件显示它们

<button *ngIf="!condition"></button>
<button *ngIf="condition" md-raised-button></button> 

编辑:或许这篇文章会有所帮助。


17
谢谢您的回复。 但我已经在问题细节中添加的Plunker示例中使用了这种技术。如果代码比较复杂,这是一个好选择,但不是好主意。因为这种技术破坏了重用的概念。您可以查看我的Plunker示例,并注意到由于这种方法而导致我的代码重复的情况。这就是为什么我不想使用这种技术。我想要一些更好的解决方案,以便我可以生成动态元素。 - user2899728
1
我明白了。你可以将重复的代码封装在组件中。 - LLL
3
这是一个好主意,但不幸的是,在响应式表单中它行不通。 使用响应式表单时,会出现另一个问题。如果我将输入放入单独的组件中,则Angular还要求我在该子组件中添加[formGroup] =“form”。但是如果我这样做,我的数组绑定将无法工作。 - user2899728
60
那个解决方案很糟糕,因为它重复了代码。想象一下,这不仅仅是一个按钮,而是一个里面有很多 HTML 代码的 div。 - Shachar Har-Shuv
在包装 input 的子组件中,不必将 formGroup 包含进去,因为你可以通过在包装器组件内部传递 formControl 作为 @Input() control: FormControl 并通过 [formControl]="control" 应用于本地 input - Oleg K
2
不幸的是,Angular 没有提供任何优雅的方式,所以我们只能采用这种方式。 - oomer

44

正如已经提到的,这似乎是不可能的。可以使用ng-template来至少防止一些重复。这允许您提取受ngIf分支影响的元素的内容。

例如,如果您想使用Angular Material创建分层菜单组件:

<!-- Button contents -->
<ng-template #contentTemplate>
    <mat-icon *ngIf="item.icon != null">{{ item.icon }}</mat-icon>
    {{ item.label }}
</ng-template>

<!-- Leaf button -->
<button *ngIf="item.children == null" mat-menu-item
    (click)="executeCommand()"
    [disabled]="enabled == false">
    <ng-container *ngTemplateOutlet="contentTemplate"></ng-container>
</button>
<!-- Node button -->
<ng-container *ngIf="item.children != null">
    <button mat-menu-item
        [matMenuTriggerFor]="subMenu">
        <ng-container *ngTemplateOutlet="contentTemplate"></ng-container>
    </button>

    <mat-menu #subMenu="matMenu">
        <menu-item *ngFor="let child of item.children" [item]="child"></menu-item>
    </mat-menu>
</ng-container>

这里的有条件应用指令是matMenuTriggerFor,它只应用于具有子项的菜单项。通过ngTemplateOutlet,按钮的内容会被插入到两个位置。


13
这是唯一的答案,能够让开发者同时使用条件指令和代码重用。 - Ravinder Payal
这个解决方案非常优雅,谢谢! - Timtim
嵌套的ng-container(在Node按钮下面)让我感到困惑,这个解决方案只需要一个就足够了,对吗? - DFSFOT
1
@DFSFOT 其中一个用于if语句检查子元素,另一个用于呈现内容模板。关键是模板呈现,其余部分仅供示例。 (外部ng-container也可以是其他内容,例如<div>,具体取决于所需的文档结构。) - H.B.
然而,如果您的父容器依赖于ContentChildren,则这不是一个选项。例如,mat-table组件内部元素不能放入模板插座中,因为找不到所需的标题、行和页脚定义。不幸的是,这就是我目前卡住的地方,必须复制一些HTML。 - B. Feenstra
这个解决方案确实帮助我们防止按钮内容的重复,但仍无法解决按钮自身属性的重复问题:叶子和节点可能具有非常不同的属性集,您将不得不复制所有属性。 - undefined

30
这可能有点晚,但这是一种可行且优雅的方法,可以有条件地应用指令。
在指令类中创建输入变量:

这可能有些晚了,但这是一种可行且优雅的方法,可以有条件地应用指令。

在指令类中创建输入变量:

@Input('myDirective') options: any;

应用指令时,设置输入变量的apply属性:

<div [myDirective] = {apply: someCondition}></div>

在指令的方法中,检查变量 this.options.apply 并根据条件应用指令逻辑。

ngAfterViewInit(): void {
    if (!this.options.apply) {
        return;
    }

    // directive logic
}

7
对我来说,这并不起作用,因为指令仍然存在于组件中,只是没有执行任何逻辑。我希望指令的存在可以有条件地应用,具体原因是因为CSS检查了该指令。 - Ran Lottem
你遇到了一个不同的问题,与这里列出的问题(有条件地应用指令)不同。请发布您的问题,人们会帮助您。作为第一个想法,您可以将指令放在一个div上,并在其周围放置一个带有*ngIf的ng-container以应用您的条件。ngIf从DOM中删除div节点,因此它可能有效。我只是猜测,您需要发布带有代码示例/描述的问题,以便其他人帮助您。 - Tiha
1
这不是一个好的解决方案。指令仍然会被初始化。 - Dino
这个解决方案只适用于您使用自定义指令的情况。如果您使用的是来自库的指令,而您无法访问其逻辑(或不应该触及它),则无法实现此功能。 - Sixteen

12

正如其他人所说,directive 无法动态应用。

然而,如果您只想从 flat 切换到 raised 来切换 md-button 的样式,则可以使用以下内容:

<button md-button [class.mat-raised-button]="isRaised">Toggle Raised Button</button>

这样就可以解决问题了。 Plunker


请注意,仅使用 mat-raised-button 类将无法与 Ripple 一起使用。 - ZecKa

6

目前,没有条件性地将指令应用于组件的方法。这是不被支持的。您创建的组件可以被有条件地添加或删除。

已经为同一问题创建了一个问题 angular2,所以在 angular4 中也应该是这样的。

或者您可以选择使用 ng-if 选项。

<button ngIf="!condition"></button>
<button ngIf="condition" md-raised-button></button> 

3
已有LLL的回答中涉及此内容。 - Sumit Ramteke

6

也许对某些人有帮助。

在下面的示例中,我有一个my-button.component.html文件,并且只想在role属性被设置时将*appHasPermission指令应用于<button>元素。

<ng-container *ngIf="role; else buttonNoRole" >
  <ng-container *appHasPermission="role">
    <!-- button with *appHasPermission -->
    <ng-template *ngTemplateOutlet="buttonNoRole;"></ng-template>
  </ng-container>
</ng-container>

<ng-template #buttonNoRole>
  <!-- button without *appHasPermission -->
  <button
    mat-raised-button type="button"
    [color]="color"
    [disabled]="disabled"
    [(appClickProgress)]="onClick"
    [key]="progressKey">
    <mat-icon *ngIf="icon">{{ icon }}</mat-icon> {{ label }}
  </button>
</ng-template>

那样你就不会重复 <button> 的代码。

你仍然需要编写两个按钮的代码,因此您可以使用双引号*ngIf="condition" 来控制它。 - DFSFOT
似乎有误,你所写的内容并不需要模板。如果有模板的话,在这种情况下至少应该使用两次。 - Adeel Shekhani

3
这也可以是一个解决方案: [md-raised-button]="condition ? 'true' : ''"
在 Angular 4 和 Ionic 3 中,它可以这样工作: [color]="condition ? 'primary' : ''" 其中 `condition` 是一个函数,决定这是否是一个活动页面。整个代码如下所示: <button *ngFor="let page of ..." [color]="isActivePage(page) ? 'primary' : ''">{{ page.title }}</button>

2
传递 null 给指令会将其移除!
<button md-raised-button="condition ? true : null"></button>

6
仅适用于有条件地删除HTML标签属性,而不适用于Angular指令。 - masterxilo

1

我找不到一个好的现有解决方案,所以我建立了自己的指令来做到这一点。

import { Directive, ElementRef, Input } from '@angular/core';

@Directive({
  selector: '[dynamic-attr]'
})
export class DynamicAttrDirective {
  @Input('dynamic-attr') attr: string;
  private _el: ElementRef;

  constructor(el: ElementRef) {
    this._el = el;
  }

  ngOnInit() {
    if (this.attr === '') return null;
    const node = document.createAttribute(this.attr);
    this._el.nativeElement.setAttributeNode(node);
  }
}

然后是你的HTML代码:

你可以使用@HostBinding('attr.dynamic-attr') @Input('dynamic-attr') attr: string;代替ngOnInit + ElementRef。 - pinguinjkeke
4
这不是对问题的回答,问题是如何动态添加一个指令,而不是属性。 - Chris Haines

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接