ngClass-Angular with input and css - html

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/

Related

How to change the background color of input field when a button is clicked?

I would like to change my input field to change color when I click on a button. For extra information: the button's value itself changes when it is clicked.
This is the HTML I have right now:
<h2 class="label-custom">Tag</h2>
<input class="input" [readonly]="isReadOnly" formControlName="tag" [placeholder]="'Enter your tags' | transloco">
<button type="alter-button" class="alter-button" (click)="alter()">{{btnVal}}</button>
All the alter() function does is make the field readonly and change the button name. I can also change the color of the input field depending on what value the btnVal currently holds since the alter() function is called with each click. Is there a way I can change the background of the input field within the typescript file containing the alter() function?
You can modify the alter() function to something like this
import { Component, ElementRef } from '#angular/core';
#Component({
selector: 'app-root',
template: `
<h2 class="label-custom">Tag</h2>
<input class="input" [readonly]="isReadOnly" formControlName="tag" [placeholder]="'Enter your tags' | transloco">
<button type="alter-button" class="alter-button" (click)="alter()">{{btnVal}}</button>
`
})
export class AppComponent {
isReadOnly = false;
btnVal = 'Alter';
constructor(private elementRef: ElementRef) {}
alter() {
this.isReadOnly = !this.isReadOnly;
this.btnVal = this.isReadOnly ? 'Revert' : 'Alter';
const inputElement = this.elementRef.nativeElement.querySelector('.input');
inputElement.style.backgroundColor = this.isReadOnly ? 'lightgray' : 'white';
}
}

Why property binding to the color of a text element is not reactive in angular?

Below is my .ts file,
import { Component } from '#angular/core';
#Component({
selector: 'app-event-binding',
templateUrl: './event-binding.component.html',
styleUrls: ['./event-binding.component.css']
})
export class EventBindingComponent {
textColor = '';
onInput = (ev: any) => {
this.textColor = ev.target.value;
}
}
Below is my HTML template ,
<div>
<h3 [style.color]="textColor">EVENT BINDING</h3>
<input type="text" (input)="onInput($event)">
</div>
Here when I completely type "blue" in the input box my text color of h3 changes to blue.
But I noticed when i press backspace and now the value of textColor is "blu" , the text still remains in Blue color. I was expecting return to black.
It changes to black only when I clear the entire input.
So is there some kind of history retaining in the color in html? What does this?
The same happens when maninpulating the DOM with plain JavaScript, I have prepared an example for you:
document.querySelector('input').addEventListener('input', event => {
document.querySelector('h3').style.color = event.target.value;
})
<h3>EVENT BINDING</h3>
<input type="text">
When trying to set a value which the browser considers as invalid, the operation is not performed. You can see in the DOM that the value of the inline style is not updated. In this case, the last valid value will be used. Angular works the same than plain JavaScript here.
As a workaround, you can check whether the entered value is valid with CSS.supports(), and fall back to black:
onInput = (ev: any) => {
const value = ev.target.value;
this.textColor = CSS.supports('color', value) ? value : 'black';
}

Change behaviour of enter key in a phone - Angular 5

I am working with inputs but I am not really sure about how is the configuration of the navigation done (I guess that it are predefined behaviours).
I am not in the last input the enter key goes to the next one. This one is working as I want.
Nevertheless, when I am on the last input, when I press enter, it automatically clicks on the next button.
This is what I am trying to avoid. Is there any way to change this behaviour? Just to close the keyboard or to click on another button?
I have tried with keyup.enter and it pseudo works. It calls to the method but also clicks on the next button
HTML
<input
type="text"
class="form-control"
id="validationCustomSurname"
placeholder="e.g. Lopez"
required
(keyup.enter)="onNavigate(1, 'forward')"
[(ngModel)]="values.store.surname"
name="surname"
/>
This method should work on a phone, so I guess that keydown is not an option since $event.code does not give me any code in the phone.
Some time ago I make a directive see stackblitz that you apply in a div (or in a form) in the way
<form [formGroup]="myForm" (submit)="submit(myForm)" enter-tab>
Each input or button add a reference variable #nextTab like
<input name="input1" formControlName="input1" #nextTab/>
<button type="button" #nextTab/>
</form>
The directive use ContentChildren to add a keydown.enter to all the components that have #nextTab to focus to the next control
export class EnterTabDirective {
#ContentChildren("nextTab") controls: QueryList<any>
nextTab
constructor(private renderer: Renderer2, private el: ElementRef) {
}
ngAfterViewInit(): void {
this.controls.changes.subscribe(controls => {
this.createKeydownEnter(controls);
})
if (this.controls.length) {
this.createKeydownEnter(this.controls);
}
}
private createKeydownEnter(querycontrols) {
querycontrols.forEach(c => {
this.renderer.listen(c.nativeElement, 'keydown.enter', (event) => {
if (this.controls.last != c) {
let controls = querycontrols.toArray();
let index = controls.findIndex(d => d == c);
if (index >= 0) {
let nextControl = controls.find((n, i) => n && !n.nativeElement.attributes.disabled && i > index)
if (nextControl) {
nextControl.nativeElement.focus();
event.preventDefault();
}
}
}
})
})
}
Here's a very simple approach, with just a few lines of code:
First, in your Template when you dynamically create your Input elements: 1. populate the tabIndex attribute with a unique number, 2. populate a super-simple custom "Tag" Directive with the same unique number as the tabIndex, and 3. set up a Keydown "Enter" event listener:
Template:
<ng-container *ngFor="let row in data">
<input tabindex ="{{row[tabCol]}}" [appTag]="{{row[tabCol]}}" (keydown.enter)="onEnter($event)" . . . />
</ng-container>
In your component, your super-simple event-listener onEnter():
#ViewChildren(TagDirective) ipt!: QueryList<ElementRef>;
onEnter(e: Event) {
this.ipt["_results"][(<HTMLInputElement>e.target).tabIndex%(+this.ipt["_results"].length-1)+1].el.nativeElement.focus();
}
Note: The modulus (%) operation is just to make sure that if you're at the last Input, you'll get cycled back to the first input.
Super-simple, bare-minimum "Tag" Directive
import { Directive, ElementRef, Input } from '#angular/core';
#Directive({
selector: '[appTag]'
})
export class TagDirective {
#Input('appTag') id: number;
constructor(public el: ElementRef) { }
}
There's probably even a way to get rid of the "Tag" `Directive altogether and make it even more simple, but I haven't had time to figure out how to do that yet . . .

Add class through ngClass if a disabled button is clicked

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
}

Angular 5 FormGroup reset doesn't reset validators

I have a form on my page and when I call FormGroup.reset() it sets the forms class to ng-pristine ng-untouched but FormControl.hasError(...) still returns truthy. What am I doing wrong here?
Template
<form [formGroup]="myForm" (ngSubmit)="submitForm(myForm)">
<mat-form-field>
<input matInput formControlName="email" />
<mat-error *ngIf="email.hasError('required')">
Email is a required feild
</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput type="password" formControlName="password" />
<mat-error *ngIf="password.hasError('required')">
Password is a required feild
</mat-error>
</mat-form-field>
<button type="submit">Login</button>
</form>
Component
export class MyComponent {
private myForm: FormGroup;
private email: FormControl = new FormContorl('', Validators.required);
private password: FormControl = new FormControl('', Validators.required);
constructor(
private formBuilder: FormBuilder
) {
this.myForm = formBuilder.group({
email: this.email,
password: this.password
});
}
private submitForm(formData: any): void {
this.myForm.reset();
}
}
Plunker
https://embed.plnkr.co/Hlivn4/
It (FormGroup) behaves correctly. Your form requires username and password, thus when you reset the form it should be invalid (i.e. form with no username/password is not valid).
If I understand correctly, your issue here is why the red errors are not there at the first time you load the page (where the form is ALSO invalid) but pop up when you click the button. This issue is particularly prominent when you're using Material.
AFAIK, <mat-error> check the validity of FormGroupDirective, not FormGroup, and resetting FormGroup does not reset FormGroupDirective. It's a bit inconvenient, but to clear <mat-error> you would need to reset FormGroupDirective as well.
To do that, in your template, define a variable as such:
<form [formGroup]="myForm" #formDirective="ngForm"
(ngSubmit)="submitForm(myForm, formDirective)">
And in your component class, call formDirective.resetForm():
private submitForm(formData: any, formDirective: FormGroupDirective): void {
formDirective.resetForm();
this.myForm.reset();
}
GitHub issue: https://github.com/angular/material2/issues/4190
In addition to Harry Ninh's solution, if you'd like to access the formDirective in your component without having to select a form button, then:
Template:
<form
...
#formDirective="ngForm"
>
Component:
import { ViewChild, ... } from '#angular/core';
import { NgForm, ... } from '#angular/forms';
export class MyComponent {
...
#ViewChild('formDirective') private formDirective: NgForm;
constructor(... )
private someFunction(): void {
...
formDirective.resetForm();
}
}
After reading the comments this is the correct approach
// you can put this method in a module and reuse it as needed
resetForm(form: FormGroup) {
form.reset();
Object.keys(form.controls).forEach(key => {
form.get(key).setErrors(null) ;
});
}
There was no need to call form.clearValidators()
Add the property -
#ViewChild(FormGroupDirective) formGroupDirective: FormGroupDirective;
and use this instead of this.myForm.reset();
this.formGroupDirective.resetForm();
This will reset the error display and also do the job of form.reset(). But the form, along with the fields, will still show ng-invalid class
Check this answer for more details - https://stackoverflow.com/a/56518781/9262627
The below solution works for me when trying to reset specific form controller in form group -
this.myForm.get('formCtrlName').reset();
this.myForm.get('formCtrlName').setValidators([Validators.required, Validators.maxLength(45), Validators.minLength(4), Validators.pattern(environment.USER_NAME_REGEX)]);
this.myForm.get('formCtrlName').updateValueAndValidity();
form.reset() won't work on custom form control like Angular Material that's why the function is not working as expected.
My workaround for this is something like this
this.form.reset();
for (let control in this.form.controls) {
this.form.controls[control].setErrors(null);
}
this.form.reset() the issue with this is that it will reset your formcontrol values but not the errors so you need to reset them individually by this line of code
for (let control in this.form.controls) {
this.form.controls[control].setErrors(null);
}
With this you don't need to use FormGroupDirective which is a cleaner solution for me.
Github issue: https://github.com/angular/angular/issues/15741
I found that after calling resetForm() and reset(), submitted was not being reset and remained as true, causing error messages to display. This solution worked for me. I found it while looking for a solution to calling select() and focus() on an input tag, which also wasn't working as expected. Just wrap your lines in a setTimeout(). I think setTimeout is forcing Angular to detect changes, but I could be wrong. It's a bit of a hack, but does the trick.
<form [formGroup]="myFormGroup" #myForm="ngForm">
…
<button mat-raised-button (click)="submitForm()">
</form>
submitForm() {
…
setTimeout(() => {
this.myForm.resetForm();
this.myFormGroup.reset();
}, 0);
}
resetForm() {
this.myFormGroup.reset();
this.myFormGroup.controls.food.setErrors(null);
this.myFormGroup.updateValueAndValidity();
}
UPDATE FROM 2021 - ANGULAR 11.2
The fact to use a [formGroup]="form and a #formDirective="ngForm" directly into the HTML function is not a good practise. Or maybe you would prefer to use #ViewChild, and do it directly from your .ts. Actually, the problem don't come from Angular, but Material.
If you take a look at their GitHub, you will see this :
/** Provider that defines how form controls behave with regards to displaying error messages. */
#Injectable({providedIn: 'root'})
export class ErrorStateMatcher {
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
return !!(control && control.invalid && (control.touched || (form && form.submitted)));
}
}
The form will keep its submitted state. So you just have to delete the last part of the function.
Here is my solution (tested and working). I have a Material Module, into I've implemented this :
export class ShowOnInvalidTouchedErrorStateMatcher implements ErrorStateMatcher {
isErrorState(control: FormControl): boolean {
return !!(control && control.invalid && control.touched);
}
}
#NgModule({
providers: [
{
provide: ErrorStateMatcher, useClass: ShowOnInvalidTouchedErrorStateMatcher
}
],
exports: [
MatSnackBarModule,
MatTabsModule,
...
]
});
If you want to use this ErrorStateMatcher on only one form, it's possible. Please see this Material example. This is the same principle.
I had no luck with resetting the form directive. But You can also change the input state to pending to do that as well.
this.myForm.get("email").reset();
this.myForm.get("password").reset();
To anyone whom this may help, I am running Angular 9.1.9 and I didn't want to reset the form/controls just the overall validity of the form so I just ran:
this.registerForm.setErrors(null);
...where registerForm: FormGroup and that reset the form errors, leading to:
this.registerForm.valid
...returning true.
The same can be done for controls:
this.registerForm.get('email').setErrors(null)
As soon as the form is touched, these errors are re-evaluated anyway so if that's not good enough, you may need to have a boolean flag to further pin-down exactly when you want to start showing/hiding error UI.
I did not need to touch the directive in my case.
I was also having the same set of problems. My problem was that i was using mat-form-field and formGroup. After resetting the form submitted flag was not resetting.
So, the solution that worked for me is, putting a directive of ngForm along with formGroup and passing onSubmit(form). Added
#ViewChild('form') form;
in component and then I used
this.form.resetForm();
Nothing from above worked for me (Angular 7.2, Angular Material 7.3.7).
Try to pass with submit method an event on view:
<form [formGroup]="group" (ngSubmit)="onSubmit($event)">
<!-- your form here -->
</form>
Then use it to reset currentTarget and your form afterwards:
public onSubmit(event): void {
// your code here
event.currentTarget.reset()
this.group.reset()
}
Simple fix: use button with type="reset" and function submitForm() together
<form [formGroup]="MyForm" (ngSubmit)="submitForm()">
<input formControlName="Name">
<mat-error>
<span *ngIf="!tunersForm.get('Name').value && tunersForm.get('Name').touched"></span>
</mat-error>
<button type="reset" [disabled]="!MyForm.valid" (click)="submitForm()">Save</button>
</form>