Angular child form field validation - javascript

I have some existing code that's structured more or less like this:
<!-- my-form.html -->
<form ngNativeValidate>
<my-other-component></my-other-component>
<button mat-button type='submit'>Submit</button>
</form>
<!-- my-other-component.html -->
<input name='native-input' required />
<mat-form-field>
<mat-select name='balls' required>
<mat-option value='a'>a</mat-option>
<mat-option value='b'>b</mat-option>
<mat-option value='c'>c</mat-option>
</mat-select>
</mat-form-field>
The problem: while the native input is validated correctly — the browser displays a "Please fill out this field." prompt upon submission when needed — an empty input to balls is happily accepted.
The ngNativeValidate behavior of showing generic/non-descriptive errors only upon submission is fine for now, and we don't have time to convert everything into an Angular form with custom validation messages, so that isn't an option. What I'm looking for is a way to switch from native validation to Angular validation with the least possible effort to maintain an acceptable UX — e.g. simply disabling the submit button until the form input is valid.
As per angular.io/guide/form-validation, I see that I can change ngNativeValidate to #myForm='ngForm' and add [disabled]='myForm.invalid' to the submit button. However, this validation is only applying to form fields directly within my-form.html, not the ones in my-other-component.
Documentation here is really sparse, so I'm not sure what to try next. Does anyone know if it's possible to configure ngForm to validate those descendant components' form fields?

If you want to use template driven form approach then you have to import FormsModule into your #NgModule and apply NgModel directive to all your controls
<input name='native-input' ngModel required />
^^^^^^^
<mat-form-field>
<mat-select name='balls' ngModel required>
^^^^^^^
<mat-option value='a'>a</mat-option>
<mat-option value='b'>b</mat-option>
<mat-option value='c'>c</mat-option>
</mat-select>
</mat-form-field>
Now your child component should be aware about parent form. In order to do that you need to provide ControlContainer token within viewProviders array:
#Component({
selector: 'my-other-component',
template: `
...
`,
viewProviders: [ { provide: ControlContainer, useExisting: NgForm } ]
})
export class MyOtherComponent {
Ng-run Example
See also:
Angular2 nested template driven form

Related

How to display max limit error message without using ts file in angular?

I want to show error message like required error message for max input limit but using below code it work for required but not for maxlength . I also not to use ts file . Is there any way to achieve this. Thanks
<form #featureForm="ngForm">
<mat-form-field class="featureInputForm" appearance="fill">
<mat-label>Feature Namre</mat-label>
<input matInput [(ngModel)]="featureName" required name="featureName" maxlength="64" (ngModelChange)="moduleNameChange($event)" />
<mat-error *ngIf="featureForm.controls['featureName']?.errors?.required">Feature Name is required.</mat-error>
<mat-error *ngIf="featureForm.controls['featureName']?.errors?.maxlength">Maximum limit exceed.</mat-error>
</mat-form-field>
</form>
According to documentation for HTML attribute: maxlength, the browser will generally prevent the user from entering more text than the maxlength attribute allows. This explains why you don't see the error message when typing into the input.
However, if the value is longer than the maxlength on the .ts side of things, then the code you have will render the error text. For instance assuming maxlength=10 and if featureName = '12345678901' <- string is longer than maxlength so error message would render on the page.
See this stackblitz for an example.
There are two approaches to this.
1. Use reactive forms approach
2. Use template forms approach
As per your requirement, you don't want to use the .ts file for validation.
Then you can proceed with template-driven forms in angular rather than proceeding with the reactive forms-driven approach. Reactive Forms driven approach mainly deals with forms of validation-related logic in the .ts file itself. While in the template-driven approach it does handle the logics in the template itself.
For further reading on reactive-forms in angular
For further reading on template-forms in angular
Please refer to the code block below :
<form #featureForm="ngForm">
<mat-form-field class="featureInputForm" appearance="fill">
<input matInput name="featureName" [ngModel]="featureName" maxlength="10" #featureName="ngModel" required>
<div *ngIf="featureName.errors" [ngClass] = "'error'">
<div *ngIf="featureName.errors.maxlength">
Name must be at least 10 characters long.
</div>
<div *ngIf="featureName.errors.required">
featureName is required.
</div>
</div>
</mat-form-field>
</form>

Textarea with ngModel get overwritten in a ngFor loop

I just started working on an Angular project.
In the following code, I would like to use ngFor and ngModel to iterate an array and put the item value as a default value for the text area. And I have an example with two values in the array, all the textareas are always get overwritten by one of them instead of showing the different values on the website. Does anyone know what could be possible reason for that?
<mat-form-field *ngFor = "let item of model.answers; let index = index;" class="example-full-width" appearance="fill">
<mat-label>Cluster Answer</mat-label>
<textarea
matInput
id="{{item['id']}}"
rows="7"
placeholder="Cluster answer"
[(ngModel)]="model.answers[index]['answer']"
name="clusterAnswer"
formControlName="clusterAnswer"
></textarea>
<mat-hint>Define the cluster answer to be suggested</mat-hint>
</mat-form-field>
you must define the value in the form builder in the ts non in the html:
exampe.formBuilder{
test:[defaultValue]
}
you already have ngModel so no need to specify formControlName, use of [(ngModel)] with formControlName has been deprecated
<mat-form-field *ngFor = "let item of model.answers; let index = index;" class="example-full-width" appearance="fill">
<mat-label>Cluster Answer</mat-label>
<textarea
matInput
[id]="item['id']"
rows="7"
placeholder="Cluster answer"
[(ngModel)]="model.answer"
></textarea>
<mat-hint>Define the cluster answer to be suggested</mat-hint>
</mat-form-field>
You need to provide different name attribute value for each individual iteration because the ngModel value uses the value of the name attribute to connect the new control with the provided name to the form.
In your case it's not even necessary to use two way data binding (the bananna in a box [(ngModel)]) because you already have the state of the form inside the form itself so you can just use the [ngModel] to bind the value if there is one and whenever you want to get the new values you can either use #ViewChild(NgForm) form: NgFrom in order to get the form from your view (probably its going to be better to query it using a template variable name. It will look something like <form #f="ngForm"> where f is your template variable. Also check out the View Child Decorator) or inside the ngSubmit event of your form you can provide the value of the form using a template variable. <form #f="ngForm" (ngSubmit)="submitHandler(f.value)">.
I don't recommend using the two way data binding because what happens is that we store the same values at two different locations:
the state of each individual ngModel control that we have
inside the state your component.
Also formControlName directive comes from the Reactive-Forms and it's not good to mix both types of forms inside a single form.
<mat-form-field *ngFor="let item of model.answers; let i = index;" class="example-full-width" appearance="fill">
<mat-label>Cluster Answer</mat-label>
<textarea
matInput
[id]="item['id']"
rows="7"
placeholder="Cluster answer"
[ngModel]="model?.answers?[i]?['answer']"
[name]="'clusterAnswer' + i"
></textarea>
<mat-hint>Define the cluster answer to be suggested</mat-hint>
</mat-form-field>

Adding search functionality in mat-multiple select in angular material

I want to add search functionality in my mat multiple select dropdown.
I have done this thing in Jquery but I want to do same thing in Angular material.
Here is the design pic.
Here is the image
Try this example in stackblitz
stackblitz example with angular 8
in your html file
<h2>Multiple selection</h2>
<p>
<mat-form-field>
<mat-select [formControl]="bankMultiCtrl" placeholder="Banks" [multiple]="true">
<mat-select-search [formControl]="bankMultiFilterCtrl"></mat-select-search>
<mat-option *ngFor="let bank of filteredBanksMulti | async" [value]="bank">
{{bank.name}}
</mat-option>
</mat-select>
</mat-form-field>
</p>
<p>
Selected Banks:
</p>
<ul *ngFor="let bank of bankMultiCtrl?.value">
<li>{{bank.name}}</li>
</ul>
The problem is that the material select doesn't have the functionality for an input. Therefore you need to do a bit of css to make it look good.
<mat-select multiple>
<mat-form-field>
<input matInput placeholer="Search" (input)="filterOptione($event.target.value)" />
</mat-form-field>
<mat-option *ngFor="let option of options" [value]="option">
{{option}}
</mat-option>
</mat-select>
The filter function:
public filterOptions(filter: string): void {
this.options = this._unfilteredOptions.filter(x => x.toLowerCase().includes(filter.toLowerCase()));
}
Instead of using a mat-select, use a mat-autocomplete. https://material.angular.io/components/autocomplete/overview
You can make it work the same as a mat-select by populating it with all values when the input is blank, then filter the auto-complete options when the input value changes.
Again, the trick is to make it show all available options when the input is empty.
Hope this helps.
Edit:
I should add that I use a mat-chip-list (https://material.angular.io/components/chips/overview) for the multi-select part of the equation. When an option is selected, add it to the chip list, and you can add X icons to each chip to remove things from the list.
Granted, this is my personal flavor of a multi-select input, but I thought it might give you some ideas.
My approach for this solution is as follow:
I populated mat-option from an array of object with many parameters. Of course, I used only one or 2 parameters to show, but I want my user to search by any value of the object even the values of the parameters that are not shown in the mat-option, So, in the template I created an input field like the follow
<mat-form-field>
<mat-select multiple>
<input matInput placeholer="Search" (input)="search($event)" />
<mat-option *ngFor="let d of data" [value]="d">
{{d}}
</mat-option>
</mat-select>
</mat-form-field>
And in the .ts file:
AllData:any[]; //of course the type will be the type of your data.
Search(event: any, PropertyToSearch: string)
{
this.data = this.AllData.filter(x =>
{
for (let v of String(event.target.value).trim().split(" "))
{
if (Object.values(x).join(" ").toLowerCase().includes(v.toLowerCase()))
return true;
}
return false;
});
}
In this function, I creates an array of the words entered by the user, and joined all the values of the each object inside the data array and search each word inside that joined string. Why did I do that? Because there is a case when the user by mistake add 2 spaces between 2 words like this "word1 word2".
If I used simple solution like
this.AllData.filter(x => x.toLowerCase().includes(event.target.value.toLowerCase()));
and the user write the words with only one space between the words, the matched result will not shown. Also, my solution make the user use any value he remember to search with.

How to set a default value when using Object.keys in an ngFor loop?

<mat-select required formControlName="country" [(ngModel)]="defaultCountry" placeholder="Country">
<mat-option *ngFor="let key of Object.keys(countryList)" [value]="[key]">{{[key]}}</mat-option>
</mat-select>
I have the above code. The countries are displayed fine in the Material Select, but the default option "DefaultCountry" is not working. I believe this is due to me using Object.keys because the following code does work with the default option.
<mat-select required formControlName="country" [(ngModel)]="defaultCountry" placeholder="Country">
<mat-option *ngFor="let country of deleteLaterCountryList" [value]="country.countryCode">{{country.countryCode}}</mat-option>
</mat-select>
I want to know how I can make my first piece of code work using the default option. I don't want to use the second piece of code.
I really need more details here, but I can see that you are using Reactive Form. In that case, there is no need for ngModel. Let us assume that you are using a formGroup called 'yourForm'.
<form [formGroup]="yourForm">
.
.
<mat-select required formControlName="country" placeholder="Country">
<mat-option *ngFor="let key of Object.keys(countryList)" [value]="[key]">{{[key]}}</mat-option>
</mat-select>
.
.
<form>
On the component.ts, you can use patchValue to assign a value to country formControl, which will be displayed as a default option.
yourForm: FormGroup = this.formBuilder.group({
country: [null, Validators.required],
.
.
// other form controls
});
ngOnInit() {
this.yourForm.patchValue({
country: 'SG'
});
}

Angular 7 - No value accessor for form control with unspecified name attribute

I'm getting this error on my Angular 7 app and I don't know why. Even with some researches it seems that this error doesn't make sense.
This is what I have:
<mat-form-field>
<input matInput placeholder="Name" name="name" [(ngModel)]="data.name" [disabled]="inputReadonly" ngDefaultControl >
</mat-form-field>
I've tried with and withoud ngDefaultControl on mat-form-field and/or input, with and without the name attribute and to move [(ngModel)] on input. Nothing is changed, I'm still receiving the same error.
ngModel has to be specified on the input, not on the mat-form-field. The only purpose of mat-form-field is to apply some Material CSS
make sure you have MatFormFieldModule and MatInputModule imported in your app.module.ts and also declared in #NgModule({... imports:[MatFormFieldModule,MatInputModule, ...],...}

Categories

Resources