Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set button type dynamicaly #15367

Open
mfarhani opened this issue Mar 2, 2019 · 28 comments
Open

Set button type dynamicaly #15367

mfarhani opened this issue Mar 2, 2019 · 28 comments
Labels
area: material/button feature This issue represents a new feature or feature request rather than a bug or bug fix P4 A relatively minor issue that is not relevant to core functions

Comments

@mfarhani
Copy link

mfarhani commented Mar 2, 2019

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?

@Shamim56
Copy link

Shamim56 commented Mar 3, 2019

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>"

@mfarhani
Copy link
Author

mfarhani commented Mar 3, 2019

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')

@Shamim56
Copy link

Shamim56 commented Mar 4, 2019

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>"

@Totati
Copy link
Contributor

Totati commented Mar 9, 2019

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.

@intellix
Copy link

intellix commented Mar 26, 2019

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>

@mfarhani
Copy link
Author

mfarhani commented Mar 26, 2019

Thanks @intellix , i wrote some code similar this.

@andrewseguin andrewseguin added feature This issue represents a new feature or feature request rather than a bug or bug fix P4 A relatively minor issue that is not relevant to core functions labels May 7, 2019
@maxpopovitch
Copy link

<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.

@amakhrov
Copy link
Contributor

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.

@vjonas
Copy link

vjonas commented Feb 24, 2021

<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!

@intellix
Copy link

intellix commented May 13, 2021

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

@angular-robot
Copy link
Contributor

angular-robot bot commented Feb 21, 2022

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
Copy link
Contributor

angular-robot bot commented Mar 13, 2022

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.

@AStoker
Copy link

AStoker commented Aug 26, 2022

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?

@eMuonTau
Copy link

eMuonTau commented Aug 27, 2022

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.

@wshager
Copy link

wshager commented Sep 2, 2022

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.

@gunjan-it-engg
Copy link

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

@michaelnem
Copy link

michaelnem commented Apr 4, 2023

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.

@CodyTolene
Copy link

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?

@Fanalea
Copy link

Fanalea commented Jun 2, 2023

It should be possible with this example.

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

@AStoker
Copy link

AStoker commented Jun 2, 2023

@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.

@jfchuman
Copy link

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.

@willflame
Copy link

willflame commented Oct 13, 2023

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>

@CodyTolene
Copy link

@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
Copy link

@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 = ...

@BenGrn
Copy link

BenGrn commented Dec 10, 2023

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.

@pedroestabruxelles
Copy link

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

@CodyTolene
Copy link

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

@Ac1d0n3
Copy link

Ac1d0n3 commented Apr 30, 2024

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" ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: material/button feature This issue represents a new feature or feature request rather than a bug or bug fix P4 A relatively minor issue that is not relevant to core functions
Projects
None yet
Development

No branches or pull requests