Creating multiple forms using ngFor - angular6

Have to create a form inside each row-detail of a ngx-datatable.
<ngx-datatable-row-detail [rowHeight]="'100%'" #serviceDetailRow (toggle)="onDetailToggle($event)">
<ng-template let-row="row" let-expanded="expanded" ngx-datatable-row-detail-template>
<mat-tab-group>
<mat-tab label="Components">
<mat-card-actions class="component-actions">
<form [formGroup]="addComponentForm" (ngSubmit)="addComponent(row.serviceId)">
<mat-form-field class="add-component">
<input matInput placeholder="Component Name" [formControl]="componentFormControl" [errorStateMatcher]="componenteMatcher">
<mat-error *ngIf="componentFormControl.hasError('pattern') && !componentFormControl.hasError('required')">
Please enter a valid component name
</mat-error>
<mat-error *ngIf="componentFormControl.hasError('minlength') && !componentFormControl.hasError('required')">
Please enter at least 3 characters
</mat-error>
<mat-error *ngIf="componentFormControl.hasError('required')">
Component Name is <strong>required</strong>
</mat-error>
</mat-form-field>
<button type="button" mat-raised-button color="primary">Add Components</button>
</form>
</mat-card-actions>
...
</ngx-datatable-row-detail>
On click of expanded row button, we trigger the expanded row function
toggleExpandRow(row) {
this.servicesTable.rowDetail.toggleExpandRow(row);
// Create component form
console.log(this.services);
this.addComponentForm[row.serviceId] = this.fb.group({
component: this.componentFormControl
});
** component.ts **
export class ServicesComponent implements OnInit {
#ViewChild('servicesTable') servicesTable: any;
addServiceForm: FormGroup;
addComponentForm: FormGroup[] = [];
On trying to attach a formGroup to the componentForm which is an array of FormGroup initialised as an empty array, i get the error
ERROR TypeError: this.form._updateTreeValidity is not a function

Related

Angular set focus to select element on page load

I can set focus to an input field, but not a select. What am I missing?
This works:
<input matInput formControlName="name" required maxlength="100" id="elementFocus" appElementFocus="true" />
This does not
<mat-select formControlName="countryId" required id="elementFocus" appElementFocus="true">
Here's the entire section that exists now, and when the page loads, the 2nd form element (Name) has focus. I need the select to have focus.
<mat-card-content>
<div fxLayout="column" fxLayoutGap="25px" class="container">
<mat-form-field appearance="standard">
<mat-label>Countries</mat-label>
<mat-select formControlName="countryId" #countrySelectRef required>
<mat-option *ngFor="let c of countries" [value]="c.id"
>{{ c.name }}
</mat-option>
</mat-select>
<mat-error *ngIf="hasError(f.countryId)">{{
getErrorMessage(f.countryId)
}}</mat-error>
</mat-form-field>
<mat-form-field appearance="standard">
<mat-label>Name</mat-label>
<input matInput formControlName="name" required maxlength="100" />
<mat-hint align="end">{{ f.name.value.length }} / 100</mat-hint>
<mat-error *ngIf="hasError(f.name)">{{
getErrorMessage(f.name)
}}</mat-error>
</mat-form-field>
</div>
</mat-card-content>
.ts code (narrowed down to all that is relevant, I think).
#ViewChild(MatSelect) countrySelectRef: MatSelect;
constructor(
private formBuilder: FormBuilder,
private activatedRoute: ActivatedRoute,
private router: Router,
private matDialog: MatDialog,
private readonly countryService: CountryService,
private readonly stateService: StateService,
private messageService: UIMessageService) { }
ngOnInit(): void {
const paramId = this.getParam('id');
this.isAdd = !paramId
this.id = !paramId ? uuid() : paramId
this.heading = this.isAdd ? `Add ${this.headingName}` : `Edit ${this.headingName}`
this.initForm();
if (!this.isAdd) {
this.patchForm()
}
}
ngAfterViewInit(): void {
if (this.countrySelectRef) {
this.countrySelectRef.focus();
}
}
Your code has a problem, in the sense that it can trigger a ExpressionChangedAfterItHasBeenCheckedError error, but even with that error, it should work fine. Your code and the error message, described in your comment, don't match up and may indicate that the issue is elsewhere.
I do provide an alternative, which hopefully, will solve your problem. You need to add the A11yModule to the imports array of the module that declare the component that use that template.
Template
<mat-form-field appearance="standard" cdkMonitorSubtreeFocus>
<mat-label>Countries</mat-label>
<mat-select formControlName="countryId" #countrySelectRef required
cdkTrapFocusAutoCapture
cdkTrapFocus="false">
<mat-option *ngFor="let c of countries" [value]="c.id">
{{ c.name }}
</mat-option>
</mat-select>
<mat-error *ngIf="hasError(f.countryId)">
{{ getErrorMessage(f.countryId) }}
</mat-error>
</mat-form-field>
Note the 2 (with 3 inputs in total) directives added:
cdkMonitorSubtreeFocus: considers an element focused if it or any of its children are focused
cdkTrapFocusAutoCapture: Whether the directive should automatically move focus into the trapped region upon initialization and return focus to the previous activeElement upon destruction.
cdkTrapFocus="false": If this value is true, it will trap the focus on that directive indefinitely.
Ref: Material documentation

Routing via MatAutocomplette Angular

I am trying to implement the autocomplete component of Angular Material:
It works fine, it at least displays and I see the data from my json. Is there a way to route on click of the event, that is when something is selected from the autocomplete to a page?
My HTML currently looks like this:
<div class="example-form">
<div class="container">
<form [formGroup]="schoolForm">
<mat-form-field class="example-form">
<input type="text" matInput placeholder="Gib den Namen deiner Schule ein" formControlName="schoolGroup" required
[matAutocomplete]="autoGroup">
<mat-autocomplete #autoGroup="matAutocomplete">
<mat-optgroup *ngFor="let group of schoolGroupOptions | async" [label]="group.letter">
<mat-option *ngFor="let name of group.names" [value]="name">
{{name}}
</mat-option>
</mat-optgroup>
</mat-autocomplete>
</mat-form-field>
</form>
<form [formGroup]="schoolForm">
<mat-form-field class="example-form1">
<input type="text" matInput placeholder="Gib den Nachnamen deines Lehrers ein">
</mat-form-field>
</form>
</div>
<button mat-raised-button color="primary" routerLink="/teachers">Ergebnisse anzeigen</button>
</div>
how should the TS. look like?
Thx for your help
Looking at the documentation we see that MatAutoComplete exposes an event when an option is selected.
#Output() optionSelected
Reading further, we need to add [matAutocomplete] as a directive onto our text input, and pass it a reference to the auto-complete in our template.
To do this, I create a mat-autocomplete and give it the template literal name of #navigable which we pass into the [matAutocomplete] directive.
This gives us the following html template
<mat-form-field>
<input type="text" matInput [matAutocomplete]="navigable">
<mat-autocomplete #navigable (optionSelected)="chooseOption($event)">
<mat-option *ngFor="let group of groups" [value]="group.route">{{group.name}}</mat-option>
</mat-autocomplete>
</mat-form-field>
Note we can bind to (optionSelected) on the mat-autocomplete as it is the event stated in the API to be called when the value is selected for our attached input.
in our .ts file
#Component()
export class MyExampleComponent {
// group dummy object, hard-coded for demonstration purposes
// I've chosen to store the route directly on the group.
// You should choose whichever strategy suits your needs
groups = [
{
name: "One",
route: 'one'
},
{
name: "Two",
route: 'two'
},
{
name: "Three",
route: 'three'
}
];
// inject router for navigation
constructor(private router: Router) {}
chooseOption(event: MatAutocompleteSelectedEvent) {
// destructure the value and navigate.
const {option: {value}} = event
this.router.navigate([value]);
}
}
and that is it. We will now navigate as soon as an option is selected in the dropdown list.

Angular: Submit method executed by clicking another button

I'm using Angular with Angular Material components.
So, I added a login component (form) where the user can type in his email and password. The input field for the password has an eye on the end to enable the user to display the password in plain text if wanted.
Unfortunately, by clicking twice the eye button, the submit method executes which is binded to the submit button.
The simple question is: why?
Template
<form [formGroup]='loginForm' (ngSubmit)="onSubmit()">
<div>
<mat-form-field>
<mat-label for="email">E-Mail</mat-label>
<input matInput type="text" formControlField="email" />
<mat-error>
Test
</mat-error>
</mat-form-field>
</div>
<div>
<mat-form-field>
<mat-label for="password">Password</mat-label>
<input matInput [type]="hide ? 'password' : 'text'" formControlField="password" />
<button mat-icon-button matSuffix class="mat-icon-button mat-button-base" (click)="hide = !hide" [attr.aria-label]="'Hide password'" [attr.aria-pressed]="hide">
<mat-icon>{{ hide ? 'visibility_off' : 'visibility' }}</mat-icon>
</button>
</mat-form-field>
</div>
<button mat-raised-button color="primary" type="submit">Login</button>
Component
export class LoginComponent implements OnInit {
loginForm: FormGroup;
hide = true;
constructor(
private formBuilder: FormBuilder
) {
this.loginForm = this.formBuilder.group({
email: ['', [Validators.required, Validators.email]],
password: ['', [Validators.required]]
})
}
onSubmit() {
window.alert("Login button clicked");
}
Try adding type="button" to your "eye" button.
This will tell the browser not to treat it as a submit button when inside a form.
The reason is that missing type argument from button is treated as default state and by default button element has submit.
The missing value default and invalid value default are the Submit Button state.
Source: https://html.spec.whatwg.org/multipage/form-elements.html#attr-button-type

Set initial value to a Toggle of a form in Angular Material within HTML

I am working with a simple form:
HTML
<mat-card>
<form (submit)="onAddBet(betForm)" #betForm="ngForm">
<mat-form-field>
<textarea #description="ngModel" matInput rows="6" placeholder="Description" name="description" ngModel
required></textarea>
<mat-error *ngIf="description.invalid">Please enter a Bet Description</mat-error>
</mat-form-field>
<mat-slide-toggle #privacy="ngModel" name="privacy" ngModel>Private</mat-slide-toggle>
<mat-slide-toggle #comments="ngModel" name="comments" ngModel>Allow comments</mat-slide-toggle>
<button mat-raised-button color="primary" type="submit">
CREATE
</button>
</form>
</mat-card>
If I press submit without touching any field of the form I get all the fields empty as it should be expected but I would like to get instead of the status of the form, meaning all the fields as "" but in the fields "privacy" and "comments" as false (boolean) (the default appearance of the toggles is as not marked).
I know that this could be easily done by Typescript from the component following the method:
Typescript
onAddBet(form: NgForm) {
if (!form.value.privacy) {
form.value.privacy = false;
console.log(form.value);
} else console.log(form.value);
if (form.invalid) return;
form.resetForm();
}
But I was wondering if there is any HTML attribute to run the same code avoiding to use Typescript
How could I do it?
You can initialize your ngModel in your HTML by using ngModel with a one-way binding
<mat-slide-toggle #privacy="ngModel" name="privacy" [ngModel]="false">Private</mat-slide-toggle>
<mat-slide-toggle #comments="ngModel" name="comments" [ngModel]="false">Allow comments</mat-slide-toggle>
Then in your component
onAddBet(form: NgForm) {
console.log(form.value);
if (form.invalid) return;
form.resetForm();
}
As per the documentation of slide-toggle you can use [checked]="checked" in HTML.

Reset Angular Formly Form Validations on click event

I am implementing dynamic forms using the angular formly module and It's working fine. What functionality I need is Initially there should be a select box which contains multiple options, Based on selection different form fields should be displayed. As I explained I have Implemented and it's working, here what my problem is If I select option 1 and If I submit the form without filling fields, form displaying validation errors that is also cool. But when I select option 2 form fields are changing, but by default, all required fields are showing errors. How can I resist this? Please suggest me.
html
<div class="row">
<mat-form-field class="col-lg-2">
<mat-select placeholder="Form For" (selectionChange)="getSelectedFormName($event)">
<mat-option value="uf001">UF001</mat-option>
<mat-option value="uf002">UF002</mat-option>
<mat-option value="uf003">UF003</mat-option>
</mat-select>
</mat-form-field>
<div class="col-lg-4">
<button type="button" class="btn btn-default btn-one" (click)="getDynamicForm()">GET FORM</button>
</div>
</div>
<form [formGroup]="form" (ngSubmit)="submit(model)" >
<formly-form [model]="model" [fields]="fields" [form]="form" *ngIf="isFormTypeSelected" >
</formly-form>
<button type="submit" class="btn btn-success">Submit</button>
</form>
ts file
getSelectedFormName(eve) {
this.isFormSaved = false;
this.form = new FormGroup({});
this.fields=[];
this.model = {};
this.parentFormName = eve.value;
}
getDynamicForm() {
this.isFormSaved = false;
this.savedFields=[];
this.getDynamicFormBasedOnSelection(this.parentFormName);
//fields getting from api call
}
getDynamicFormBasedOnSelection(formName: string) {
this.auth.getDynamicFormBasedOnSelction(formName, this.userAgencyCode).subscribe(
(result) => {
const str = JSON.stringify(result);
this.fields = JSON.parse(str);
this.isFormTypeSelected = true;
this.addEvents(this.fields);
});
}
Here I'm providing my screens which are for better understanding
Actually form.reset() just reset the form values. You need to reset the form directive too. for eg.
<form [formGroup]='authForm' (submit)='submitForm(formDirective)' #formDirective="ngForm" class="is-mat-form">
<mat-form-field>
<input matInput placeholder="Email ID" formControlName='login'>
<mat-error *ngIf="authForm.controls.login.hasError('required')">
Email is required
</mat-error>
<mat-error *ngIf="authForm.controls.login.hasError('email')">
Please enter a valid email address
</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput type="password" formControlName='password' placeholder="Password">
<mat-error *ngIf="authForm.controls.password.hasError('required')">
Password is required
</mat-error>
<mat-error *ngIf="authForm.controls.password.hasError('minlength')">
Password must be minimum 6 digit long.
</mat-error>
</mat-form-field>
and .ts file is
submitForm(formDirective: FormGroupDirective){
if (this.authForm.invalid) {
console.log('form submitted')
this.authForm.reset()
return;
}
this will reset the form values only, to reset the form error we need to reset the formdirective as well.
submitForm(formDirective: FormGroupDirective){
if (this.authForm.invalid) {
console.log('form submitted')
this.authForm.reset()
formDirective.resetForm();
return;
}