Nested FormArray Error: "cannot find control with path:" - html

I am developing an APP Using Angular 9.
But I'm facing a problem with nested formArray.
I'm having the error: "Cannot find control with path: 'arrayProcedimientos -> subprocedimientos'"
The goal of this form is that arrayProcedimientos is an array, and inside that there are subprocedimientos which is an array too.
Below is my code:
HTML:
<form #f="ngForm" [formGroup]="registros" class="fila-contenido">
<!-- A lot of mat-form-field -->
<div formArrayName="arrayProcedimientos"
*ngFor="let procedimientoArr of registros.get('arrayProcedimientos')['controls']; let i = index">
<div [formGroupName]="i">
<!-- Procedimientos ESTO ES UN ARRAY-->
<mat-form-field appearance="">
<mat-label>Procedimientos</mat-label>
<mat-select formControlName="procedimiento">
<mat-option *ngFor="let procedimiento of procedimientos"
[value]="procedimiento">
{{procedimiento}}
</mat-option>
</mat-select>
</mat-form-field>
<!-- Contenido ESTO ES UN ARRAY-->
<mat-form-field>
<mat-label>Fecha Inicio</mat-label>
<input matInput [matDatepicker]="picker8"
formControlName="fechaInicioProcedimiento">
<mat-datepicker-toggle matSuffix [for]="picker8"></mat-datepicker-toggle>
<mat-datepicker #picker8></mat-datepicker>
</mat-form-field>
<!-- Contenido ESTO ES UN ARRAY-->
<mat-form-field>
<mat-label>Fecha Fin</mat-label>
<input matInput [matDatepicker]="picker9" formControlName="fechaFinProcedimiento">
<mat-datepicker-toggle matSuffix [for]="picker9"></mat-datepicker-toggle>
<mat-datepicker #picker9></mat-datepicker>
</mat-form-field>
<!-- Notas -->
<mat-form-field appearance="fill">
<mat-label>Notas</mat-label>
<textarea matInput formControlName="notasProcedimiento"></textarea>
</mat-form-field>
</div>
{{registros.value | json}}
<div formArrayName="subprocedimientos"
*ngFor="let subprocedimiento of procedimientoArr.get('subprocedimientos')['controls']; let j = index">
<div [formGroupName]="j">
{{registros.value | json}}
</div>
</div>
</div>
</form>
TS:
export class AddRegistroComponent implements OnInit {
data = {
procedimientos: [
{
procedimiento: "",
fechaInicioProcedimiento: "",
fechaFinProcedimiento: "",
notasProcedimiento: "",
subprocedimientos: [
{
subprocedimiento: "",
fechaInicioSubprocedimiento: "",
fechaFinSubprocedimiento: "",
fechaLimiteSubprocedimiento: "",
notasSubprocedimiento: ""
}
]
}
]
}
registros: FormGroup;
constructor(public dialog: MatDialog, private service: AddRegistroService, private fbuilder: FormBuilder) {
this.inicializaFormGroup();
}
inicializaFormGroup() {
this.registros = this.fbuilder.group({
arrayProcedimientos: this.fbuilder.array([]),
});
this.setProcedimientos();
}
setProcedimientos() {
let control = <FormArray>this.registros.controls.arrayProcedimientos;
this.data.procedimientos.forEach(x => {
control.push(this.fbuilder.group({
procedimiento: x.procedimiento,
fechaInicioProcedimiento: x.fechaInicioProcedimiento,
fechaFinProcedimiento: x.fechaFinProcedimiento,
notasProcedimiento: x.notasProcedimiento,
subprocedimientos: this.setSubProcedimientos(x)
}))
})
}
setSubProcedimientos(x) {
let arr = new FormArray([])
x.subprocedimientos.forEach(y => {
arr.push(this.fbuilder.group({
subprocedimiento: y.subprocedimiento,
fechaInicioSubprocedimiento: y.fechaInicioSubprocedimiento,
fechaFinSubprocedimiento: y.fechaFinSubprocedimiento,
fechaLimiteSubprocedimiento: y.fechaLimiteSubprocedimiento,
notasSubprocedimiento: y.notasSubprocedimiento
}))
})
return arr;
}
}
My solution is based on an stackblitz that a mate from StackOverflow made, here is the link of his solution:
https://stackblitz.com/edit/angular-dffny7?file=app%2Fapp.component.html
Thanks for your time.
Alberto.

Related

Setting value of FormControl does not trigger valueChanges

I have an Angular Material Autocomplete and I am trying to trigger the valueChanges subscription by setting a value in ngAfterViewInit. But nothing happens and I dont know why. I also checked similar questions and the answers didnt help me out so far.
This is the relevant code:
options$?: Observable<GroupOption[]>;
/** interne Liste der Optionen zum Filtern, Prüfen etc. von Items */
private options: GroupOption[] = [];
private emptyOption: OptGroupItem = {
group: '',
name: '',
id: '',
};
control = new FormControl('');
ngOnInit(): void {
this.options$ = this.control.valueChanges.pipe(
startWith(''),
map(value => this.createGroupOptions(value)),
tap(value => console.log(value))
);
this.options$.subscribe(res => {
console.log("options");
console.log(res);
})
}
ngAfterViewInit() {
console.log('what is inside field?');
console.log(this.control.value);
this.control.setValue(this.emptyOption);
}
And my html:
<mat-form-field appearance="outline">
<mat-label>{{ label }}</mat-label>
<ng-container>
<input
type="text"
matInput
[formControl]="control"
[matAutocomplete]="auto"
/>
<mat-autocomplete
#auto="matAutocomplete"
(optionSelected)="onDropdownChange($event)"
[displayWith]="displayName"
>
<mat-optgroup
*ngFor="let option of options$ | async"
[label]="option.group"
>
<mat-option *ngFor="let item of option.items" [value]="item">
{{ item.name }}
</mat-option>
</mat-optgroup>
</mat-autocomplete>
</ng-container>
</mat-form-field>
Maybe you know whats going on? Thanks for every help!

How can I call a function in mat-error element?

I want to add an error to my input ! The function will return true if the bith passwords are matching otherwise it will return false
How can I call a boolean function in mat-error !
My Function:
checkPasswords(): Boolean { // here we have the 'passwords' group
this.test = false
let pass = this.changePassForm.value.motpasseconf;
let confirmPass = this.changePassForm.value.Nouveaumotpasse;
console.log(pass);
if (pass === confirmPass) {
return true;
}
}
The ERROR:
<mat-error *ngIf=" "> Not matching Password </mat-error>
The same function in the button works but annd in the error mat dosnt work
TS File:
ngOnInit(): void {
// initialisation du modifier password Form
this.changePassForm = this.formBuilder.group({
'userFullName': [this.user.firstName, [
]],
'Nouveaumotpasse': [this.user.motpasseconf, [
Validators.required,
Validators.minLength(6),
]],
'motpasseconf': [this.user.motpasseconf, [
Validators.required,
Validators.minLength(6),
]]
});
}
HTML Form:
<
div fxFlex="48" class="form" *ngIf="resultUserData != null || resultUserData != undefined">
<form [formGroup]="changePassForm" novalidate>
<mat-form-field class="w-50">
<input id="userFullName" matInput formControlName="userFullName" placeholder="Nom et prénom de l'utilisateur concerné"
type="text" readonly/>
</mat-form-field>
<!-- Nouveau mot de passe -->
<mat-form-field class="w-50">
<input id="Nouveaumotpasse" matInput #passwordWithValidation formControlName="Nouveaumotpasse"
placeholder="Nouveau mot de passe" [type]="hide ? 'password':'text'" required />
<mat-icon matSuffix (click)="hide= !hide" required class="pointer" color="primary">
{{ hide ? 'visibility' : 'visibility_off' }}
</mat-icon>
<mat-error *ngIf="changePassForm.controls['Nouveaumotpasse'].hasError('required')">Votre mot de passe est
<strong>obligatoire</strong>
</mat-error>
<mat-error
*ngIf="(changePassForm.controls['Nouveaumotpasse'].hasError('minlength')) && !changePassForm.controls['Nouveaumotpasse'].hasError('required') && changePassForm.controls['Nouveaumotpasse'].hasError('notEqual')">
Le Mot de passe doit comporter 6 caractères minimum
</mat-error>
<mat-error *ngIf="checkPasswords()">
Les mots de passe saisis ne sont pas identiques</mat-error>
</mat-form-field>
<!-- Confirmer nouveau mot de passe -->
<mat-form-field class="w-50">
<input id="motpasseconf" matInput formControlName=motpasseconf placeholder="Confirmer nouveau mot de passe"
[type]="hide ? 'password':'text'" appConfirmEqualValidator="Nouveaumotpasse" required />
<mat-icon matSuffix (click)="hide= !hide" class="pointer" color="primary">
{{ hide ? 'visibility' : 'visibility_off' }}
</mat-icon>
<mat-error *ngIf="changePassForm.controls['motpasseconf'].hasError('required')">Retapez le mot de passe
pour confirmation </mat-error>
<mat-error
*ngIf="!changePassForm.controls['motpasseconf'].hasError('required') ">
Les mots de passe saisis ne sont pas identiques</mat-error>
</mat-form-field>
Please check edited post
Please check edited post
Angular Material provides an elegant way to deal with errors where several fields are involved: the ErrorStateMatcher (https://material.angular.io/components/input/overview#changing-when-error-messages-are-shown)
Let's say you use reactive forms, you can use a custom validator match on the form group:
this.passwordForm = this.fb.group({
newPassword: ['', [Validators.required, password()]],
confirmPassword: ['', [Validators.required, password()]]
}, { validators: match('newPassword', 'confirmPassword') })
Here is the implementation of the match validator:
import { AbstractControl, ValidatorFn } from '#angular/forms'
import { error } from './error'
export function match(controlName: string, matchingControlName: string): ValidatorFn {
return (control: AbstractControl) => {
const value = control.get(controlName)?.value as string | null
const matchingValue = control.get(matchingControlName)?.value as string | null
return (value && matchingValue && value !== matchingValue) ? error('notMatching') : null
}
}
This validator will check that the two form control (they need to be in the form group on which the validator is applied) have the same value.
You will also need to implement an ErrorStateMatcher:
import { FormControl, FormGroupDirective, NgForm } from '#angular/forms'
import { ErrorStateMatcher } from '#angular/material/core'
export class CrossFieldErrorMatcher implements ErrorStateMatcher {
constructor(
private crossFieldError: string
) { }
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
const isTouched = control?.touched ?? false
const isInvalid = control?.invalid ?? false
const hasCrossFieldError = form?.hasError(this.crossFieldError) ?? false
return isTouched && (isInvalid || hasCrossFieldError)
}
}
Don't forget to initialise it in your component's ts file:
crossFieldErrorMatcher = new CrossFieldErrorMatcher('notMatching')
The error state matcher will dictate when a field should be considered in error. The implementation here makes sure that the field it is applied to is considered in error if its parent has a certain error whose name is passed in the constructor. In our exemple, it will be 'notMatching'. If the error is detected on the form group then the confirm password field will be considered in error.
And then in your html:
<form (ngSubmit)="editPassword(f)" #f="ngForm" [formGroup]="passwordForm" autocomplete="off">
<mat-form-field>
<mat-label>New Password</mat-label>
<input matInput formControlName="newPassword" required autocomplete="new-password" id="new-password">
<mat-error ngs-error-message [formGroup]="passwordForm" propertyName="newPassword"></mat-error>
</mat-form-field>
<mat-form-field>
<mat-label>Confirm new password</mat-label>
<input matInput formControlName="confirmPassword" [errorStateMatcher]="crossFieldErrorMatcher" required autocomplete="new-password" id="new-password-confirmation">
<mat-error *ngIf="confirmPasswordControl.hasError('required')">
Confirm your password is required
</mat-error>
<mat-error *ngIf="passwordForm.hasError('notMatching')">
The two passwords don't match
</mat-error>
</mat-form-field>
<button mat-flat-button type="submit" color="accent"
[disabled]="passwordForm.invalid || !passwordForm.dirty || isLoading">
Submit
</button>
</form>

How to make a function that navigate when user input equal to the If Condition Using angular

I want to make a function that checks the user input if is equal to (username:"marwen" , password:"hamdi") then navigate to the URL 'apps/e-commerce/products'
This is the TypeScript:
ngOnInit(): void{
this.loginForm = this._formBuilder.group({
email : ["", [Validators.required] ],
password: ["", Validators.required]
});
}
check(){
if (this.email.value==="marwen"&& this.password.value==="hamdi"){
this.router.navigate(['apps/e-commerce/products'])
}
}
And this is the HTML part where I want to execute the function when I click on the button.
<form name="loginForm" [formGroup]="loginForm" novalidate >
<mat-form-field appearance="outline" style="color:white;">
<mat-label style="color:white;">Nom d'Utilisateur</mat-label>
<input matInput formControlName="email" id="email">
<mat-icon matSuffix class="secondary-text" style="color:white;">
supervised_user_circle
</mat-icon>
<mat-error *ngIf="loginForm.get('email').hasError('required')">
Nom d'utilisateur est requis
</mat-error>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label style="color:white;">Mot De Passe</mat-label>
<input matInput type="password" formControlName="password" id="password">
<mat-icon matSuffix class="secondary-text" style="color:white;" >vpn_key</mat-icon>
<mat-error>
Mot De Passe est requis
</mat-error>
</mat-form-field>
<button mat-raised-button color="accent" class="submit-button" aria-label="LOG IN" [disabled]="loginForm.invalid" (click)="check()" >
Connexion
</button>
</form>
I hope you are using this validation only as test or mock your login. Anyways, this can help you:
ngOnInit() {
// do something with this subscription
this.loginForm.valueChanges.subscribe(values => {
if (values.email === "marwen" && values.value==="hamdi") {
this.router.navigate(['apps/e-commerce/products'])
}
});
}

Facing issue with binding Data from JSON Object to <mat-select>

I am having trouble in populating the drop-down list from the JSON Response object coming from the API..
component.ts code
for (let k in keys) {
this.form.controls['id'].setValue(data[k].name);
console.log(data[k].name);
}
});
component.html code
<mat-form-field>
<mat-label>Select a User</mat-label>
<mat-select formControlName="id">
<mat-option *ngFor="let opt of options" [value]="opt.data" >
{{ opt.data.name}}
</mat-option>
</mat-select>
</mat-form-field>
console.log(data)
0: {id: 1, name: "User1"}
1: {id: 2, name: "User2"}
2: {id: 3, name: "User3"}
3: {id: 4, name: "User4"}
...
console.log(data[k].name); //This is the data I need on drop-down
User1
User2
User3
User4
...
The console.log data shows the index on every object. My JSON Object is pretty simple though.
It looks like:
[
{
"id": 1,
"name": "User1"
},
{
"id": 2,
"name": "User2"
},...
]
Kindly let me know what am I doing wrong. Thanks.
 EDIT
Here's working
Stackblitz Example
If I understand the question correctly, your API response is a list so just bind that with a for loop to Mat-Option.
HTML Code:
<mat-card>
<form [formGroup]="form" (submit)="add()">
<mat-form-field>
<mat-label>Select a User</mat-label>
<mat-select formControlName="id">
\/\/
<mat-option *ngFor="let key of users" [value]="key">
{{ key.name }}
</mat-option>
</mat-select>
</mat-form-field>
<br/>
<div fxLayout>
<div>
<button
mat-raised-button
color="accent" [disabled] = "form.invalid">Save
</button>
</div>
</div>
</form>
</mat-card>
TS Code:
getUsers(): void {
this.usersService.getUsers().subscribe(users => {
this.users = users;
console.log(this.users)
});
}
Forked_Stackblitz
Try this:
<mat-form-field>
<mat-label>Select a User</mat-label>
<mat-select formControlName="id">
<mat-option *ngFor="let item of list" [value]="item" >
{{ item.name }}
</mat-option>
</mat-select>
</mat-form-field>
Modify your TS file according to this :
list:any=[];
getUsers(): void {
this.usersService.getUsers()
.subscribe(users => {
this.users = users;
const data: User[] = this.users;
console.log('data',data)
console.log("object",Object.keys(data));
const keys: number[] = Object.keys(data) as any;
console.log("keys",keys);
console.log("users",this.users);
for (let k in keys) {
this.form.controls['id'].setValue(data[k].name);
console.log(data[k]);
this.list.push(data[k]);
}
});
}

Material angular auto complete: how to remains the mat-option in the screen after selecting an option?

I'm using the Material auto complete angular.
After i select one option in my mat-option list, my mat-options hide. I need to remain the options after select. Gif showing the problem:
My html
<div class="col-xl-6 margemColunas">
<label>Anunciaremos nas contas *</label>
<input class="form-control inputContasB2W"
#inputContasB2W
[formControl]="contasB2WControl"
[matAutocomplete]="auto"
(matChipInputTokenEnd)="add($event)">
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event, i)">
<mat-option *ngFor="let contaB2W of contasFiltradas | async" [value]="contaB2W">
{{contaB2W}}
</mat-option>
</mat-autocomplete>
</div>
<div class="col-xl-6 alinhaChips">
<mat-chip-list>
<mat-chip
*ngFor="let contaB2W of produto.contasAnunciarB2W; let j = index"
[selectable]="selected"
[removable]="removable"
(removed)="removeTag(i, j)">
{{contaB2W}}
<mat-icon matChipRemove><i class="fa fa-close"></i></mat-icon>
</mat-chip>
</mat-chip-list>
</div>
My ts:
constructor(){
this.contasFiltradas = this.contasB2WControl.valueChanges.pipe(
startWith(null),
map((nomeConta: string | null) => nomeConta ? this._filter(nomeConta) : this.contasB2W.slice()));
}
private _filter(value: string): string[] {
const filterValue = value.toLowerCase();
return this.contasB2W.filter(conta => conta.toLowerCase().indexOf(filterValue) === 0);
}
selected(event: MatAutocompleteSelectedEvent, index: number): void {
this.produtosConfirmadosAnuncio[index].contasAnunciarB2W.push(event.option.viewValue);
this.inputContasB2W.nativeElement.value = '';
}
}
I used workaround to blur() and then focus() again the input.
In ts:
#ViewChild('inputContasB2W') autocompleteInput: ElementRef;
selected(event: MatAutocompleteSelectedEvent) {
[..do your stuff...]
this.autocompleteInput.nativeElement.blur();
setTimeout(() => {
this.autocompleteInput.nativeElement.focus();
}, 100
);
}
If you want to preserve input text you have to save it in _filter() and set the value again before blur() like this.contasB2WControl.setValue(this.lastFilterValue);
<mat-form-field>
<input
matInput
placeholder="New fruit..."
#input
#trigger="matAutocompleteTrigger" // <-- get trigger ref
[formControl]="control"
[matAutocomplete]="auto"
/>
<mat-autocomplete
#auto="matAutocomplete"
(optionSelected)="selected($event)"
>
<mat-option
*ngFor="let option of filteredOptions | async"
[value]="option"
(click)="$event.stopPropagation(); trigger.openPanel()" // <-- use it here
>
{{ option }}
</mat-option>
</mat-autocomplete>
</mat-form-field>