I have a mat-form from where I want user to enter some values and then I want to submit those values.However I want one of the field to be converted to lowercase and then be submitted.How can I achieve that.
HTML Code:
<mat-form-field appearance="fill">
<mat-label>Hive Table</mat-label>
<input
matInput
formControlName="hiveTable"
/>
</mat-form-field>
Typescript Code:
this.generalInfoForm = new FormGroup({
hiveTable: new FormControl('', [Validators.required]),
});
What I have tried so far
<mat-label>Hive Table</mat-label>
<input
matInput
oninput="this.value = this.value.toLowerCase()"
(keyup.enter)="sendit($event.target.value)"
formControlName="hiveTable"
/>
</mat-form-field>
I used oninout and using keyup I did console.log and in console the values are getting converted tolowercase however on submitting it the value changes back to the way the user typed.
What am I doing wrong.
why not use in input
style="text-transform: lowercase"
Then, before submit you can (I imagine you has a function submit())
submit(form:FormGroup)
{
if (form.valid)
{
form.value.yourField=form.value.yourField.toLowerCase()
console.log(form.value)
}
}
You can use a Directive as shown below:
import { Directive, HostListener, ElementRef, Renderer2 } from '#angular/core';
#Directive({
selector: '[lowercase]'
})
export class LowercaseDirective {
constructor(private renderer: Renderer2, private elementRef: ElementRef) {}
#HostListener('keyup') onKeyUp() {
const nativeElement = this.elementRef.nativeElement;
nativeElement.value = nativeElement.value.toLowerCase();
}
}
And in your template, this needs to be defined as follows:
<input matInput ... lowercase/>
Please have a look at this StackBlitz
Related
Creating a signup form using angular material, It is displaying errors even if there is no error.
For a valid input it is displaying the error Invalid Input.
I've removed that mat-error condition the field is turning red while entering data.
The status inside the control is showing the field is valid on submit.
The error conditions are in in global constants file.
-Global const ts
export class globalConstants{
public static genericErr:string="Someting wnet wrong,Please try again";
public static usrname:string='[a-zA-Z0-9 ]*';
}
HTML-
<mat-form-field appearance="fill" fxFlex>
<mat-label>User Name</mat-label>
<input matInput formControlName="usrname" required>
<mat-error *ngif="signUpForm.controls.usrname.touched && signUpForm.controls.usrename.invalid">
<span *ngIf="signUpForm.controls.usrname.errors.required">User Name is Mandatory</span>
<span *ngIf="signUpForm.controls.usrname.errors.pattern">Ivalid Input</span>
</mat-error>
</mat-form-field>}
Signup Ts File:
import { globalConstants } from '../shared/globalConst';
signUpForm: any = FormGroup;
ngOnInit(): void {
this.signUpForm = this.formBuilder.group({usrname: ['Enter Username', [Validators.required, Validators.pattern(globalConstants.usrname)]],
Kindly assist with your expertise.
Please check the following code
input-error-state-matcher.ts
import { Component } from '#angular/core';
import {
FormGroup,
FormBuilder,
FormControl,
FormGroupDirective,
NgForm,
Validators,
} from '#angular/forms';
/** #title Input with a custom ErrorStateMatcher */
#Component({
selector: 'input-error-state-matcher-example',
templateUrl: './input-error-state-matcher-example.html',
styleUrls: ['./input-error-state-matcher-example.css'],
})
export class InputErrorStateMatcherExample {
userAddressValidations: FormGroup;
constructor(private formBuilder: FormBuilder) {}
ngOnInit() {
this.userAddressValidations = this.formBuilder.group({
firstName: [
'',
[
Validators.required,
Validators.minLength(4),
Validators.maxLength(20),
Validators.pattern('[a-zA-Z]+'),
],
],
});
}
}
input-error-state-matcher.html
<form class="example-form" [formGroup]="userAddressValidations">
<mat-form-field class="example-full-width">
<input matInput placeholder="First Name" formControlName="firstName">
<mat-error *ngIf="userAddressValidations.get('firstName').hasError('required')">
First Name is Required!
</mat-error>
<mat-error *ngIf="userAddressValidations.get('firstName').hasError('minlength')">
First Name should be atleast 4 characters long!
</mat-error>
<mat-error *ngIf="userAddressValidations.get('firstName').hasError('maxlength')">
First Name can be atmax n characters long!
</mat-error>
<mat-error *ngIf="userAddressValidations.get('firstName').hasError('pattern')">
First Name must follow this pattern!
</mat-error>
</mat-form-field>
</form>
working example is here
My goal:
I'm trying to build a reusable mat-form-field with a clear button.
How I tried achieving my goal:
I created a "mat-clearable-input" component and used it like this:
<mat-clearable-input>
<mat-label>Put a Number here pls</mat-label>
<input matInput formControlName="number_form_control">
</mat-clearable-input>
mat-clearable-input.component.html
<mat-form-field>
<ng-content></ng-content>
</mat-form-field>
Expected result:
the ng-content tag takes the label and the input and puts them inside the mat-form-field tag.
Actual result:
Error: mat-form-field must contain a MatFormFieldControl.
at getMatFormFieldMissingControlError (form-field.js:226)
at MatFormField._validateControlChild (form-field.js:688)
at MatFormField.ngAfterContentChecked (form-field.js:558)
at callHook (core.js:2926)
at callHooks (core.js:2892)
at executeInitAndCheckHooks (core.js:2844)
at refreshView (core.js:7239)
at refreshComponent (core.js:8335)
at refreshChildComponents (core.js:6991)
at refreshView (core.js:7248)
It looks like I'm missing something and I'm not using correctly the ng-content tag.
I wasn't able to locate the documentation for the ng-content tag on the angular website.
Thank you for any help.
EDIT AFTER ANSWER BELOW
So I tried this suggested method:
export class MatClearableInputComponent implements OnInit {
#ContentChild(MatFormFieldControl) _control: MatFormFieldControl<any>;
#ViewChild(MatFormField) _matFormField: MatFormField;
// see https://stackoverflow.com/questions/63898533/angular-ng-content-not-working-with-mat-form-field/
ngOnInit() {
this._matFormField._control = this._control;
}
}
unfortunately, when I try to use this in a form it still fails with the error "Error: mat-form-field must contain a MatFormFieldControl."
Code where i try to use this component in a form:
<mat-clearable-input>
<mat-label>Numero incarico</mat-label>
<buffered-input matInput formControlName="numero"></buffered-input>
</mat-clearable-input>
Repro on stackblitz: https://stackblitz.com/edit/angular-material-starter-xypjc5?file=app/clearable-form-field/clearable-form-field.component.html
notice how the mat-form-field features aren't working (no outline, no floating label), also open the console and you'll see the error Error: mat-form-field must contain a MatFormFieldControl.
EDIT AFTER OPTION 2 WAS POSTED
I tried doing this:
<mat-form-field>
<input matInput hidden>
<ng-content></ng-content>
</mat-form-field>
It works, but then when i added a mat-label to my form field, like this:
<mat-clearable-input>
<mat-label>Numero incarico</mat-label>
<buffered-input matInput formControlName="numero"></buffered-input>
</mat-clearable-input>
the label is never floating and it's just staying there as a normal span the whole time.
So i tried assigning to the this._matFormField._control._label the content child with the label but that didn't work because _label is private and there is no setter for it.
It looks like I'm out of luck and this can't be done in Angular without going through a lot of effort.
If you have any further ideas feel free to fork the stackblitz and try!
Edit after #evilstiefel answer
the solution works only for native <input matInput>.
When I try replacing that with my custom input component, it doesn't work anymore.
Working setup:
<mat-form-field appClearable>
<mat-label>ID incarico</mat-label>
<input matInput formControlName="id">
</mat-form-field>
Same setup but with my custom "buffered-input" component (not working :( )
<mat-form-field appClearable>
<mat-label>ID incarico</mat-label>
<buffered-input matInput formControlName="id"></buffered-input>
</mat-form-field>
The console logs this error when I click on the clear button:
TypeError: Cannot read property 'ngControl' of undefined
at ClearableDirective.clear (clearable.directive.ts:33)
at ClearButtonComponent.clearHost (clearable.directive.ts:55)
at ClearButtonComponent_Template_button_click_0_listener (clearable.directive.ts:47)
at executeListenerWithErrorHandling (core.js:14293)
at wrapListenerIn_markDirtyAndPreventDefault (core.js:14328)
at HTMLButtonElement.<anonymous> (platform-browser.js:582)
at ZoneDelegate.invokeTask (zone-evergreen.js:399)
at Object.onInvokeTask (core.js:27126)
at ZoneDelegate.invokeTask (zone-evergreen.js:398)
at Zone.runTask (zone-evergreen.js:167)
Another solution is using a directive to implement the behaviour.
import {
AfterViewInit,
Component,
ComponentFactory,
ComponentFactoryResolver,
ContentChild,
Directive,
Injector,
Input,
Optional,
SkipSelf,
TemplateRef,
ViewContainerRef,
} from '#angular/core';
import { MatFormFieldControl } from '#angular/material/form-field';
#Directive({
selector: '[appClearable]'
})
export class ClearableDirective implements AfterViewInit {
#ContentChild(MatFormFieldControl) matInput: MatFormFieldControl<any>;
#Input() appClearable: TemplateRef<any>;
private factory: ComponentFactory<ClearButtonComponent>;
constructor(
private vcr: ViewContainerRef,
resolver: ComponentFactoryResolver,
private injector: Injector,
) {
this.factory = resolver.resolveComponentFactory(ClearButtonComponent);
}
ngAfterViewInit(): void {
if (this.appClearable) {
this.vcr.createEmbeddedView(this.appClearable);
} else {
this.vcr.createComponent(this.factory, undefined, this.injector);
}
}
/**
* This is used to clear the formControl oder HTMLInputElement
*/
clear(): void {
if (this.matInput.ngControl) {
this.matInput.ngControl.control.reset();
} else {
this.matInput.value = '';
}
}
}
/**
* This is the markup/component for the clear-button that is shown to the user.
*/
#Component({
selector: 'app-clear-button',
template: `
<button (click)="clearHost()">Clear</button>
`
})
export class ClearButtonComponent {
constructor(#Optional() #SkipSelf() private clearDirective: ClearableDirective) { }
clearHost(): void {
if (this.clearDirective) {
this.clearDirective.clear();
}
}
}
This creates a directive called appClearable and an optional Component for a fallback-layout. Make sure to add the component and the directive to the declarations-array of your module. You can either specify a template to use for providing the user-interface or just use the ClearButtonComponent as a one-size-fits-all solution. The markup looks like this:
<!-- Use it with a template reference -->
<mat-form-field [appClearable]="clearableTmpl">
<input type="text" matInput [formControl]="exampleInput">
</mat-form-field>
<!-- use it without a template reference -->
<mat-form-field appClearable>
<input type="text" matInput [formControl]="exampleInput2">
</mat-form-field>
<ng-template #clearableTmpl>
<button (click)="exampleInput.reset()">Marked-Up reference template</button>
</ng-template>
This works with and without a ngControl/FormControl, but you might need to adjust it to your use-case.
Update:
Option 1 does not work for new angular versions because #ViewChild() returns undefined in ngOnInit() hook. Another hack is to use a dummy MatFormFieldControl -
Option 2
<mat-form-field>
<input matInput hidden>
<ng-content></ng-content>
</mat-form-field>
Edit:
That error is thrown because MatFormField component queries the child content using #ContentChild(MatFormFieldControl) which does not work if you use nested ng-content (MatFormField also uses content projection).
Option 1 (deprecated)
Below is how you can make it work -
#Component({
selector: 'mat-clearable-input',
template: `
<mat-form-field>
<ng-content></ng-content>
</mat-form-field>
`
})
export class FieldComponent implements OnInit {
#ContentChild(MatFormFieldControl) _control: MatFormFieldControl<any>;
#ViewChild(MatFormField) _matFormField: MatFormField;
ngOnInit() {
this._matFormField._control = this._control;
}
}
Please checkout this stackBlitz. Also, there is this issue created in github already.
As of Angular 14 in 2022, the issue for the MatFormField has been closed against angular/angular#37319, without a solution. If you need this to work, the following seems to be the best solution that's possible now to use <ng-content> with mat-form-field:
#Component({
selector: 'my-input-wrapper',
template: `
<mat-form-field appearance="standard">
<ng-content></ng-content>
<!-- make sure this is destroyed so all bindings/subscriptions are removed-->
<input *ngIf="isBeforeViewInit$$ | async" hidden matInput />
</mat-form-field>
`,
});
class MyInputWrapper implement AfterViewInit {
isBeforeViewInit$$ = new BehaviorSubject(true);
#ContentChild(MatFormFieldControl) matFormControl: MatFormFieldControl<unknown>;
#ViewChild(MatFormField) matFormField: MatFormField;
ngAfterViewInit() {
// replace the reference to the dummy control
this.matFormField._control = this.matFormControl;
// force the form field to rebind everything to the actual control
this.matFormField.ngAfterContentInit();
this.isBeforeViewInit$$.next(false);
}
}
I have the following code written to debounce and delay the spamming of button presses:
app.directive.ts:
// Debounce click method for buttons to prevent spamming during asynchronous function waits
import { Directive, EventEmitter, HostListener, Input, OnDestroy, OnInit, Output } from '#angular/core';
import { Subject, Subscription } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
#Directive({
selector: '[appDebounceClick]'
})
export class DebounceClickDirective implements OnInit, OnDestroy {
#Input() debounceTime = 500;
#Output() debounceClick = new EventEmitter();
private clicks = new Subject();
private subscription: Subscription;
constructor() { }
ngOnInit() {
this.subscription = this.clicks.pipe(
debounceTime(this.debounceTime)
).subscribe(e => this.debounceClick.emit(e));
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
#HostListener('click', ['$event'])
clickEvent(event) {
event.preventDefault();
event.stopPropagation();
this.clicks.next(event);
}
}
app.component.html:
<button mat-raised-button appDebounceClick (debounceClick)="buttonPressed()" [debounceTime]="700">Example Button</button>
My end goal is to have a text box that will only call a function after the user has stopped typing for a certain amount of seconds (very similar to the button). How would I make a similar directive to instead work for the texting of a text box key press instead of a button click?
EDIT:
Here is my current input textbox HTML (not debounced):
<form class="form">
<mat-form-field class="full-width" (keyup)="exampleFunction('exampleInputString')">
<input matInput placeholder="Input something...">
</mat-form-field>
</form>
The solution was fairly simple.
directive.ts:
// Change click event to keyup event
#HostListener('click', ['$event']) to #HostListener('keyup', ['$event'])
component.html:
<form class="form">
<mat-form-field class="full-width" appDebounceClick (debounceClick)="exampleFunction('exampleInputString')" [debounceTime]="700">
<input matInput placeholder="Input something...">
</mat-form-field>
</form>
Using debounce directly with onChange even on an input works very nicely. Something like this:
<input type="text" id="myId" onChange={debounce(400, functionToCall())}/>
So I am having a very weird problem with Angular reactive form. I asked my instructor, he couldn't figure it out so I have only one place I am wishing to get some help from. It's here.
So I am using a Angular form and the signup.component.html code snippet is:
<form [formGroup]="form" (submit)="onSaveUser()" *ngIf="!isLoading">
<mat-accordion class="accordion-headers-align">
<mat-expansion-panel [expanded]="1">
<mat-expansion-panel-header>
<mat-panel-title>Personal data</mat-panel-title>
<mat-panel-description>Type your personal details</mat-panel-description>
</mat-expansion-panel-header>
<mat-form-field>
<input matInput type="text" formControlName="name" placeholder="Name">
<mat-error *ngIf="form.get('name').invalid">Please enter your name</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput type="date" formControlName="dob" placeholder="DD/MM/YYYY">
<mat-error *ngIf="form.get('dob').invalid">Please enter your valid date of birth in form of DD/MM/YYYY</mat-error>
</mat-form-field>
and continue like that, ignore the accordion part pls.
Then my signup.component.ts file is:
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormControl, Validators } from '#angular/forms';
import { ActivatedRoute, ParamMap } from '#angular/router';
#Component({
selector: 'app-signup',
templateUrl: './signup.component.html',
styleUrls: ['./signup.component.css']
})
export class SignupComponent implements OnInit {
isLoading = false;
form: FormGroup;
imagePreview: string;
constructor(public userService: UsersService, public route: ActivatedRoute) { }
ngOnInit() {
this.form = new FormGroup({
name: new FormControl(null, {validators: [
Validators.required,
Validators.minLength(3),
// tslint:disable-next-line:quotemark
Validators.pattern(some regex not of concern)
]}),
dob: new FormControl(null, {validators: [
Validators.required,
// tslint:disable-next-line:max-line-length quotemark
Validators.pattern(some regex not of concern)
]}),
and continued like this, nothing special. Just trying to map the form fields. So the form renders like this: The error I am getting in console is:
ERROR TypeError: "this.form is undefined, can't access property "get" of it".
ERROR Error: "formGroup expects a FormGroup instance. Please pass one in.
ERROR TypeError: "_co.form is undefined, can't access property "get" of it".
and I don't understand what is going wrong, I checked the documentation and everything, no help. I am guessing it's a possible bug so wanted to make sure.
I resolve with *ngIf="form", in this way form tag will be rendered only when form is ready.
<form [formGroup]="form" (submit)="onSaveUser()" *ngIf="form">
I'am using a custom input field as a separated component. I add more than one input field in parent component via directive(s): <app-input ...></app-input>
I want to pass the blur event/function to parent component for specific input (in this case password field), to get its value and check this value based on Reg Expression.
I have been searching quite long (also here on SO) and tried really couple of options, but none of them did give the expected clean behavior.
I keep getting the following error:
el => undefined
I think the point is how to pass the exact element Ref to form.component.html so that the function can be applied on the exact specific input field which is password in this case.
I'am using Angular: 5.2.10
input.component.html:
<mat-form-field>
<mat-label>
<mat-icon matPrefix>lock</mat-icon>
<span>{{ inputLabel }}</span>
</mat-label>
<input matInput type="{{inputType}}" name="{{inputName}}" [(ngModel)]="model" (ngModelChange)="modelChange.next($event)" required="{{required}}" pattern="{{regEx}}" (blur)="onFieldBlurClient('name.value')">
<mat-error align="end">
{{ inputError }}
</mat-error>
</mat-form-field>
input.component.ts:
export class InputComponent implements OnInit {
public parentFormGroup: FormGroup;
#Output() onFieldBlur: EventEmitter<any> = new EventEmitter<any>();
#Input('label') inputLabel: string;
#Input('name') inputName: string;
#Input('validationError') inputError: string;
#Input('type') inputType: string;
#Input('pattern') regEx: string;
#Input() required = true;
#Input() model: any;
#Output() modelChange = new EventEmitter();
constructor() {
}
ngOnInit() {
}
onFieldBlurClient(el) {
this.onFieldBlur.emit(true);
}
}
form.component.html:
<app-input [label]="'Password'"
[type]="'password'" [name]="'password'"
[validationError]="'Pass is not valid'"
[required]="true"
[parentFormGroup]="form"
[pattern]="(password_rexExp)" (onFieldBlur)="onBlur()"> .
</app-input>
form.component.ts:
export class FormComponent implements OnInit {
....
public password_regExp
public toogleRegExCheck = false;
...
constructor() {
this.password_regExp = '......';
}
ngOnInit() {
}
public onBlur(el) {
console.log(`el => ${el}`);
if (!password_regExp.test(el)) {
this.toogleRegExCheck = true;
} else {
this.toogleRegExCheck = false;
}
}
}
pass the Event
(onFieldBlur)="onBlur($event)"> .
You need to pass the $event in your template in the function call, in order to get the current item's value.
<app-input [label]="'Password'"
[type]="'password'" [name]="'password'"
[validationError]="'Pass is not valid'"
[required]="true"
[parentFormGroup]="form"
[pattern]="(password_rexExp)" (onFieldBlur)="onBlur($event)"> .