Add class through ngClass if a disabled button is clicked - html

I want to be able to add a scss class ("error") to an element, if a [disabled] button is clicked. How do i achieve this?
Angular2 disable button
the [disabled] attribute is required, so this is not a working solution.
<button [disabled]="!isTermAgreed || isLoading" (click)="payForStory(paymentObject.StoryId)">
scss class that a span element will recieve through [ngClass]:
&.error {
border: 2px solid red;
}

You cannot listen for click events on a disabled button. You could add a disabled class to the button and then listen for events as normal. Please review this stackblitz: https://stackblitz.com/edit/angular-3ksttt
Component:
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
isButtonClicked: boolean = false;
public disableButtonClick() {
this.isButtonClicked = true;
}
}
Template:
<button [ngClass]="isButtonClicked ? 'error' : ''" (click)="disableButtonClick()" class="disabled">BUTTON</button>

Create a boolean property in your component, I.e. activeClass, listen for a click event on your button to toggle it, and then simply use ngClass to assign the class depending on the boolean.
[ngClass]="'yourClass': activeClass"
edit
As correctly pointed out, you can't listen to events on a disabled button. A solution to this is to wrap the element with a div and listen to events on that element (see this answer for more details) or to use a class to simulate the disabled effect.

Whenever the button is disabled, a class of button:disabled will be added by default. So you can put your styles in the class. As shown:
button:disabled {
background-color: #97929248;
cursor: not-allowed;
}

There is no pretty way to do this in Angular. The way Angular is structured such that you shouldn't be manually checking the DOM except in rare circumstances. If you must do this, check out this link, this accepted answer has a solution that may be useful to you.
An alternative approach could be to always add the .error class on click, but construct your sass such that the properties of .error would only be activated when .disabled is also present.
For example:
<button (click)="isError = true" [ngClass]="{error: isError}">My Button</button>
And then in your SASS:
.disabled.error {
//Your style here
}

If your button is disabled then it won't listen to the (click) event, In this case one thing you can do is to make it look like disabled using css and enable the disabled attribute once its clicked.
in your component:
clickedMe: boolean = false;
In your HTML:
<button [disabled]="(!isTermAgreed || isLoading) && clickedMe" [ngClass]="{'disableCss': (!isTermAgreed || isLoading), 'error': clickedMe && (!isTermAgreed || isLoading)}"(click)="payForStory(paymentObject.StoryId); clickedMe = true">
And you css will be something like:
.disableCss {
background: #dddddd
}

Related

ngClass-Angular with input and css

when the input variable is touched I would like it to change the css, but I'm having difficulties, when I click on the input it doesn't change the css, can anyone help me?
The idea would be that when the input was changed, the card would turn upside down with the css "hover"
enter image description here
enter image description here
enter image description here
One way to detect if input has been touched would be to use event callbacks (such as click listener on the form).
#Component({
selector: 'input-clearable-example',
templateUrl: './input-clearable-example.html',
styleUrls: ['./input-clearable-example.css'],
})
export class InputClearableExample {
value = 'Clear me';
isTouched: boolean = false;
onTouch(event: any) {
console.log(event)
this.isTouched = true;
}
}
<mat-form-field class="example-form-field" appearance="fill"
(click)="onTouch($event)" [ngClass]="{'background-red' : isTouched}">
<mat-label>Touch me!</mat-label>
<input matInput type="text" [(ngModel)]="value"/>
</mat-form-field>
.background-red {
background: red;
}
Working example: https://stackblitz.com/edit/angular-by3kvr-5wv7oo?file=src%2Fapp%2F.%2Finput-clearable-example.html
An even more correct way would be to actually check if the form has been touched https://www.geeksforgeeks.org/how-to-check-whether-a-form-or-a-control-is-touched-or-not-in-angular-10/

Angular/Typescript Text with routerLink

Updated Question for more Clarity:
Need to display some texts and links as innerHTML(data from service/DB) in the Angular HTML and when user clicks, it should go to Typescript and programmatically navigates by router.navigate
Also, How to add DomSanitizer from #ViewChild/ElementRef
Added all example in below code
Here is the updated stackblitz code
As shown in screenshot from angular.io some texts and some links
Sorry, I didn't realize you answered my comment. Angular routing is not secondary, if you don't use Angular modules you'll end up with just an HTML/CSS/Typescript application. you need at least the RouterModule for Angular to be able to use routing and hence, do what it's supposed to with the DOM.
First:
You are not importing RouterModule
solution:
imports: [
BrowserModule,
FormsModule,
RouterModule.forRoot([]) // this one
]
Second:
You can't bind Angular events through innerHTML property
fix:
Make use of #ViewChild directive to change your innerHTML property and manually bind to the click event, so change in your app.component.html from
<div id="box" [innerHTML]="shouldbedivcontent" ></div>
to
<div #box id="box"></div>
Now, in your app.component.ts, add a property to hold a reference to that "box" element so you can later make some changes to the dom with it:
#ViewChild('box') container: ElementRef;
Implement AfterViewInit, that hook is where you will be able to actually handle your container, if you try using it for example in OnInit you'd get undefined because that component's html is not in the dom yet.
export class AppComponent implements AfterViewInit {
and
ngAfterViewInit() {
this.container.nativeElement.innerHTML = this.shouldbedivcontent;
this.container.nativeElement.addEventListener('click',
() => this.goto('bar')
);
}
change shouldbedivcontent property from:
'1) this is a click
<a (click)="goto("bar")">Click</a><br>
2)this is with routerlink
<a routerLink="" (click)="goto("bar")">Click</a><br>
3)This only works with href
bar and test'
to
'1) this is a click
<a id="link_1">Click</a><br>
2)this is with routerlink
<a [routerLink]="" (click)="goto(\'bar\')">Click</a><br>
3)This only works with href
bar and test'
And even so you'd still not get the default anchor style unless you apply some styling yourself.
Third
You are not HTML sanitizing, which could be dangerous. read more here
MY SUGGESTION:
Seems like a lot to do for you and a lot to read for someone else working alongside you for something you could easily do like in the example below!
Move your html to your app.component.html:
<div id="box">
1) this is a click
<a (click)="goto('bar')">Click</a><br>
2)this is with routerlink
<a routerLink="" (click)="goto('bar')">Click</a><br>
3)This only works with href
bar and test
</div>
<p>Below is actual content</p>
You'll notice that everything works now, except the anchor without routerLink or href, because that's not a link.
EDIT:
Looking at the new stackblitz, i suggest a change of approach, binding to innerHTML is ok when working with plain text or even some simple html but not a great choice to bind events or routing logic.
Angular's Renderer2 provides with a bunch of methods to dyncamically add elements to the DOM. With that on the table, you just need a little effort to take that simple html you get from your backend and turn it into something like (paste this property in your code to test it along the rest of the code provided below):
public jsonHTML = [
{
tagName: '',
text: 'some text with click ',
attributes: {
}
},
{
tagName: 'a',
text: 'bar',
attributes: {
value: 'bar' // goto parameter
}
},
{
tagName: '',
text: ' some more text with click ',
attributes: {
}
},
{
tagName: 'a',
text: 'foo',
attributes: {
value: 'foo' // goto parameter
}
}
]
Once you have it, it's way easier to create all of those elements dynamically:
this is for the code in your Q1:
Inject Renderer2 with private r2: Renderer2
And replace the Q1 related code in AfterViewInit hook to:
const parent = this.r2.createElement('div'); // container div to our stuff
this.jsonHTML.forEach((element) => {
const attributes = Object.keys(element.attributes);
const el = element.tagName && this.r2.createElement(element.tagName);
const text = this.r2.createText(element.text);
if (!el) { // when there's no tag to create we just create text directly into the div.
this.r2.appendChild(
parent,
text
);
} else { // otherwise we create it inside <a></a>
this.r2.appendChild(
el,
text
);
this.r2.appendChild(
parent,
el
);
}
if (attributes.length > 0) {
attributes.forEach((name) => {
if (el) {
this.r2.setAttribute(el, name, element.attributes[name]); // just the value attribute for now
if (name === 'value') {
this.r2.listen(el, 'click', () => {
this.goto(element.attributes[name]); // event binding with property "value" as parameter to navigate to
})
}
} else {
throw new Error('no html tag specified as element...');
}
})
}
})
this.r2.appendChild(this.container.nativeElement, parent); // div added to the DOM
No html sanitizer needed and no need to use routerLink either just inject Router and navigate to the route you want! Make improvements to the code t make it fit your needs, it should be at least a good starting point
Good Luck!
You have a css problem.
looks like a link
<a [routerLink]="something"></a> looks like a link, because if you inspect the HTML it actually gets an href property added because of routerLink
<a (click)="goTo()"></a> does NOT look like a link, because there is no href
Chrome and Safari default user agents css will not style <a> without an href (haven't confirmed Firefox but I'm sure its likely). Same thing for frameworks like bootstrap.
Updated stackblitz with CSS moved to global, not app.css
https://stackblitz.com/edit/angular-ivy-kkgmkc?embed=1&file=src/styles.css
This will style all links as the default blue, or -webkit-link if that browser supports it. It should be in your global.css file if you want it to work through the whole app.
a {
color: rgb(0, 0, 238);
color: -webkit-link;
cursor: pointer;
text-decoration: underline;
}
this works perfectly for me :D
#Directive({
selector: "[linkify]",
})
// * Apply Angular Routing behavior, PreventDefault behavior
export class CustomLinkDirective {
#Input()
appStyle: boolean = true;
constructor(
private router: Router,
private ref: ElementRef,
#Inject(PLATFORM_ID) private platformId: Object
) {}
#HostListener("click", ["$event"])
onClick(e: any) {
e.preventDefault();
const href = e.target.getAttribute("href");
href && this.router.navigate([href]);
}
ngAfterViewInit() {
if (isPlatformBrowser(this.platformId)) {
this.ref.nativeElement.querySelectorAll("a").forEach((a: HTMLElement) => {
const href = a.getAttribute("href");
href &&
this.appStyle &&
a.classList.add("text-indigo-600", "hover:text-indigo-500");
});
}
}
}
HOW I USE IT
<p linkify
class="mt-3 text-lg text-gray-500 include-link"
[innerHtml]="apiSectionText"
></p>
result

Angular - Prevent click event on disabled buttons

I'm trying to prevent click event on disabled buttons, in other words, prevent some user who removes the disabled attribute to call some action.
For now, I have the following code to do this:
<button [disabled]="someCondition" (click)="executeAction()">Execute action</button>
executeAction(): void {
if (this.someCondition) return;
// ...
}
Works, but it isn't a good solution as I have to do it for ALL buttons in my app (and believe me, it's easy to forgot to do this and even a Linter can't help me here).
Looking for a more robust solution, I thought that directive could help me:
import { Directive, HostListener, Input, Renderer2, ElementRef } from '#angular/core';
#Directive({
selector: 'button'
})
export class ButtonDirective {
#Input() set disabled(value: boolean) {
this._disabled = value != null;
this.renderer2.setAttribute(this.elementRef.nativeElement, 'disabled', `${this._disabled}`);
}
private _disabled: boolean;
constructor(
private readonly elementRef: ElementRef,
private readonly renderer2: Renderer2
) { }
#HostListener('click', ['$event'])
onClick(mouseEvent: MouseEvent) {
// nothing here does what I'm expecting
if (this._disabled) {
mouseEvent.preventDefault();
mouseEvent.stopImmediatePropagation();
mouseEvent.stopPropagation();
return false; // just for test
}
}
}
<button [disabled]="someCondition" (click)="executeAction()">Execute action</button>
executeAction(): void {
console.log('still being called');
}
...however it does absolutely nothing. It doesn't prevent the click event. Is there any solution that I don't have to control the action itself in its call?
STACKBLITZ
This is a workaround with CSS which cheaper than scripts.
You easily could use
pointer-events: none;
In this case, the button will not be clickable.
As a UX enhance you could also wrap your button inside a div and give this div a CSS property
cursor: not-allowed;
Which will show the blocked circle icon instead of normal mouse view when hover.
In your directive, you can do something like this. You can achieve it by adding an event listener to parent in the capturing phase.
ngOnInit() {
this.elementRef.nativeElement.parentElement.addEventListener('click',(e) => {
if(this._disabled && e.target.tagName === 'BUTTON') {
e.stopImmediatePropagation();
e.stopPropagation();
}
}, true);
}
You can remove the listener in onDestroy
Prevent click event on disabled buttons
If the disabled attribute is there the click will not happen.
When user decides to use devtools
However if the user edits the HTML and removes the disabled attribute manually, then click will happen. You can try and do the check as you have suggested, but the browser is an unsafe environment. The user will still be able to execute any code on the webpages behalf irrespective of any frontend checks you might put in.

How to extend Angular Form Directive to have globally autocomplete="off"

As we know in Angular <form> is a directive. I'm wondering if there is any way to extend that directive.
I need this because I want to append attribute autocomplete="off" always when I use <form> on my view. Or maybe there is another, easier way to set it globally?
Angular diretive selector can also be select by css.As #Andrei Gatej mentioned in the comment you can use hostbinding to bind autocomplete attribute to all input inside form like this:
import { Directive, HostBinding } from '#angular/core';
#Directive({
selector: 'input[type="text"]'
})
export class FormDirective {
#HostBinding('attr.autocomplete') autoComplete ='off';
constructor() {
}
}
you should write own directive. Here is docs https://angular.io/guide/attribute-directives

How to add disable attribute on component selector?

I am trying to disable button. But button is angular component.
And Html5 disabled attribute cannot work on component selector.
I tried to add like this but does not work: [attr.disabled]="isOpenModal
Button Html code:
<add-new-button [attr.disabled]="isOpenModal"
(click)="openModal('new')"
class="nano-bc-green hover-effect">
</add-new-button>
Button - Component "add new button"
#Component({
selector: 'nano-add-new-button',
template: `
<div class='nano-f-r nano-f'>
<i class='fa fa-plus'></i>
<span class='nano-ml-5'>
Add New
</span>
</div>`
})
export class NanoAddNewButtonComponent {
}
Open Modal method which is used on button:
public openModal(id: string): void {
const data = {id: id};
this.modalModel.add(AudienceModel.ENTITY_NAME, data);
}
Any idea for solution?
Because your add-new-button component can be anything, and disabled is not a property that all elements have, that can't work.
Check out the list of Global Attributes.
You have to define your own disabled property:
#Input() disabled: boolean;
And you can bind this to the elements you want to disable like:
<button [disabled]="disabled">My button</button>
You can use it like this after:
<add-new-button [disabled]="isOpenModal"
(click)="openModal('new')"
class="nano-bc-green hover-effect">
</add-new-button>
Just put the disabled logic into the click method itself:
Template:
<add-new-button (click)="onModalClick()"
class="nano-bc-green hover-effect">
</add-new-button>
TypeScript:
onModalClick() {
if (!this.isOpenModal) {
this.openModal('new');
}
}
here is disable attribute
<my-date-picker [options]="myOptions" [disabled]="disabled"
(dateChanged)="onDateChanged($event)"></my-date-picker>
it may be helpful ;)
You can use the CSS selector [ng-reflect-disabled] to trigger the disabled value.
and add [disabled]="isOpenModal" instead of [attr.disabled]="isOpenModal" in your HTML file.
and in your CSS file add the below code:
add-new-button[ng-reflect-disabled="true"] {
cursor: default;
pointer-events: none;
}