I would create a form with the possibility to add inputs dynamically
I found a question about the same problem in angular 2 but I can't make it working in my exemple
Here's my component ts file :
export class AjoutProjetComponent implements OnInit {
isLinear = false;
firstFormGroup: FormGroup;
secondFormGroup: FormGroup;
constructor(private _formBuilder: FormBuilder) {}
ngOnInit() {
this.secondFormGroup = this._formBuilder.group({
pers: [this._formBuilder.array([this.createItem()])]
});
}
createItem(): FormGroup {
return this._formBuilder.group({
name: ['', Validators.required]
poste: ['', Validators.required],
});
}
addItem(): void {
const control = < FormArray > this.secondFormGroup.controls['pers'];
control.push(this.createItem());
}
}
then HTML file
<mat-step [stepControl]="secondFormGroup">
<form [formGroup]="secondFormGroup">
<ng-template matStepLabel>Constituez votre équipe</ng-template>
<div formArrayName="pers">
<mat-form-field *ngFor="let control of secondFormGroup.controls.pers.controls; let i= index">
<input matInput placeholder="Nom collaborateur" formControlName="name" required>
</mat-form-field>
</div>
</form>
</mat-step>
<div>{{secondFormGroup.value | json}}</div>
When I click in my favorite icon I get this error :
ERROR TypeError: control.push is not a function at AjoutProjetComponent.addItem
How can I make adding dynamically inputs working ?
UPDATE
I have updated my html code so that I could print two inputs but when I run my code I get this error now
ERROR Error: Cannot find control with path: 'pers -> name'
You did not declare your FormArray properly. You use arrays only to initialize simple FormControls, not FormGroups or FormControls, change to :
this.secondFormGroup = this._formBuilder.group({
pers: this._formBuilder.array([this.createItem()]) // remove opening and closing brackets
});
To see the inputs added dynamically to the html, you need to use an ngFor loop. I think you somewhat misunderstood the usage of formArrayName, which only adds context to the template to use with FormArrays. Try this:
<ng-container formArrayName="pers">
<input placeholder="Address"
*ngFor="let control of secondFormGroup.controls.pers.controls"
[formControl]="control.controls.name" required />
</ng-container>
And read more about FormArrayName directive here
Related
I am trying to implement a reactive angular form where either A or B has to be entered. A is a unique id and B is a set of values which identify the id. Now I try to validate a Form that is valid if either A is entered or B is entered including all the required values. I found several solutions that implement this behavior based on FormFields but was not able to get it working with the group of values.
<form class="container" [formGroup]="myForm" (ngSubmit)="onSubmit()">
<mat-form-field class="w-1/2">
<mat-label>ID</mat-label>
<input matInput type="number" formControlName="id">
</mat-form-field>
<div class="grid grid-cols-3 gap-4" formGroupName="innerGroup">
<mat-form-field>
<mat-label>First Name</mat-label>
<input matInput type="number" formControlName="firstName">
</mat-form-field>
<mat-form-field>
<mat-label>Last Name</mat-label>
<input matInput type="number" formControlName="lastName">
</mat-form-field>
</div>
</form>
My first idea was to override the default validator for the form but I could not figure out how to do that. Not even sure if it would be possible. I was trying to adjust https://stackoverflow.com/a/48714721 to work in my scenario but I had no idea how to get it to work because of the additional complexity with the inner form group.
Using angular 14 I was able to produce a similar result to what you are describing I am not sure it will 100% solve your issue however it might help.
Basically what I did was create a validator function that is to be applied at the group level. This validator will check the valid state of any given controls be they a FormGroup or a FormControl. However, this alone will not solve the problem as if you have a form group angular will see that any underling control or group that is invalid will also invalidate the parent. So what I did was call .disable() on any control that was being checked by the validator function. This will disable the UI element and disable validation checking by angular allowing the parent to be considered valid when one of the children is valid but the other is invalid effectively creating a one and only one validator.
My specific example I was trying to get the OnlyOneValidator to work for a MatStepper.
Validator
export function onlyOneValidator(controlKeys: string[]) {
return (control: AbstractControl): ValidationErrors | null => {
let countOfValidControls = 0;
for (let key of controlKeys) {
const controlToCheck = control.get(key);
if (controlToCheck === null || controlToCheck === undefined) {
throw new Error(`Error: Invalid control key specified key was ${key}`);
}
countOfValidControls += controlToCheck?.valid ? 1 : 0;
}
if (countOfValidControls !== 1) {
// the count is not exactly one
return {
onlyOneValid: {
actualValidCount: countOfValidControls,
expectedValidCount: 1
}
};
}
return null;
};
}
Controller
#Component({
selector: "app-equipment-creation-page",
templateUrl: "./equipment-creation-page.component.html",
styleUrls: ["./equipment-creation-page.component.scss"],
})
export class EquipmentCreationPageComponent implements OnInit, OnDestroy {
public categories = [null, "Tools", "Vehicles"];
constructor(private _formBuilder: FormBuilder) {}
public categoryInformationGroup = this._formBuilder.group({
existingCategory: this._formBuilder.group({
category: new FormControl(null, [ Validators.required ])
}),
newCategory: this._formBuilder.group({
name: new FormControl("", [Validators.required]),
description: new FormControl("", [Validators.required])
})
}, {
validators: [
onlyOneValidator(["existingCategory", "newCategory"])
],
});
public ngOnDestroy(): void {
this.subscriptions.forEach(sub => {
sub.unsubscribe();
});
}
private subscriptions: Subscription[] = [];
public ngOnInit(): void {
this.subscriptions.push(this.categoryInformationGroup.controls.existingCategory.statusChanges.pipe(
tap((status: string) => {
if (status === "VALID") {
this.categoryInformationGroup.controls.newCategory.disable();
} else {
this.categoryInformationGroup.controls.newCategory.enable();
}
})
).subscribe());
this.subscriptions.push(this.categoryInformationGroup.controls.newCategory.statusChanges.pipe(
tap((status: string) => {
if (status === "VALID") {
this.categoryInformationGroup.controls.existingCategory.disable();
} else {
this.categoryInformationGroup.controls.existingCategory.enable();
}
})
).subscribe());
}
}
Template
<form [formGroup]="categoryInformationGroup.controls.existingCategory">
<mat-form-field>
<mat-label>Apply to existing category?</mat-label>
<mat-select formControlName="category">
<mat-option *ngFor="let category of categories" [value]="category">
{{ category ?? "None" }}
</mat-option>
</mat-select>
</mat-form-field>
</form>
OR
<form [formGroup]="categoryInformationGroup.controls.newCategory">
<mat-form-field>
<mat-label>Create New Category</mat-label>
<input matInput formControlName="name" placeholder="Name">
<mat-error *ngIf="categoryInformationGroup.controls.newCategory.controls.name.hasError('required')">This field
is required
</mat-error>
</mat-form-field>
<mat-form-field>
<mat-label>Create New Category</mat-label>
<input matInput formControlName="description" placeholder="Description">
<mat-error *ngIf="categoryInformationGroup.controls.newCategory.controls.description.hasError('required')">
This field is required
</mat-error>
</mat-form-field>
</form>
Hopefully this helps or at least gives you some ideas about how to approach this. If anyone else has any thoughts on this please let me know I would love to find a better way to do this.
OK it's a bit more complicated than the headline..
This form I am working on is a form group. It has a few fields ( supplement name, description and tags) the supplement name one is what I need help with as I have not worked on a complicated form like this and want to get it right and not just offer a messy patch job.
Here is the expected logical order of what happens
user adds a new supplement by clicking on the field and begins typing "creatine" for example
there is a query sent out that fetches products based on the entry into the input and
returns a JSON that are offered as suggestions
user clicks the suggestion "creatine"
field is populated and binds
we add another entry through the "add option" and repeat for X amount of products we want to
add.
What actually happens
user adds new supplement by clicking the field and types "creatine" suggestion request is
sent off and populates the dropdown
user clicks on the suggestion "creatine" the field takes that value
value is actually blank
user adds another supplement but the previous selection is in the field
user clears it and types again
value is blank
What needs to happen is the user can add X amount of supplements and able to grab whatever option from the dropdown recommendation and it is added to the form group array and does not interfere with the other form group array values.
I know this is not the right way to bind the form and I don't think it's right the way i'm binding the mat input field to trigger the query and this is the reason why I'm asking the question again, to not offer a patch job.
Component code
import { Subscription } from 'rxjs/Subscription';
import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '#angular/forms';
import { UtilitiesService } from '../../utilities/utilities.service';
import { GetSupplementsService } from './get-supplements.service';
#Component({
selector: 'app-supplements',
templateUrl: './supplements.component.html',
styleUrls: ['./supplements.component.css'],
providers: [GetSupplementsService],
})
export class SupplementsComponent implements OnInit {
supplementForm: FormGroup;
queryField: FormControl = new FormControl();
private supplementInventoryResults: Array<ISupplementInventoryResponse>;
private eventForm: FormGroup;
private searchResults: any;
private searchSubscription: Subscription;
private addSupplementSubscription: Subscription;
subcription: Subscription;
constructor (
private bottomSheet: MatBottomSheet,
private _fb: FormBuilder,
private ref: ChangeDetectorRef,
private _utils: UtilitiesService,
private getSupplements: GetSupplementsService,
private router: Router
) { }
public ngOnInit(): void {
this.browsingStackHistory = false;
this.loading = true;
this.supplementForm = this._fb.group({ // the form in question
entryArray: this._fb.array([
this.getUnit()
])
});
this.searchSubscription =
this.queryField.valueChanges
.debounceTime(600)
.distinctUntilChanged()
.switchMap((query) => this.getSupplements.search_supplement_by_category(query))
.subscribe((result) => {
this.searchResults = result;
});
}
public ngOnDestroy(): void {
this.subcription.unsubscribe();
}
private getUnit(): FormGroup {
return this._fb.group({
supplementName: [''],
description: [''],
tags: ['']
});
}
private addUnit(): void {
const control = <FormArray>this.supplementForm.controls['entryArray'];
control.push(this.getUnit());
}
private removeUnit(i: number): void {
const control = <FormArray>this.supplementForm.controls['entryArray'];
control.removeAt(i);
}
private addSupplement(): void { // this will do the post to the service
const supplementObject = {
start: this._utils.get_upload_time(),
data: this.supplementForm.value,
rating: 0
};
}
}
Template
[![<mat-tab label="Add Stack (Test)">
<div style="padding:8px;">
<div fxLayout="row wrap">
<div fxFlex.gt-sm="50%" fxFlex="100">
<h1>Add New Supplements Stack</h1>
<form \[formGroup\]="supplementForm" class="log-workout">
<!-- Start form units array with first row must and dynamically add more -->
<div fxLayout="column" fxLayoutAlign="center center" class="row-height">
<div formArrayName="entryArray">
<mat-divider></mat-divider>
<!-- loop throught units -->
<div *ngFor="let reps of supplementForm.controls.entryArray.controls; let i=index">
<!-- row divider show for every nex row exclude if first row -->
<mat-divider *ngIf="supplementForm.controls.entryArray.controls.length > 1 && i > 0"></mat-divider>
<br>
<!-- group name in this case row index -->
<div \[formGroupName\]="i">
<!-- unit name input field -->
<div class="row">
<mat-form-field class="example-form">
<input matInput placeholder="Supplement Name" \[formControl\]="addSupplementField"
formControlName="supplementName" \[matAutocomplete\]="auto">
<mat-autocomplete #auto="matAutocomplete">
<mat-option *ngFor="let product of supplementResults" \[value\]="product?.product_name">
<img class="example-option-img" aria-hidden \[src\]="product?.product_image" height="25">
{{product?.product_name}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
<mat-form-field class="example-form">
<input matInput placeholder="Description" formControlName="description" required>
</mat-form-field>
<mat-form-field class="example-form">
<input matInput placeholder="Tags" formControlName="tags" required>
</mat-form-field>
</div>
<!-- row delete button, hidden if there is just one row -->
<button mat-mini-fab color="warn" *ngIf="supplementForm.controls.entryArray.controls.length > 1"
(click)="removeUnit(i)">
<mat-icon>delete forever</mat-icon>
</button>
</div>
</div>
<!-- New unit button -->
<mat-divider></mat-divider>
<mat-card-actions>
<button mat-raised-button (click)="addUnit()">
<mat-icon>add box</mat-icon>
Add Other Product
</button>
</mat-card-actions>
<button mat-raised-button (click)="addSupplement()">
<mat-icon>add box</mat-icon>
Add Supplement
</button>
</div>
</div>
<!-- End form units array -->
</form>
</div>
</div>
</div>][1]][1]
Having the below when the getUnit() function is called apparently binds it in the sense it will operate independently and without conflicts.
private getUnit(): FormGroup {
const formGroup = this._fb.group({
supplementName: [''],
review: [''],
rating: [''],
notes: [''],
tags: ['']
});
formGroup.get('supplementName').valueChanges
.debounceTime(300)
.distinctUntilChanged()
.switchMap((search) => this.getSupplements.search_supplement_by_category(search))
.subscribe((products) => {
this.supplementResults = products;
});
return formGroup;
}
I have a service with a FormGroup named mainForm-
export class DepositFormDataService{
this.mainForm = this.formBuilder.group({
title: ['', Validators.required],
category: '',
type: '',
languages: this.formBuilder.array([]),
});
}
get title(): FormControl{
return this.mainForm.get('title') as FormControl;
}
}
In a component I use this service-
constructor(public depositFormDataService: DepositFormDataService) {}
HTML:
<input matInput [formControl]="depositFormDataService.title">
When the user writes "b" for example I see in the debug a difference if I look on the FormGroup depositFormDataService.mainForm:
in: Controls --> title --> value = b
in: value --> title --> null
Why?
I found this post in git-
https://github.com/angular/angular/issues/13792
I tries to do this hack in the component-
ngOnInit() {
this.onChanges();
}
onChanges(): void {
this.depositFormDataService.title.valueChanges
.pipe(takeUntil(this.titleDestroy))
.subscribe(value => {
this.depositFormDataService.mainForm.get('title').patchValue(value, {emitEvent: false});
}
But it did not work and the value was still "null".
Any idea how to make the value be sync with the value inside the controls?
It causes a strange problem that depositFormDataService.mainForm.status differs from the depositFormDataService.title.status.
I think In the HTML you should use formControlName instead of formcontrol like this:
<input formControlName="title" matInput >
I have a FormGroup that was created like that:
form: FormGroup;
constructor(private _formBuilder: FormBuilder) { }
this.form = this._formBuilder.group({
name: ['', Validators.required],
email: ['', Validators.required, Validators.email]
});
When an event occurs I want to disable those inputs, so, in the HTML I added:
<input class="form-control" placeholder="Name" name="name" formControlName="name" [(ngModel)]="name" autocomplete="off" [disabled]="isDisabled" required>
<input class="form-control" placeholder="Email" name="email" formControlName="email" [(ngModel)]="email" email="true" autocomplete="off" [disabled]="isDisabled" required>
Where isDisabled is a variable I toggle to true when the said event happens.
As you can imagine, I get the message:
It looks like you're using the disabled attribute with a reactive form
directive. If you set disabled to true
when you set up this control in your component class, the disabled attribute will actually be set in the DOM for
you. We recommend using this approach to avoid 'changed after checked' errors.
Example:
form = new FormGroup({
first: new FormControl({value: 'Nancy', disabled: true}, Validators.required),
last: new FormControl('Drew', Validators.required)
});
So, with the example this warning shows and with a little search I found that I should declare my controls like:
name: [{ value: '', disabled: this.isDisabled }, Validators.required]
The problem is: It is not toggling between disabled/enabled when the variable changes between true/false
How is the correct way of having a variable controlling if an input is enabled or disabled?
I don't want to do it manually (ex: this.form.controls['name'].disable()) because it doesn't seems a very reactive way, I would have to call it inside a good amount of methods. Probably not a good practice.
Thx
You can change the assignment of the variable to a setter method so that you'd have:
set isDisabled(value: boolean) {
this._isDisabled = value;
if(value) {
this.form.controls['name'].disable();
} else {
this.form.controls['name'].enable();
}
}
One solution is creating a directive and using binding for that as described in here
import { NgControl } from '#angular/forms';
#Directive({
selector: '[disableControl]'
})
export class DisableControlDirective {
#Input() set disableControl( condition : boolean ) {
const action = condition ? 'disable' : 'enable';
this.ngControl.control[action]();
}
constructor( private ngControl : NgControl ) {
}
}
then
<input class="form-control" placeholder="Name" name="name" formControlName="name" autocomplete="off" [disableControl]="isDisabled" required>
NOTE:
Doesn't work with Ivy
For input use [readonly] rather than [disabled] and it'll work
The proper way to disable an form control. With reactive forms you should never disable an input from the template. So in whatever method in your component you are calling you should disable the input like this:
this.form.get('name').disable();
Disable TextBox in Angular 7
<div class="center-content tp-spce-hdr">
<div class="container">
<div class="row mx-0 mt-4">
<div class="col-12" style="padding-right: 700px;" >
<div class="form-group">
<label>Email</label>
<input [disabled]="true" type="text" id="email" name="email"
[(ngModel)]="email" class="form-control">
</div>
</div>
</div>
</div>
You can use this code on your ts file.
All controls:
this.form.disable()
this.form.enable()
Some controls
this.form.get('first').disable()
this.form.get('first').enable()
Or Initial set method.
first: new FormControl({value: '', disabled: true}, Validators.required)
In Reactive Form you can disable all form fields by this.form.disable().
In Template Driven Form you can disable all form fields by this.myform.form.disable() where myForm is #ViewChild('form') myForm;
Not the clean or dry'st I imagine. Bu I tried the "set method" and didn't work out of the box...
Needed some refactoring () => {simpleVersion} (hope it helps someone)
component.ts
...
// standard stuff...
form: FormGroup;
isEditing = false;
...
// build the form...
buildForm() {
this.form = this.FormBuilder.group({
key: [{value:'locked', disabled: !this.isEditing}],
name: [],
item: [],
active: [false]
})
}
// map the controls to "this" object
// => i.e. now you can refer to the controls directly (ex. this.yourControlName)
get key() { return <FormControl>this.form.get('key') }
get name() { return <FormControl>this.form.get('name') }
...
// ----------------------------------------
// THE GRAND FINALÉ - disable entire form or individual controls
// ----------------------------------------
toggleEdit() {
if(!this.isEditing) {
this.key.enable(); // controls
this.name.enable();
// this.form.enable(); // the form
this.isEditing = !this.isEditing;
} else {
this.key.disable(); // the controls
this.name.disable(); // the controls
// this.form.disable(); // or the entire form
this.isEditing = !this.isEditing;
}
}
& perhaps overkill on the HTML logic, so hope you find the bonus integrated ngClass toggle just as helpful.
component.html (toggle button)
<div class="btn-group" (click)="toggleEdit()">
<label
class="btn"
role="button"
[ngClass]="{'btn-success': isEditing,
'btn-warning': !isEditing}">toggle edit
</label>
</div>
The solution by creating a directive and using binding for that worked for me in Angular 10 is described in here
Template:
<mat-form-field>
<input matInput class="form-control" formControlName="NameText" [disableControl]="condition" type="text">
</mat-form-field>
TypeScript:
import { Directive, Input } from '#angular/core';
import { NgControl } from '#angular/forms';
#Directive({
selector: '[opDisabled]'
})
export class DisabledDirective {
#Input()
set opDisabled(condition: boolean) {
const action = condition ? 'disable' : 'enable';
setTimeout(() => this.ngControl.control[action]());
}
constructor(private ngControl: NgControl) {}
}
I have a function that enables a control on click.
controlClick(control: any) {
this.form.controls[control.ngControl.name].enable();
}
Originally i was using
control.disabled = false;
But this did not work for controls with <input> for example in my mat-chip-list.
I use FormGroup and disable each control in the constructor
constructor(
private fb: FormBuilder,
private dialogRef: MatDialogRef<EditDialogComponent>,
#Inject(MAT_DIALOG_DATA) data
) {
this.data = data;
this.multiEdit = data.multiSelect;
this.form = new FormGroup({
autoArchive: new FormControl({
value:
this.getPreFill(data.selectedPolicy.autoArchive, this.multiEdit),
disabled: true
/*, Validators.required*/
}),
...
<mat-form-field (click)="controlClick(retrieveChipList)">
<mat-chip-list #retrieveChipList formControlName="retrieveChipList">
<mat-chip
*ngFor="let email of data.selectedPolicy.retrieveEmailsToBeNotified"
(removed)="remove(email)" [selectable]="selectable"
[removable]="removable"
>
{{ email }}
<mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
<input
placeholder="Retrieve Emails to be Notified"
formControlName="retrieveChipList"
[matChipInputFor]="retrieveChipList"
[matChipInputAddOnBlur]="true"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
(matChipInputTokenEnd)="addRetrieveEmails($event)"
/>
</mat-chip-list>
</mat-form-field>
As control can't be accessed in reactive forms. This is due to migration to Ivy. You can use can access the html attribute directly and specify your condition. See this issue #35330 for more details and alternative methods.
[attr.disabled]="true || false"
You can create set and get method to achieve conditionally enable/disable functionality for Angular material Reactive Forms:
*// 1st Step:
set isDisabled(value:boolean) {
if(value){
this.form.controls['Form controller name'].disable(); //you can keep empty if you don't add controller name
}
else{
this.form.controls['Form controller name'].enable();
}
}
// 2nd Step: Add conditions in getter
get isDisabled(){
return condition ? true : false;
}
// 3rd Step
this.form = this._formBuilder.group({
name: [{value: '', disabled: this.isDisabled }, [Validators.required]],
});
Remove [disabled]="isDisabled" from input fields and add ng-disabled="all" and in the event field add ng-model="all"
<body ng-app="">
Click here to disable all the form fields:<input type="checkbox" ng-model="all">
<input class="form-control" placeholder="Name" name="name" formControlName="name" [(ngModel)]="name" autocomplete="off" ng-disabled="all" required>
<input class="form-control" placeholder="Email" name="email" formControlName="email" [(ngModel)]="email" email="true" autocomplete="off" ng-disabled="all" required>
</body>
I want to check whether the dropdown is empty.
Need to show the required message and
If not empty, enable the submit button.
If empty, disable the submit button. Below is my html
Below is my html
<form [formGroup]="myForm" (ngSubmit)="save()" >
<mat-form-field>
<mat-select formControlName="name" placeholder="Element List" (selectionChange)="elementSelectionChange($event)" required>
<mat-option *ngFor="let element of Elements" [value]="element.name">
{{ element.name }}
</mat-option>
</mat-select>
<mat-error *ngIf="myForm.hasError('required', 'name')">Please choose an name</mat-error>
</mat-form-field>
<mat-form-field>
<mat-select formControlName="symbol" placeholder="Symbol List" required>
<mat-option *ngFor="let element of selectedElementSymbol" [value]="element.symbol">
{{ element.symbol }}
</mat-option>
</mat-select>
<mat-error *ngIf="myForm.hasError('required', 'symbol')">Please choose an symbol</mat-error>
</mat-form-field>
<div mat-dialog-actions>
<button mat-button (click)="onNoClick()">Cancel</button>
<button type="submit" mat-button cdkFocusInitial>Add</button>
</div>
</form>
below is my component
export class DialogOverviewExampleDialog {
myForm: FormGroup;
symbol = new FormControl('', Validators.required);
name = new FormControl('', Validators.required);
constructor(
public dialogRef: MatDialogRef<DialogOverviewExampleDialog>,
#Inject(MAT_DIALOG_DATA) public data: any,
private formBuilder: FormBuilder) {
this.myForm = this.formBuilder.group({
name: [this.name],
symbol: [this.symbol],
});
}
save() {
console.log(this.myForm.value);
}
}
updated demo here
You are currently assigning formcontrols to your formcontrol, whereas you want to assign value to your form controls. Below you are assigning form control name to formcontrol name:
WRONG:
name = new FormControl('', Validators.required);
this.myForm = this.formBuilder.group({
'name': [this.name, Validators.required],
// ...
});
so instead, just declare name, do what you want with the value, then assign that value to your form control...
CORRECT:
name: string;
this.myForm = this.formBuilder.group({
'name': [this.name, Validators.required],
// ...
});
Then just add [disabled]="!myForm.valid" on your submit button.
As for the other question, by default Angular material doesn't show error message unless the field has been touched, so you need to have a custom error state matcher that shows the error even when field is not touched (if that is what you want):
import {ErrorStateMatcher} from '#angular/material/core';
export class MyErrorStateMatcher implements ErrorStateMatcher {
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
return !!(control.invalid);
}
}
and in your TS, declare a error state matcher:
matcher = new MyErrorStateMatcher();
and use in template:
<mat-select formControlName="name" ... [errorStateMatcher]="matcher">
Here's your
StackBlitz
To make the submit button disabled (link)
<button type="submit" [disabled]="!myForm.valid" mat-button cdkFocusInitial>Add</button>
In order to check whether the dropdown is empty or not, you need to make the form fields required like
this.myForm = this.formBuilder.group({
'name': [this.name, Validators.required],
'symbol': [this.symbol, [Validators.required]]
});
Inorder to show the error highlight you need to add an ngClass in the templete like.
[ngClass]="{'error': myForm.controls.name.valid == false}"
You have to insert the validator into the form builder object.
Have a quick look at:
https://angular.io/guide/reactive-forms#validatorsrequired
this.heroForm = this.fb.group({
name: ['', [Validators.required] ],
});
As for the button:
<button type="submit" [disabled]="!form.valid" mat-button cdkFocusInitial>Add</button>