Angular Load new Autocomplete values on each select change event - html

I have several most important fields in my form. In first form, user should choose the company by mat-select-search. After that, by using (selectionChange) I'm calling API with selected value in the first field( this is company id) and return users from that company to autocomplete for recipient fields. It works perfectly, if user does not change his chosen company. But if he do that (selectionChange) method doesn't reload autocomplete with new value. There is a situation in which the user has another company selected, and autocomplete remains from the previous one. Event only loads new one if the previous output was empty.
Is it possible to change this behavior? May be it,s possible to delete previous results from api calling?
My form :
<form [formGroup]="orderForm" novalidate (ngSubmit)="createOrder()">
<div class="row">
<div class="col-md-12">
<mat-form-field class="example-full-width" formGroupName='Company'>
<mat-select ngDefaultControl placeholder="Company" #singleSelect formControlName="id" (openedChange)="onSelectionChange($event)" [(value)]="selectedVariable">
<mat-option>
<ngx-mat-select-search [formControl]="companySelectFilter" [searching]="searching" placeholderLabel="Find company..." noEntriesFoundLabel="'no matching companies found'" ></ngx-mat-select-search>
</mat-option>
<mat-option *ngFor="let company of filteredCompanies | async" [value]="company.id">
{{company.company_name| titlecase}} {{company.company_address| titlecase}}
</mat-option>
</mat-select>
<mat-error *ngIf="orderForm.hasError('notSame')">
Recipient has another company! Select again.
</mat-error>
</mat-form-field>
</div>
</div>
<div class="row" matAutocompleteOrigin #origin="matAutocompleteOrigin">
<div class="col-md-6">
<mat-form-field class="example-full-width" formGroupName='recipient' >
<input type="text"
matInput
formControlName="user_name"
placeholder="First Name"
[matAutocomplete]="auto"
[matAutocompleteConnectedTo]="origin">
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="setFormData($event)">
<mat-option *ngFor="let recipient of filteredOptions | async" [value]="recipient">
<div style="display:flex;flex-wrap: nowrap;align-items:center;justify-content:center;margin: auto;">
<span style="flex-grow: 1;flex: 1 1 33%;"> {{recipient.user_name.toString() | titlecase}} {{recipient.user_surname.toString() | titlecase}} {{recipient.company.company_name.toString() | titlecase}}
</span>
</div>
</mat-option>
</mat-autocomplete>
</mat-form-field>
</div>
<div class="col-md-6" >
<mat-form-field class="example-full-width" formGroupName='recipient'>
<input matInput formControlName="user_surname" placeholder="Last Name"[matAutocomplete]="auto"
[matAutocompleteConnectedTo]="origin" >
<mat-error *ngIf="orderForm.get('recipient.user_surname').hasError('required')">
Last Name is <strong>required</strong>
</mat-error>
</mat-form-field>
</div>
</div>
<div class="col-md-6" >
<mat-form-field class="example-full-width" formGroupName='recipient'>
<input matInput placeholder="Mobile Phone" type="text" formControlName="user_telephone" >
</mat-form-field>
</div>
OnSelected change method:
onSelectionChange(opened: boolean) {
console.log(`opened is : ${opened}`);
if (!opened && this.selectedVariable) {
this.orderForm.get('recipient.user_name').reset()
this.orderForm.get('recipient.user_surname').reset()
this.orderForm.get('recipient.user_telephone').reset()
this.filteredOptions = this.orderForm
.get('recipient.user_name')
.valueChanges.pipe(
startWith(""),
debounceTime(400),
distinctUntilChanged(),
switchMap((val) => {
return this.doFilter(this.selectedVariable, val || '');
})
);
}
}
And doFilter method :
doFilter(id: number, val: any): Observable<any[]> {
return this.recipientService.getRecipientsByCompany(id).pipe(
map((response) =>
response.filter((option) => {
return (
option.user_name
.toString()
.toLowerCase()
.indexOf(val.toString().toLowerCase()) === 0 ||
option.user_surname
.toString()
.toLowerCase()
.indexOf(val.toString().toLowerCase()) === 0
);
})
)
);
}

Finally, I found a solution. My mistake was I trying to retrieve the data from API after page loaded. Now, I load all the recipients in NgOnInit method and filter the data after user change the company.
LoadRecipients method which we put into ngOnInit:
this.recipientService
.getRecipientsObs()
.subscribe((recipients) => (this.recipients = recipients));
}
And new OnChange method :
selectedVariable: any;
onSelectionChange(opened: boolean) {
console.log(`opened is : ${opened}`);
console.log(this.selectedVariable)
if (!opened && this.selectedVariable) {
this.orderForm.get("recipient.user_name").reset();
this.orderForm.get("recipient.user_surname").reset();
this.orderForm.get("recipient.user_telephone").reset();
const formsInput = merge(
this.orderForm.get('recipient.user_name').valueChanges,
this.orderForm.get('recipient.user_surname').valueChanges
);
formsInput.pipe(
startWith(""),
debounceTime(400),
distinctUntilChanged(),
map((search) => {
if (!this.recipients) {
return [];
} else {
search = search.toLowerCase();
}
return this.recipients.filter(
(recipient) =>
recipient.company.id === this.selectedVariable &&
recipient.user_name.toLowerCase().indexOf(search) > -1 &&
recipient.user_surname.toLowerCase().indexOf(search) > -1
);
}),
delay(500)
)
.subscribe(
(filteredRecipients) => {
this.searching = false;
this.filteredRecipients.next(filteredRecipients);
},
(error) => {
this.searching = false;
}
);
}
}

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 to trigger mat chip error validation?

I'm doing a error validation for a field with mat-chip, I'm able to pop up the error validation message only after user add a single mat-chip value, then remove the value. I want to make it where when user click on the field then go to a next field of the form without adding any value, it'll already trigger the error message. Below is my code snippet
HTML :
<form [formGroup]="positionForm">
<div fxLayout="column" fxLayoutAlign="space-between stretch">
<mat-form-field appearance="outline">
<mat-label>Title</mat-label>
<input matInput formControlName="title" type="text" placeholder="Title" required>
<mat-error *ngIf="positionForm.controls.title.invalid">This field is required.</mat-error>
</mat-form-field>
<mat-form-field class="chip-list" appearance="outline" (click)="formValue()">
<mat-label>Skills *</mat-label>
<mat-chip-list #chipSkills>
<mat-chip *ngFor="let skill of positionForm.get('skills').controls; let i = index" [selectable]="selectable"
[removable]="removable" (removed)="removeSkills(i)" (click)="formValue()">
{{skill.value}}
<mat-icon class="remove-chip" matChipRemove *ngIf="removable">cancel</mat-icon>
</mat-chip>
<input placeholder="Enter Skills..."
[matChipInputFor]="chipSkills"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="addOnBlur"
(matChipInputTokenEnd)="addEditSkill($event)" required>
</mat-chip-list>
<mat-error *ngIf="positionForm.controls.skills.errors">Atleast 1 skill need to be added </mat-error>
</mat-form-field>
</form>
TS :
title = new FormControl('', [Validators.required,this.customValidator.NoWhiteSpaceValidator]);
skills = new FormArray([], [Validators.required, this.validateArrayNotEmpty] );
validateArrayNotEmpty(chip: FormControl) {
if (chip.value && chip.value.length === 0) {
return {
validateArrayNotEmpty: { valid: false }
};
}
return null;
}

Adding new user in database from angular+node.js webapp adds/duplicates itself

As I register new user, after few seconds , the same api calls are made(I checked in terminal for data that I logged...api calls are made...not a problem in database) and a duplicate entry of that user is stored in database, it take some time before it duplicates the user. I tried making email field unique to avoid duplicate entries, but then it crashes node server due to MYSQL error for duplicate value for email key. I made user object global, Reset the form after each user, but nothing worked it only delayed duplication a little bit.
HTML Signup Form
<div class="container">
<mat-card class="card">
<mat-card-title>SIGNUP</mat-card-title>
<mat-card class="form-card">
<form [formGroup]="signupForm" #form="ngForm" (ngSubmit)="register(form)">
<mat-form-field appearance='outline'>
<mat-label>FirstName</mat-label>
<input matInput formControlName="first_name" type="text" placeholder="FirstName" required/>
<mat-error>
<div *ngIf="signupForm.get('first_name').hasError('required')">FirstName is <strong>required</strong>.</div>
</mat-error>
</mat-form-field>
<mat-form-field appearance='outline'>
<mat-label>LastName</mat-label>
<input matInput formControlName="last_name" type="text" placeholder="LastName" required/>
<mat-error>
<div *ngIf="signupForm.get('last_name').hasError('required')">LastName is <strong>required</strong>.</div>
</mat-error>
</mat-form-field>
<br>
<mat-form-field appearance='outline' class="full-width">
<mat-label>Email</mat-label>
<input matInput formControlName="email" type="email" placeholder="Email" required/>
<mat-icon matSuffix>email</mat-icon>
<mat-error>
<div *ngIf="signupForm.get('email').hasError('email')">Enter valid Email ID.</div>
<div *ngIf="signupForm.get('email').hasError('required')">Email is <strong>required</strong>.</div>
</mat-error>
</mat-form-field>
<br>
<mat-form-field appearance='outline' class="full-width">
<mat-label>Password</mat-label>
<input matInput formControlName="pass" [type]="hidePass ? 'password' : 'text'" placeholder="Password" required/>
<button mat-icon-button matSuffix (click)="hidePass = !hidePass">
<mat-icon>{{hidePass ? 'visibility_off' : 'visibility'}}</mat-icon>
</button>
<mat-error>
<div *ngIf="signupForm.get('pass').hasError('required')">Password is <strong>required</strong>.</div>
<div *ngIf="signupForm.get('pass').hasError('minlength')">Password should be <strong>atleast 6 characters long</strong>.</div>
</mat-error>
</mat-form-field>
<br>
<mat-form-field appearance='outline' class="full-width">
<mat-label>Confirm Password</mat-label>
<input matInput formControlName="conf_pass" [type]="hideConfPass ? 'password' : 'text'" placeholder="Re-enter the Password" [errorStateMatcher]="matcher" required/>
<button mat-icon-button matSuffix (click)="hideConfPass = !hideConfPass">
<mat-icon>{{hideConfPass ? 'visibility_off' : 'visibility'}}</mat-icon>
</button>
<mat-error>
<div *ngIf="signupForm.hasError('notSame')">Password & Confirm Password does not match.</div>
</mat-error>
</mat-form-field>
<br>
<mat-card-actions>
<button type="submit" mat-raised-button class="button full-width" [disabled]="signupForm.invalid">Signup</button>
</mat-card-actions>
<button mat-button class="" [routerLink]="'/login'" >Already have an account</button>
</form>
</mat-card>
</mat-card>
</div>
Function that is called on submit
register(form: NgForm){
// Make an User object to store in database.
this.user = {
firstname: this.signupForm.controls.first_name.value,
lastname: this.signupForm.controls.last_name.value,
email: this.signupForm.controls.email.value,
password: this.signupForm.controls.pass.value
}
this.authService.registerUser(this.user).subscribe((data: any) => {
if (data.statusCode === util.statusCode.OK) {
console.log(data);
}
});
console.log(this.user);
form.resetForm();
}
}
Front-End service (authService)
registerUser(user: User): Observable<any> {
const cpHeaders = new HttpHeaders({'Content-Type': 'application/json'});
const options = {headers: cpHeaders};
return this.http.post(this.baseUrl + '/register-user', user, options)
.pipe(catchError(err => this.handleError(err)));
}
Routes
router.post('/register-user', (req, res) => {
authService.registerUser(req.body, (data) => {
res.send(data);
});
});
Back-End Service (authService)
let registerUser = (data, callback) => {
async.auto({
user: (cb) => {
bcrypt.hash(data.password, saltRounds, function(err, hash) {
var dataToSet = {
"first_name": data.firstname,
"last_name": data.lastname,
"email": data.email,
"password": hash
}
userDAO.registerUser(dataToSet, (err, dbData) => {
if (err) {
cb(null, { "statusCode":404, "statusMessage": "Server is busy" });
return;
}
cb(null, { "statusCode": util.statusCode.OK, "statusMessage": util.statusMessage.USER_ADDED, "result":dataToSet });
});
});
}
}, (err, response) => {
callback(response.user);
})
}
userDAO.registerUser()
let registerUser = (dataToSet, callback) => {
console.log("data in DAO", dataToSet);
console.log("insert into Users set ? ", dataToSet);
dbConfig.getDB().query(`insert into Users set ? `, dataToSet);
}
It shows this CORS error when it calls itself the second time in the browser console
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:3000/register-user. (Reason: CORS request did not succeed).
Http failure response for http://localhost:3000/register-user: 0 Unknown Error auth.service.ts:33:12
ERROR 0 core.js:9110:19
Setting a unique key constraint is meant to protect your datatable but it doesn't mean your server shouldn't protect your datatable too.
In your AuthService, try to retrieve any entry from your database for the provided unique email. If any entry is returned throw an error and don't call userDAO.registerUser().
Check a point where called twice or more through using console.log or debugging with the breaking point. And prevent multiple calling.
Set the email field to the unique key in DB. Throw 'duplicate' error.
Check this a link for CORS issue(https://developer.mozilla.org/ko/docs/Web/HTTP/CORS/Errors)
I hope this helps you. thanks.

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>

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;
}