components icon indicating copy to clipboard operation
components copied to clipboard

Set button type dynamicaly

Open mfarhani opened this issue 5 years ago • 28 comments

Please describe the feature you would like to request.

I'm using MatButton directive as attribute like this: <button mat-button buttonType="{{button.type}}></button>" I want to change 'mat-button' directive according to 'button.type', for example if 'button.type' is 'stroked' then 'mat-button' changed to 'mat-stroked-button'. How can to change type of button dynamicaly?

What is the use-case or motivation for this proposal?

Is there anything else we should know?

mfarhani avatar Mar 02 '19 17:03 mfarhani

you could just use the render 2 library remove/set attribute library for this

edit: cant actually do this, just use

<button *ngIf="!stroked" mat-button>Text</button>
<button *ngIf="stroked" mat-button-stroked>Text</button>"

Shamim56 avatar Mar 03 '19 05:03 Shamim56

you could just use the render 2 library remove/set attribute library for this

If i using setAttribute for this purpose, what should place instead value? renderer.setAttribute(elRef,'mat-stroked-button','value')

mfarhani avatar Mar 03 '19 06:03 mfarhani

it seems this cant actually be done using setAttribute since mat-button seems to be a directive

why not just use ngIf, this is usually the most common way to address something like this

<button *ngIf="!stroked" mat-button>Text</button>
<button *ngIf="stroked" mat-button-stroked>Text</button>"

Shamim56 avatar Mar 04 '19 00:03 Shamim56

I've had a similar question at stackoverflow, but nobody had a better idea... I understand why it can't be done, but it's sad I have to make 2 buttons all the time.

Totati avatar Mar 09 '19 10:03 Totati

I'm using a CMS to allow adding buttons dynamically and for the button.type I'm doing something like this:

export interface Button {
  id: string;
  text: string;
  type: string;
  color: string;
  routerLink: string;
}
<ng-container [ngSwitch]="data.type">
  <a *ngSwitchDefault mat-button [color]="data.color" [routerLink]="data.routerLink">{{ data.text }}</a>
  <a *ngSwitchCase="'raised'" mat-raised-button [color]="data.color" [routerLink]="data.routerLink">{{ data.text }}</a>
  <a *ngSwitchCase="'stroked'" mat-stroked-button [color]="data.color" [routerLink]="data.routerLink">{{ data.text }}</a>
  <a *ngSwitchCase="'flat'" mat-flat-button [color]="data.color" [routerLink]="data.routerLink">{{ data.text }}</a>
  <a *ngSwitchCase="'icon'" mat-icon-button [color]="data.color" [routerLink]="data.routerLink">{{ data.text }}</a>
  <a *ngSwitchCase="'fab'" mat-fab [color]="data.color" [routerLink]="data.routerLink">{{ data.text }}</a>
  <a *ngSwitchCase="'mini-fab'" mat-mini-fab [color]="data.color" [routerLink]="data.routerLink">{{ data.text }}</a>
</ng-container>

intellix avatar Mar 26 '19 06:03 intellix

Thanks @intellix , i wrote some code similar this.

mfarhani avatar Mar 26 '19 08:03 mfarhani

<button mat-button [ngClass]="condition ? 'mat-raised-button' : 'mat-stroked-button'">Button</button> You can also use 'mat-flat-button' instead of 'mat-raised-button' if you don't need extra styling such as shadow.

maxpopovitch avatar Jul 14 '20 06:07 maxpopovitch

Dynamically setting the class works currently just because, based on the button source code (https://github.com/angular/components/blob/master/src/material/button/button.ts#L105) it's pretty much everything that those different attributes (mat-raised-button, mat-stroke-button, etc) do. However, it's not a part of the official mat button api - so this can break in future.

I'm wondering if it would make sense to introduce a new input for a button variant (similar to variant prop in React's Material implementation: https://material-ui.com/components/buttons/) and deprecate existing attributes over time? For instance, we already have a single color input instead of relying on multiple mat-color-primary / mat-color-accent attributes.

amakhrov avatar Aug 15 '20 22:08 amakhrov

<button mat-button [ngClass]="condition ? 'mat-raised-button' : 'mat-stroked-button'">Button</button> You can also use 'mat-flat-button' instead of 'mat-raised-button' if you don't need extra styling such as shadow.

amazing, just what I was looking for. thank you!

vjonas avatar Feb 24 '21 13:02 vjonas

The idea of doing this is nice:

<button mat-button class="mat-raised-button" color="primary">

But it doesn't really work due to .mat-button having more precedence on color and causing various other issues, which comes from theming.scss within Material:

.mat-button.mat-primary {
  color: black;
}

.mat-raised-button {
  color: white;
}

So I guess I have to go back to my solution which is even more problematic as I need to dynamically support <a> or <button> so have to duplicate all of that again.

My dynamic buttons/anchors from CMS were at 36 LOC but since I can't do that, I'm back to 200 LOC to achieve it since I need to switch over the variants for both anchors and buttons

intellix avatar May 13 '21 14:05 intellix

Just a heads up that we kicked off a community voting process for your feature request. There are 20 days until the voting process ends.

Find more details about Angular's feature request process in our documentation.

angular-robot[bot] avatar Feb 21 '22 15:02 angular-robot[bot]

Thank you for submitting your feature request! Looks like during the polling process it didn't collect a sufficient number of votes to move to the next stage.

We want to keep Angular rich and ergonomic and at the same time be mindful about its scope and learning journey. If you think your request could live outside Angular's scope, we'd encourage you to collaborate with the community on publishing it as an open source package.

You can find more details about the feature request process in our documentation.

angular-robot[bot] avatar Mar 13 '22 15:03 angular-robot[bot]

It's the year 2022. Libraries roam the land, and TypeScript reigns supreme. A lone developer drags his weary companion, the "Reusable Component" onto a rock to rest. He asks, "What's wrong, why are you so exhausted?" Reusable Component replies, "My body grows tired from copy pasting lines of code..." The developer responds, "Why don't you just change one attribute dynamically instead of rewriting 8 lines for eternity?" Reusable Component grasps the developers face and looks him straight in the eye and whispers, "I can't." Overcome with exhaustion, he expires there, becoming one with the landscape, never to be noticed or cared about again...

I've spent the last while looking for a way to dynamically set directives, and it seems quite counter-productive to require developers to rewrite the same element over and over. My use case is for a simple reusable button component that fits our design system. It's essentially an abstraction from Material so developers and designers never have to worry about if things change. However, to capture the 7 different button states, I'm being told that I need to write 7 duplicate lines of code with "if" statements to toggle between the two? Is there any way around this particular flavor of madness?

AStoker avatar Aug 26 '22 20:08 AStoker

This is the simplest solution i could find, based on v14 button directive and HTML. I can work on a PR but I don't know if mat-dynamic-button is ok as selector name.

https://stackblitz.com/edit/angular-vf9m7n

Since this component is using styles from button component and if you don't have any material button directive on any loaded component, button styles are unloaded. You can prevent it by adding a simple hidden mat-button to your root component or your layout view if you are using routing.

DynamicButtonType can be simplified as basic, raised, stroked and flat but you need to map them to class names.

eMuonTau avatar Aug 27 '22 16:08 eMuonTau

Spot on, @AStoker. Designers shouldn't care about what type of button developers use. Developers shouldn't care about what a button should look like. Madness indeed.

wshager avatar Sep 02 '22 15:09 wshager

I'm using a CMS to allow adding buttons dynamically and for the button.type I'm doing something like this:

export interface Button {
  id: string;
  text: string;
  type: string;
  color: string;
  routerLink: string;
}
<ng-container [ngSwitch]="data.type">
  <a *ngSwitchDefault mat-button [color]="data.color" [routerLink]="data.routerLink">{{ data.text }}</a>
  <a *ngSwitchCase="'raised'" mat-raised-button [color]="data.color" [routerLink]="data.routerLink">{{ data.text }}</a>
  <a *ngSwitchCase="'stroked'" mat-stroked-button [color]="data.color" [routerLink]="data.routerLink">{{ data.text }}</a>
  <a *ngSwitchCase="'flat'" mat-flat-button [color]="data.color" [routerLink]="data.routerLink">{{ data.text }}</a>
  <a *ngSwitchCase="'icon'" mat-icon-button [color]="data.color" [routerLink]="data.routerLink">{{ data.text }}</a>
  <a *ngSwitchCase="'fab'" mat-fab [color]="data.color" [routerLink]="data.routerLink">{{ data.text }}</a>
  <a *ngSwitchCase="'mini-fab'" mat-mini-fab [color]="data.color" [routerLink]="data.routerLink">{{ data.text }}</a>
</ng-container>

Thank you

gunjan-it-engg avatar Feb 21 '23 12:02 gunjan-it-engg

Hi there,

I think it would be really helpful if the HOST_SELECTOR_MDC_CLASS_PAIR variable could be exported in the API. This would allow developers to have more control over setting button type classes dynamically. Alternatively, it would be great to have the class decision moved to a later point in the code or even to a method that can be called manually.

michaelnem avatar Apr 04 '23 10:04 michaelnem

It's the year 2022... Is there any way around this particular flavor of madness?

The year is 2023, madness persists. Is there a better solution for this besides an ng-switch or ng-if yet?

CodyTolene avatar May 01 '23 13:05 CodyTolene

It should be possible with this example.

<button
   [mat-button]="isMaterialButton"
   [ngClass]="{ 'mat-button-stroked': isMaterialButton && isStroked }"
   [color]="isMaterialButton && isPrimary ? 'primary' : ''"
>
   Click me
</button>

Fanalea avatar Jun 02 '23 10:06 Fanalea

@Fanalea , that would only change the class. The issue here isn't exactly about setting the class dynamically, but rather setting the type, or an attribute dynamically.

AStoker avatar Jun 02 '23 12:06 AStoker

In my case, I found out that, if you have a mat-icon inside of the button, setting the class with ngClass makes the icon to lose its alignment. I fixed it by manually adding a <span class="mat-button-wrapper">, I don't know why but it was missing. Hope this helps someone.

jfchuman avatar Jun 12 '23 06:06 jfchuman

Hello, I came across the same problem and ended up finding this solution which I found more useful, since the remaining behavior is centralized, just with the variation of the button type.

Font: https://stackoverflow.com/a/75980043/16881969 Angular Material >= 15.

your-cmponent.ts

@Input() label = '';
@Input() type: 'basic' | 'raised' | 'stroked' | 'flat' | 'icon' | 'fab' | 'mini-fab' = 'stroked';
@Input() color: 'basic' | 'primary' | 'accent' | 'warn' | 'disabled' | 'link' = 'basic';

your-cmponent.html

<button
  mat-button
  [color]="color"
  [ngClass]="{
    'mat-mdc-button mat-mdc-button': type === 'basic',
    'mat-mdc-button mat-mdc-raised-button': type === 'raised',
    'mdc-button--outlined mat-mdc-outlined-button': type === 'stroked',
    'mat-mdc-unelevated-button': type === 'flat',
    'mdc-icon-button mat-mdc-icon-button': type === 'icon',
    'mdc-fab mat-mdc-fab': type === 'fab',
    'mdc-fab mdc-fab--mini mat-mdc-mini-fab': type === 'mini-fab'
  }">
    {{ label }}
</button>

willflame avatar Oct 13 '23 19:10 willflame

@willflame The only issue with that is if the class names change in the future your code may break.

You'll have to use a *ngIf or *ngSwitch wrapper around your buttons so that classes are added dynamically. The following should be future proof as long as the directives mat-button and mat-raised-button don't change.

<ng-container [ngSwitch]="type">
  <button *ngSwitchCase="'basic'" mat-button></button>
  <button *ngSwitchCase="'raised'" mat-raised-button></button>
  ...
</ng-container>

This is the best solution I've found so far.

CodyTolene avatar Oct 13 '23 20:10 CodyTolene

@willflame Quick tip you can use ThemePalette for your colors (or at lest the base colors). Then you don't need to redefine all the string literals:

@Input() color: import('@angular/material/core').ThemePalette = ...

CodyTolene avatar Oct 13 '23 20:10 CodyTolene

It blows my mind that this is still a problem 4 years later. The ngClass work around no longer works in v17 with safari. Something as simple as going from mat-stroked-button to mat-flat-button when pressed shouldn't be this hard.

BenGrn avatar Dec 10 '23 02:12 BenGrn

How hard is it to just provide a type for the button instead of having multiple directives which were never flexible.

pedroestabruxelles avatar Jan 26 '24 10:01 pedroestabruxelles

Hi all, any updates on this yet? Just checking in

CodyTolene avatar Mar 20 '24 17:03 CodyTolene

Cheers I also had this issue ... if you only want to modify the mat-button to stroked flat raised ... this works fine

`import { Directive, ElementRef, Input, Renderer2, inject } from '@angular/core'; import { BnButtonType } from '../../interfaces/src/bn-button-type'; import { BnRendererUtil } from '@binom/sdk-utils/renderer';

@Directive({ selector: 'button[bnMatbutType]', exportAs: "bnMatbutType", standalone: true, }) export class BnMatbutTypeDirective {

private renderEl: BnRendererUtil; private renderer = inject(Renderer2);

private el = inject(ElementRef);

@Input() set bnMatbutType(val:BnButtonType){ this.renderEl.removeClasses([ 'mat-mdc-button', 'mdc-button--unelevated', 'mat-mdc-unelevated-button', 'mdc-button--raised', 'mat-mdc-raised-button', 'mdc-button--outlined', 'mat-mdc-outlined-button']);

let classes=[]

switch(val){
  case 'stroked': 
    classes = ['mdc-button--outlined', 'mat-mdc-outlined-button']; break;
  case 'raised': 
    classes = ['mdc-button--raised', 'mat-mdc-raised-button']; break;
  case 'flat': 
    classes = ['mdc-button--unelevated', 'mat-mdc-unelevated-button']; break;
  case '': default:
    classes = ['mat-mdc-button']; break;
}
this.renderEl.addClasses(classes)

}
constructor() { this.renderEl = new BnRendererUtil(this.renderer, this.el); }`

... <button mat-button color="accent" [bnMatbutType]="butType" ...

Ac1d0n3 avatar Apr 30 '24 08:04 Ac1d0n3