Apply a directive conditionally

112,531

Solution 1

I don't know if you can apply directives based on a condition, but a workaround would be having 2 buttons and display them based on a condition.

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

Edit: maybe this will be helpful.

Solution 2

If you just need to add an attribute in order to trigger CSS rules, you can use the below method: (this does not dynamically create/destroy a directive)

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

Applied the same to your plunker: fork

Update:

How condition ? '' : null works as the value:

When its the empty string ('') it becomes attr.md-raised-button="", when its null the attribute will not exist.

Update: plunker update: fork (version issues fixed, please note the question was originally based on angular 4)

Solution 3

As already noted this does not appear to be possible. One thing that can be used to at least prevent some duplication is ng-template. This allows you to extract the content of the element affected by the ngIf branching.

If you for example want to create a hierarchical menu component using 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>

Here the conditionally applied directive is matMenuTriggerFor, which should only be applied to menu items with children. The contents of the button are inserted in both places via ngTemplateOutlet.

Solution 4

This may come late, but it is a viable and elegant method for applying a directive conditionally.

In the directive class create the input variable:

@Input('myDirective') options: any;

When applying the directive, set the apply property of the input variable:

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

In the method of the directive check for the variable this.options.apply and apply the directive logic based on the condition:

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

    // directive logic
}

Solution 5

As others have also stated, directives can't be dynamically applied.

However, if you just want to toggle md-button's style from flat to raised, then this

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

would do the trick. Plunker

Share:
112,531
user2899728
Author by

user2899728

Updated on July 08, 2022

Comments

  • user2899728
    user2899728 almost 2 years

    I am using Material 2 to add md-raised-button. I want to apply this directive only if certain condition becomes true.

    For example:

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

    Another example: I created a basic dynamic reactive form in plunker. I am using formArrayName directive of reactive form for array of controls. I want to apply formArrayName directive only if specific condition becomes true, otherwise don't add formArrayName directive.

    Here is a plunker link.

    • user2899728
      user2899728 almost 7 years
      Yes md-raised-button it is attribute directive (material.angular.io/components/component/button)
    • Reactgular
      Reactgular over 6 years
      Applying conditions on which directives are used would likely render AoT useless as you wouldn't be able to compile the templates unless the app was running.
  • user2899728
    user2899728 almost 7 years
    This is not what I asked. You are showing example for class. I am talking about attribute-directive.
  • Edric
    Edric almost 7 years
    @user2899728 Unfortunately there's no other way to do it. (You can't set md-raised-button attribute as false)
  • Moshe
    Moshe almost 7 years
    @user2899728 There is no way to apply directives conditionally. I've attempted to give you what you were looking for (end result) with a different approach (the "means").
  • user2899728
    user2899728 almost 7 years
    Thank you for your response. But I already have used this technique in the plunker example which I added in question detail. This is good option but not good idea if code is complex. Because because this technique ruin the concept of reusing. You can see my example in plunker and notice how my code is duplicated because of this approach. That is why I don't want to use this technique. I want some better solution so I can generate dynamic elements with.
  • LLL
    LLL almost 7 years
    I see. You could wrap the code that is duplicated in component.
  • user2899728
    user2899728 almost 7 years
    It is good idea but unfortunately it does not work in the case of reactive forms. With reactive forms, another issue appears. If I put input into separate component, then angular would require me to add [formGroup]="form" within that child component as well. But if I do so then my array binding would not work.
  • tam.teixeira
    tam.teixeira over 6 years
    As a side note, the Renderer is deprecated, instead use Renderer2 : alligator.io/angular/using-renderer2
  • Ravinder Payal
    Ravinder Payal over 6 years
    This is the only answer which let's a developer to use conditional directives as well as code re-usability.
  • Aldracor
    Aldracor about 6 years
    @Luke Yes, if you mean by "(now)" as: since 2017-01-06 which was 5 months before the question was asked. see angular changelog 6th item
  • Arsenii Fomin
    Arsenii Fomin almost 6 years
    @Aldracor angular 5.2.1, doesn't work. And I don't event understand why it should look like "condition ? '' : null" where both results are basically false. What should be instead of '' ? 'true' doesn't work also.
  • Aldracor
    Aldracor almost 6 years
    @ArseniiFomin You should ask a new question for that. In regards with how it works; I will edit my answer too explain.
  • Ben Taliadoros
    Ben Taliadoros over 5 years
    ah im trying it on an ng-container, makes sense it wouldnt work in this scenario. i wonder if there's a solution for this case, will post if so
  • Ben Taliadoros
    Ben Taliadoros over 5 years
    so for anyone who doesnt want to create a html element in order for their directive to work and is using ng-container, i just went with passing in an [enabled]="booleanVar"
  • JeffryHouser
    JeffryHouser over 5 years
    Not working for me in Angular 6. The plunker sample doesn't get past the loading screen. Based on my reading, this approach may work for HTML attributes, but will not work for Angular directives. The original question was about Angular Directives from the ng material library.
  • Ran Lottem
    Ran Lottem over 5 years
    This doesn't work for me, as the directive still exists in the component, it just performs no logic. I would like to have the directive's existence apply conditionally, specifically because there's css that checks for this directive.
  • Tiha
    Tiha over 5 years
    You have a different issue, than the one listed here (Apply the directive conditionally). Post your issue and people will help you. As a first idea, you could put the directive on a div and put an ng-container with an *ngIf around it to apply your condition. ngIf removes the div node from the DOM, so it might work. I am just guessing, you need to post your problem with code samples/description for ppl to help you.
  • Reza
    Reza over 5 years
    seems doesn't work on ng7, and unable to open the plunker, can you please create a stackblitz for it
  • pinguinjkeke
    pinguinjkeke over 5 years
    You can use just a @HostBinding('attr.dynamic-attr') @Input('dynamic-attr') attr: string; instead of ngOnInit + ElementRef.
  • Aldracor
    Aldracor over 5 years
    @RezaRahmati It is still working. See here as requested.
  • Reza
    Reza over 5 years
    @Aldracor Thanks, btw question is about angular directives not simple attributes. please see this stackblitz.com/edit/angular-conditional-directive-2004
  • Aldracor
    Aldracor over 5 years
    @RezaRahmati Yes you'r right. I've added an update so the plunker fork work again. (adds the attribute but the directive is never executed) Have a look here it might solve your problem
  • Chris Haines
    Chris Haines over 5 years
    This is not an answer to the question which was how to add a directive dynamically, not an attribute.
  • Shachar Har-Shuv
    Shachar Har-Shuv about 5 years
    That's a terribly solution since it's duplicates code. Imagine it's not just button but a div with a lot of html code inside it.
  • kbpontius
    kbpontius about 5 years
    @RezaRahmati I can verify this solution works on "simple attributes". Maybe this isn't what you were referring to, but for example, on input's multiple attribute: <input type="file" accept="type/jpg" [attr.multiple]="condition ? true : null"/>. Hopefully this is helpful to someone coming afterwards.
  • Reza
    Reza about 5 years
    @kbpontius thanks for comment, it works on Html attributes but not on angular directives (as attribute)
  • kbpontius
    kbpontius about 5 years
    @RezaRahmati Ahh, gotcha, just misunderstood your comment. Thanks for the clarification.
  • Mojtaba
    Mojtaba about 5 years
    You apply the directive in both cases just with different options (i.e., orange or green). The question asks for ignoring the directive at all based on a condition.
  • bvdb
    bvdb over 4 years
    When you offer a creative alternative, it usually helps to put in a line of comments first to describe where you are going with your answer.
  • Moshe
    Moshe over 4 years
    @bvdb thank you for your kind words. I was writing without direction, and that was a mistake. In the future, I hope to edit this post, as it could help even one person.
  • Dino
    Dino over 4 years
    This is not a good solution. Directive still get's initialized.
  • Rijo
    Rijo over 4 years
    Custom directive won't work for this approch. I have custome directive 'appOnlynumber' ary = [{numeric: true},{numeric:false}] <div *ngFor="let a of ary"> <input appOnlynumber="(a.numeric) ? '': null" /></div> In this scenario custom directive won't work
  • Jesse
    Jesse over 4 years
    In my case, this almost works, but my directive was called appTooltipDirective and this method put the attribute apptooltipdirective (no casing) on my element so it didn't work.
  • Sumit Ramteke
    Sumit Ramteke about 4 years
    already covered in LLL's answer
  • electrocrat
    electrocrat over 3 years
    I had to use undefined instead of null.
  • Oleg K
    Oleg K over 3 years
    It is not necessary to include formGroup to a child component that wraps input as you can simply pass your formControl as an @Input() control: FormControl inside the wrapper component and apply it to the native input via [formControl]="control"
  • masterxilo
    masterxilo over 3 years
    this only works for conditionally removing attributes of the html tag, not for angular directives
  • oomer
    oomer over 2 years
    Unfortunately, angular has not exposed any elegant way, so we left with this way only.
  • Sixteen
    Sixteen over 2 years
    This solutions is only viable if you work with a custom Directive. If you use a Directive from a library where you can't access logic (or should not touch it), you can't implement this.
  • Timtim
    Timtim over 2 years
    This solution is really elegant, thank you!
  • DFSFOT
    DFSFOT about 2 years
    You'll still have code for two buttons in which case you might as well do *ngIf="condition" on it with double code
  • DFSFOT
    DFSFOT about 2 years
    The nested ng-container confuses me (under Node button), only one is necessary(read sufficient) for this solution right?
  • H.B.
    H.B. about 2 years
    @DFSFOT One is for the if statement to check for children, the other is for rendering the content template. The key is the template rendering, the rest is for example purposes. (The outer ng-container could also be something else, like a <div>, depending on what document structure is desired.)
  • Adeel Shekhani
    Adeel Shekhani about 2 years
    Seems wrong, doesn't look like template is actually need with what you wrote. If template is there it should utilized at least twice in this case
  • B. Feenstra
    B. Feenstra about 2 years
    This is, however, not an option if your parent container depends on ContentChildren. For example, the mat-table component inner elements cannot be put into a template outlet because the required header, row and footer definitions cannot be found. Unfortunately, this is where I am stuck at the moment. and have to duplicate some HTML.
  • ZecKa
    ZecKa about 2 years
    note that ripple will not work with only mat-raised-button class