angular / components

Component infrastructure and Material Design components for Angular
https://material.angular.io
MIT License
24.32k stars 6.73k forks source link

Set button type dynamicaly #15367

Open mfarhani opened 5 years ago

mfarhani commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

Thanks @intellix , i wrote some code similar this.

maxpopovitch commented 4 years ago

<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 commented 4 years ago

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 commented 3 years ago

<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 commented 3 years ago

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[bot] commented 2 years ago

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] commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

@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 commented 1 year ago

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 commented 11 months ago

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 commented 11 months ago

@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 commented 11 months ago

@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 commented 9 months ago

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 commented 7 months ago

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

CodyTolene commented 6 months ago

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

Ac1d0n3 commented 4 months ago

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