Using Mat Option Group in Reactive Forms - angular-material

I am trying to implement a radio button group in a form. This is how I did it:
.ts
yesNo: any = [
'Yes',
'No'
];
createForm() {
this.createPropertyForm = this.fb.group({
latepymtpen: [null],
});
ngOnInit() {
this.createForm();
}
.html
<mat-form-field class="occupy-half-wmargin">
<mat-radio-group formControlName="latepymtpen">
<mat-radio-button *ngFor="let yn of yesNo" [value]="yn">
{{yn}}
</mat-radio-button>
</mat-radio-group>
</mat-form-field>
When I run it, I get this error:
ERROR Error: mat-form-field must contain a MatFormFieldControl.
Can you please tell me what I'm doing wrong? Thank you.

MatFormField is for components like MatInput, MatSelect, and others which implement the MatFormFieldControl interface. MatRadioGroup is not one of them. In other words, you can't use a MatRadioGroup inside a MatFormField the way you have.

Related

Selected option is undefined - Angular material

I want to know from a list which options the user selected.
In both ways the event/the object is undefined.
What is the best way to do it?
My first way:
<mat-form-field>
<mat-select [(value)]="selectedUserType" multiple (selectionChange)="filterByCustomFields()">
<mat-option *ngFor="let custom of filteredRows" [value]="custom.value">
{{custom.fieldHebKey}}
</mat-option>
</mat-select>
</mat-form-field>
And the second:
<mat-select [formControlName]="'customField'" multiple (selectionChange)="filterByCustomFields($event)">
<mat-option *ngFor="let custom of filteredRows" [value]="custom.value">
{{custom.fieldHebKey}}
</mat-option>
</mat-select>
</mat-form-field>
Using reactive forms the second option is usually a better way.
Make sure you have assigned the filteredRows with the fieldHebKey and value property, if the value property is missing you might get a null or undefined value.
And instead of using selectionChange you can listen to value changes event of the form control in ngOnInit
filteredRows = [
{fieldHebKey: 1, value: 'one'},
{fieldHebKey: 2, value: 'two'},
{fieldHebKey: 3, value: 'three'}
]
ngOnInit(){
this.formGroup.get('customField').valueChanges.subscribe(val => {
console.log(val)
})
}

MatDatePicker throws an error when trying to edit the date manually

I have a problem everywhere I use the mat date picker in the project:
ERROR TypeError: Cannot read properties of undefined (reading 'dateInput')
at MatDatepickerInput._onInput (datepicker.js:3145)
at MatDatepickerInput_input_HostBindingHandler (datepicker.js:3388)
at executeListenerWithErrorHandling (core.js:15308)
at wrapListenerIn_markDirtyAndPreventDefault (core.js:15352)
at HTMLInputElement.<anonymous> (platform-browser.js:561)
at ZoneDelegate.invokeTask (zone.js:406)
at Object.onInvokeTask (core.js:28659)
at ZoneDelegate.invokeTask (zone.js:405)
at Zone.runTask (zone.js:178)
at ZoneTask.invokeTask [as invoke] (zone.js:487)
If I select the date using date picker it works fine, but when I m trying to edit it manually it generates that error and it happens everywhere in the project. For example, I have:
<div class="datepicker-wrapper mr-20">
<input [matDatepicker]="fromPicker" formControlName="from" placeholder="dd/mm/yy" />
<mat-datepicker-toggle [for]="fromPicker" matSuffix>
<mat-icon matDatepickerToggleIcon svgIcon="calendar"></mat-icon>
</mat-datepicker-toggle>
<mat-datepicker #fromPicker></mat-datepicker>
</div>
I tried to update the material and moment-adapter versions from 12.2.5 to 12.2.12, but I receive the same error. Also I created a new project and it works fine there.
Does anyone encountered this until now? Thank you in advance!
matInput attribute is missing in the input element.
<input matInput [matDatepicker]="fromPicker" formControlName="from" placeholder="dd/mm/yy" />
Try this
I had the same error. I resolved it by checking the provider MAT_DATE_FORMATS.
The reason of the js error is related to the wrong dateInput format configuration of MAT_DATE_FORMATS provider. Please, check your module where you provide the MAT_DATE_FORMATS and take into account on the property dateInput:
{
provide: MAT_DATE_FORMATS,
useValue: {
display: {
dateInput: '<check-the-format-here>',
}
}
}

Angular: Mat-card keyboard navigation

I am trying to make a mat-card navigable from the keyboard. Right now, when pressing tab the element is focused however the redirect event (should be the same as the click event) isn't triggered when pressing enter.
I've tried keydown.enter and onKeyDown (from a11y package) but no success so far.
HTML
<mat-card role="group" (click)="addQueryParam(group.name)" (keydown.enter)="addQueryParam(group.name)" class="mat-elevation-z0"
[ngClass]="'background-'+index" (mouseout)="mouseOver=false"
(mouseover)="mouseOver=true" style="padding: none; margin: 5px">
Typescript
addQueryParam(groupName) {
this.router.navigate(['/data'], { queryParams: { ['groups.title']: groupName }, queryParamsHandling: 'merge' });
}
Any idea how to solve this issue?
TIA,
Isabela
I suggest you two things:
try using (keyup.enter)=.... I used it a couple of times and it worked well
If that doesn't work try using (keyup) or (keydown) and in your function check if the key code is 13 (enter key code), something like this:
HTML
<mat-card role="group" (click)="addQueryParam(group.name)" (keydown)="addQueryParam($event, group.name)" class="mat-elevation-z0"
[ngClass]="'background-'+index" (mouseout)="mouseOver=false"
(mouseover)="mouseOver=true" style="padding: none; margin: 5px">
Typescript:
addQueryParam($event, groupName) {
if($event.keyCode === 13){
this.router.navigate(['/data'], { queryParams: ...);
}
}
If i remember correctly you can check the type of the event in a field like event.type, or something like that.
Additionally check this discussion out, because theese functions are not well documented, and here you can find som infos :
What are the options for (keyup) in Angular2?
EDIT
I also found this very useful article: https://medium.com/claritydesignsystem/angular-pseudo-events-d4e7f89247ee

Angular2 code on when some change in Textbox how its Reflect back to component

Here i have requirement like if i type type anything in Textbox it should be Reflect back to Component
<label> Search FRom Table:</label> <input (change)="someval()">
Someval(){
//Here i need that TextBox value
}
You can achieve this in Two way data binding method and so on.
Html Looks like following
<label> Search From Table:</label>
<input name="searchTxt" [(ngModel)]="searchTxt" (input)="someval()">
{{searchTxt}} <!-- here iam printed html file also -->
Typescript File:
//Add one variable
public searchTxt:any;
someval(){
console.log("Here iam printed variable",this.searchTxt);
}
Make sure you have to import Forms Module in app module file
other wise it show error like this
app.module.ts
import { FormsModule } from '#angular/forms'
imports: [
FormsModule,
],
For more details about related here, please visit:
https://www.code-sample.com/2017/05/angular2-input-textbox-change-events.html
Angular 2 change event on every keypress

Angular 2: How to link form elements across a dynamically created components?

I have a set of form fields that are in a dynamically created component. The parent Component owns the form tag. However, none of the form fields are being added to the Form. I'm using the ComponentFactoryResolver to create the component:
#Component({
selector: 'fieldset-container',
templateUrl: './fieldset-container.component.html',
styleUrls: ['./fieldset-container.component.scss'],
entryComponents: ALL_FIELD_SETS,
})
export class FieldsetContainerComponent<C> {
fieldsetComponent : ComponentRef<any> = null;
#Input() formGroup : FormGroup;
#ViewChild('fieldSetContainer', {read: ViewContainerRef})
fieldsetContainer : ViewContainerRef;
#Output() onComponentCreation = new EventEmitter<ComponentRef<any>>();
constructor(private resolver : ComponentFactoryResolver) {
}
#Input() set fieldset( fieldset : {component : any, resolve : any }) {
if( !fieldset ) return; // sorry not right
// Inputs need to be in the following format to be resolved properly
let inputProviders = Object.keys(fieldset.resolve).map((resolveName) => {return {provide: resolveName, useValue: fieldset.resolve[resolveName]};});
let resolvedInputs = ReflectiveInjector.resolve(inputProviders);
// We create an injector out of the data we want to pass down and this components injector
let injector = ReflectiveInjector.fromResolvedProviders(resolvedInputs, this.fieldsetContainer.parentInjector);
// We create a factory out of the component we want to create
let factory = this.resolver.resolveComponentFactory(findComponentForFieldset(fieldset.component));
// We create the component using the factory and the injector
let component : ComponentRef<any> = factory.create(injector);
// We insert the component into the dom container
this.fieldsetContainer.insert(component.hostView);
// Destroy the previously created component
if (this.fieldsetComponent) {
this.fieldsetComponent.destroy();
}
this.fieldsetComponent = component;
this.onComponentCreation.emit( this.fieldsetComponent );
}
}
The template:
<div #fieldSetContainer [formGroup]="formGroup"></div>
The usage of the dynamic component:
<form class="form" #omaForm="ngForm">
<div *ngFor="let fieldset of page?.fieldsets">
<fieldset-container [fieldset]="{ component: fieldset, resolve: {} }" (onComponentCreation)="onComponentCreation($event)" [formGroup]="omaForm.form"></fieldset-container>
</div>
</form>
I suspect it has something to do with the injector not being hooked up correctly, but from what I can tell it is chained to the parent. I've set a breakpoint in NgModel and it is passed a null for parent which is the problem. I traced that back up into something that looks compiled and it was just hard coding a null. So I'm not sure how that was created with hard coded nulls in there.
Any ideas on how to fix this?
Ok it turns out it has nothing to do with the dynamic nature of this component. I removed it and defined all of my components inline and it still had the problem. The issue was that having form controls inside a Component that were nested within a form tag is just not supported by Angular out of the box. Once you nest a form control in a component it can't see the NgForm anymore which is crazy.
After reading solutions on the web and seeing that no one had a good solution I designed 2 of my own directives that registered the Form into the DI container up at the NgForm, then using DI hierarchy I could inject that into another Directive that would perform the registration below.
Parent Component Template:
<form nested>
<my-component .../>
</form>
Child Component Template:
<div>
<input name="street" [(ngModel)]="address.street" required nest/>
<input name="city" [(ngModel)]="address.city" required nest/>
<input name="state" [(ngModel)]="address.state" required nest/>
<input name="zip" [(ngModel)]="address.zip" required nest/>
</div>
Once I had this in place then I could bring back my dynamic component and it worked perfectly. It was just really hard to get there.
It's really elegant and simple and doesn't require me to pass the form instance down through the layers like so many suggestions on the web show. And the work to register a form control whether it's 1 layer or 999 layers removed is the same.
IMHO NgForm and NgModel should just do this out of the box for us, but they don't which leads to complicated architecture design to accomplish moderately advanced forms.

Resources