Angular filter and binding (ngModelChange) - html

I'm working on the hotel project. I have a reservation screen. Here I ask the hotel name, region name, check-in and check-out dates, the number of adults and children. When the necessary information is entered, when I press the search button, I filter according to the values ​​there. I connected the date filter with html using the (NgModelChange) method, but I couldn't connect the html with the hotel and region methods correctly. I made a filter as an experiment, but I got the wrong result. I don't know where I'm making a mistake. How can I fix this?
.html
<form [formGroup]="form">
<mat-form-field>
<label>Which Hotel?</label>
<input type="text" matInput [matAutocomplete]="name" formControlName="name" (change)="changeHotel()" />
<mat-autocomplete #name="matAutocomplete">
<mat-option *ngFor="let hotel of filteredHotels | async" [value]="hotel">
{{hotel}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
<mat-form-field>
<label>Which Region?</label>
<input type="text" matInput [matAutocomplete]="region" formControlName="region" (change)="changeRegion()" />
<mat-autocomplete #region="matAutocomplete">
<mat-option *ngFor="let region of filteredRegions | async" [value]="region">
{{region}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
<mat-form-field>
<label>Entry Date?</label>
<input matInput [matDatepicker]="mdpStartDate" formControlName="startDate" (ngModelChange)="changeDate()">
<mat-datepicker-toggle matSuffix [for]="mdpStartDate"></mat-datepicker-toggle>
<mat-datepicker #mdpStartDate></mat-datepicker>
</mat-form-field>
<mat-form-field>
<label>Pay Date?</label>
<input matInput [matDatepicker]="mdpEndDate" formControlName="endDate" (ngModelChange)="changeDate()">
<mat-datepicker-toggle matSuffix [for]="mdpEndDate"></mat-datepicker-toggle>
<mat-datepicker #mdpEndDate></mat-datepicker>
</mat-form-field>
<br />
<mat-form-field>
<label>Adult Number?</label>
<input type="number" min="1" matInput formControlName="adult" (ngModelChange)="filterAge()">
</mat-form-field>
<mat-form-field>
<label>Chd Number?</label>
<input type="number" min="0" max="3" value="0" matInput formControlName="chd" (ngModelChange)="filterAge()">
</mat-form-field>
<button type="button" class="btn btn-success" (click)="search()">Search</button>
.ts
form = new FormGroup({
name: new FormControl(''),
region: new FormControl(''),
adult: new FormControl(1),
chd: new FormControl(0),
startDate: new FormControl(),
endDate: new FormControl(),
});
filtered: any[];
showList = false;
name = '';
regions: string[];
hotels: Hotel[] = [];
filteredHotels: Observable<string[]>;
filteredRegions: Observable<string[]>;
ngOnInit() {
this.regions = this.hotels.reduce((arr: string[], current) => {
if (!arr.includes(current.regionName)) {
arr.push(current.regionName);
}
return arr;
}, []);
this.filteredHotels = this.form.get("name").valueChanges
.pipe(
startWith(''),
map(v => {
return this.filterHotels(v)
})
);
this.filteredRegions = this.form.get("region").valueChanges
.pipe(
startWith(''),
map(value => this.filterRegions(value).map(h => h.regionName))
);
this.form.get("adult").valueChanges.
subscribe(v => {
this.adult = new Array(v).map(e => new Object)
});
this.form.get("chd").valueChanges
.subscribe(v => {
this.children = new Array(v).map(e => new Object)
});
this.hotelservice.getHotels().subscribe(
data => {
this.hotels = data;
this.dsHotels.data = data;
this.dsHotels.paginator = this.paginator;
},
err => {
console.error("Hata oluştu: ", err);
}
);
}
private filterHotels(value: string): string[] {
const name = value.toLowerCase();
return this.hotels
.map(x => x.hotelName)
.filter(option => option.toLowerCase().includes(name));
}
private filterRegions(value: string): Hotel[] {
const region = value.toLowerCase();
return this.hotels
.filter(hotel => hotel.regionName.toLowerCase().includes(region));
}
private isEqual(date1: Date, date2: Date) {
return date1.getDate() == date2.getDate() && date1.getMonth() == date2.getMonth() && date1.getFullYear() == date2.getFullYear();
}
private isDataEqualToDate(value1: any, date2: Date) {
if (value1 == null) return true;
return this.isEqual(new Date(value1), date2);
}
private getFilteredDate(): Hotel[] {
return this.hotels.filter(
x => this.isDataEqualToDate(this.form.get("startDate").value, new Date(x.checkInDate))
&& this.isDataEqualToDate(this.form.get("endDate").value, new Date(x.payDate))
);
}
changeHotel() {
this.dsHotels.data = this.filterHotels("value");
}
changeRegion() {
this.dsHotels.data = this.filterRegions("value");
}
changeDate() {
this.dsHotels.data = this.getFilteredDate();
}
filterAge() {
//this.dsHotels.data = this.hotels.filter(x => x.numberOfAd == this.form.get("adult").value && x.numberOfChd == this.form.get("chd").value);
this.dsHotels.data = this.hotels.filter(x => x.numberOfAd == x.numberOfChd);
}
search() {
this.showList = true;
this.filterHotels("");
this.filterRegions("");
this.changeDate();
}

As you created your form with reactive forms you can switch from event binding in template
< ... (change)="changeHotel()"
< ... (ngModelChange)="changeDate()">
to do it inside class.
ngOnInit() {
// ... initial logic
this.form.get('name').valueChanges.subscribe(this.changeHotel.bind(this))
this.form.get('region').valueChanges.subscribe(this.changeRegion.bind(this))
this.form.get('startDate').valueChanges.subscribe(this.changeDate.bind(this))
this.form.get('endDate').valueChanges.subscribe(this.changeDate.bind(this))
this.form.get('adult').valueChanges.subscribe(this.filterAge.bind(this))
this.form.get('chd').valueChanges.subscribe(this.filterAge.bind(this))
}
Reactive form instances like FormGroup and FormControl have a
valueChanges method that returns an observable that emits the latest
values. You can therefore subscribe to valueChanges to update instance
variables or perform operations.
source

Related

How to set the default/prefilled value of a input element in mat-form-field on load?

So I want to implement an html element such that the default data(the variable 'preselectedValue') should be already be entered/prefilled into the input field when the component loads and the user should be able to directly edit the search string.
autocomplete-display-example.html
<mat-form-field appearance="fill">
<mat-label>Assignee</mat-label>
<input type="text" matInput [formControl]="myControl" [matAutocomplete]="auto">
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn">
<mat-option *ngFor="let option of filteredOptions | async" [value]="option">
{{option.name}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
autocomplete-display-example.ts
export class AutocompleteDisplayExample implements OnInit {
myControl = new FormControl<string | User>('');
options: User[] = [{name: 'Mary'}, {name: 'Shelley'}, {name: 'Igor'}];
filteredOptions: Observable<User[]>;
preselectedValue = "John";
ngOnInit() {
this.filteredOptions = this.myControl.valueChanges.pipe(
startWith(''),
map(value => {
const name = typeof value === 'string' ? value : value?.name;
return name ? this._filter(name as string) : this.options.slice();
}),
);
}
displayFn(user: User): string {
return user && user.name ? user.name : '';
}
private _filter(name: string): User[] {
const filterValue = name.toLowerCase();
return this.options.filter(option => option.name.toLowerCase().includes(filterValue));
}
}
Any help would be appreciated.
The value of your mat-option is of type User. So you have to pass it a preselected value of that type. The code in your question does not show the definition of the User class but you could try something like this:
preselectedValue = {name: 'John} as User;
ngOnInit() {
this.myControl.value = preselectedValue;
this.filteredOptions = this.myControl.valueChanges.pipe(
startWith(''),
map(value => {
const name = typeof value === 'string' ? value : value?.name;
return name ? this._filter(name as string) : this.options.slice();
}),
);
}

Angular Dropdown Doesn't Work, Looks Like a List

In my code, I defined a mat-chip-list so that a user can select multiple items from a list of elements. The code doesn't have any errors, but the mat-chip-list doesn't appear as a dropdown. I should be able to select elements from it, but I can't do that either. It just looks like a list as it shown below. What should I do to fix this?
HTML:
<div fxLayout="row" fxLayoutAlign="start center">
<mat-form-field appearance="outline" class="w-100-p" fxFlex>
<mat-label>Product</mat-label>
<mat-chip-list #chipList>
<mat-chip *ngFor="let prm of receiptList"
[selectable]="selectable"
[removable]="removable"
(removed)="removeReceipt(prm)">
{{prm.StockIntegrationCode +' - '+ prm.StockShortName}}
<mat-icon matChipRemove *ngIf="removable">cancel</mat-icon>
</mat-chip>
<input #receiptInput
[formControl]="receiptCtrl"
[matAutocomplete]="auto"
[matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="addOnBlur"
(matChipInputTokenEnd)="addReceipt($event)">
</mat-chip-list>
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="receiptSelected($event)">
<mat-option *ngFor="let receipt of filteredReceipts| async" [value]="receipt">
{{receipt.StockIntegrationCode +' - '+ receipt.StockShortName}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
</div>
TS:
receiptList: IReceipt[] = [];
receipt: IReceipt[];
allReceipts: IReceipt[] = [];
selectable = true;
removable = true;
addOnBlur = true;
readonly separatorKeysCodes: number[] = [ENTER, COMMA];
receiptCtrl = new FormControl();
filteredReceipts: Observable<IReceipt[]>;
#ViewChild('receiptInput', { read: MatAutocompleteTrigger, static: false }) trigger: MatAutocompleteTrigger;
#ViewChild('receiptInput', { static: false }) receiptInput: ElementRef<HTMLInputElement>;
#ViewChild('auto', { static: false }) matAutocomplete: MatAutocomplete;
constructor(
private _labService: LabService,
) {
_labService.onReceiptListChanged.subscribe(
(response: IReceipt[]) => {
this.receiptList = response;
}
);
this.filteredReceipts = this.receiptCtrl.valueChanges.pipe(
startWith(null),
map((prm: IReceipt | null) =>
prm ? this._filterSupplier(prm) : this.allReceipts.slice()
)
);
}
addReceipt(event: MatChipInputEvent): void {
if (!this.matAutocomplete.isOpen) {
let input = event.input;
let value: IReceipt = event.value as IReceipt;
// Add our approver
if (value) {
this.receipt.push(value);
}
// Reset the input value
if (input) {
input.value = '';
}
this.receiptCtrl.setValue(null);
}
}
removeReceipt(receipt: IReceipt): void {
const index = this.receiptList.indexOf(receipt);
if (index >= 0) {
this.receiptList.splice(index, 1);
}
}
private _filterSupplier(value: IReceipt): IReceipt[] {
let filterValue = value;
if (isString(filterValue)) {
let filterValue = (value as any).toLocaleLowerCase('tr');
return this.allReceipts.filter((prm: IReceipt) => {
return (prm.StockShortName as any).toLocaleLowerCase('tr').indexOf(filterValue) >= 0
});
}
else {
return this.allReceipts.filter((prm: IReceipt) => {
return prm.StockIntegrationCode === prm.StockIntegrationCode;
});
}
}
receiptSelected(event: MatAutocompleteSelectedEvent): void {
let sUser: IReceipt = event.option.value;
if (this.receiptList.filter((f: IReceipt) => f.StockIntegrationCode == sUser.StockIntegrationCode).length == 0) {
this.receiptList.push(sUser);
}
this.receiptInput.nativeElement.value = '';
this.receiptCtrl.setValue(null);
}

Angular material-autocomplete for searches with multiple words?

In my Angular project, I'm using material-autocomplete to search through some of my items. So far, it works as expected.
HTML
<form class="example-form">
<mat-form-field class="example-full-width">
<input type="text" placeholder="Pick one" aria-label="Number" matInput [formControl]="myControl" [matAutocomplete]="auto">
<mat-autocomplete autoActiveFirstOption #auto="matAutocomplete">
<mat-option *ngFor="let option of filteredOptions | async" [value]="option">
{{option}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
</form>
TS
myControl = new FormControl();
options: string[] = ['One', 'Two', 'Three'];
filteredOptions: Observable<string[]>;
ngOnInit() {
this.filteredOptions = this.myControl.valueChanges.pipe(
startWith(''),
map(value => this._filter(value))
);
}
private _filter(value: string): string[] {
const filterValue = value.toLowerCase();
if(filterValue == ''){
return []
}
return this.options.filter(option => option.toLowerCase().indexOf(filterValue) === 0);
}
This function works, but only for the first word in the search. For example, if you want to search for "Banana Apple", that will autocomplete if you type "B", but it won't autocomplete if you type "Apple." Is there a way to apply this search so that you can search for either word in a multi-word item?
You should change the last line of your _filter method:
return this.options.filter(option => option.toLowerCase().includes(filterValue));
This allows to you to filter finding in all of the value of your option.

register an angular form in a nested json

I am trying to convert a html form to a nested json.
These are my classes :
export class Config {
id: string;
name: string;
email: string;
lchart:ComponentLinechart;
}
export class ComponentLinechart {
name_linechart : String;
xAxis_linechart : String;
yAxis_linechart : String;
}
The formcomponent.ts:-
export class FormsComponent implements OnInit {
newConfig: Config = new Config();
constructor(private service : MyserviceService, private configservice:ConfigurationService) {
}
email = new FormControl('', [Validators.required, Validators.email]);
xAxisControl = new FormControl('', Validators.required);
yAxisControl = new FormControl('', Validators.required);
name_linechart = new FormControl('', Validators.required);
name = new FormControl('', Validators.required);
getConfig(): void {
this.configservice.getConfig()
.subscribe(data => this.configs = data );
}
createConfiguration(todoForm: NgForm): void {
this.configservice.createConfig(this.newConfig)
.subscribe(createconfig => {
todoForm.reset();
this.newConfig = new Config();
this.configs.unshift(createconfig)
});
}
The formcomponent.html :-
<form #todoForm="ngForm" (ngSubmit)="createConfiguration(todoForm)" novalidate>
<div class="example-container">
<mat-form-field appearance="fill">
<mat-label>Enter your name</mat-label>
<input matInput [(ngModel)]="newConfig.name" name="name" [formControl]="name" required>
</mat-form-field>
<br>
<mat-form-field appearance="fill">
<mat-label>Enter your email</mat-label>
<input matInput placeholder="pat#example.com" [(ngModel)]="newConfig.email" name="email"
[formControl]="email" required>
<mat-error *ngIf="email.invalid">{{getErrorMessage()}}</mat-error>
</mat-form-field>
<br>
<mat-form-field appearance="fill">
<mat-label>Name-linechart</mat-label>
<input matInput [(ngModel)]="newConfig.name_linechart" name="name_linechart"
[formControl]="name_linechart" required>
</mat-form-field>
<br>
<mat-form-field *ngIf = "sales" >
<mat-label>xAxis-linechart</mat-label>
<mat-select [(ngModel)]="newConfig.xAxis-linechart" name="xAxis-linechart"
[formControl]="xAxisControl" required>
<mat-option *ngFor="let field of fields" [value] = "field">
{{field}}
</mat-option>
</mat-select>
<mat-error *ngIf="xAxisControl.hasError('required')">Please choose a field</mat-error>
</mat-form-field>
<br>
<mat-form-field *ngIf = "sales" >
<mat-label>yAxis-linechart</mat-label>
<mat-select [(ngModel)]="newConfig.yAxis-linechart" name="yAxis-linechart"
[formControl]="yAxisControl" required>
<mat-option *ngFor="let field of fields" [value] = "field">
{{field}}
</mat-option>
</mat-select>
<mat-error *ngIf="yAxisControl.hasError('required')">Please choose a field</mat-error>
</mat-form-field>
Expected result :
{
"name": "adam",
"email": "adam#gmail.com",
"lchart": {
"name_linechart": "books",
"xAxis_linechart": "library",
"yAxis_linechart": "percentage"
}
}
But this is what I get :
{
"name": "adam",
"email": "adam#gmail.com",
"lchart": null
}
I tried to write newConfig.lchart.name_linechart in the formcomponent.html but it gives me the error :
TypeError : cannot read property name_linechart of undefined.
Foufa, NEVER, NEVER, NEVER, NEVER use [(ngModel)] and formControlName (or formControl) in the same tag. One is for template Form, another for ReactiveForms, see the docs
Well. You has an object that has properties, one of them is an object, so, you has a FormGroup with somes FormControls and one FormGroup, (again the docs)
myForm=new FormGroup({
name:new FormControl(),
email:new FormControl(),
lchart:new FormGroup({
name_linechart: new FormControl(),
xAxis_linechart: new FormControl(),
yAxis_linechart: new FormControl(),
})
})
And the .html
<form [formGroup]="myForm">
<!--see, under the formGroup, we using formControlName for the formControl-->
<input formControlName="name">
<input formControlName="email">
<!--when we has a FomgGroup, we use formGrpupName in a div-->
<div formGroupName="lchart">
<!--and formControlName under the div -->
<input formControlName="name_linechart">
<input formControlName="xAxis_linechart">
<input formControlName="yAxis_linechart">
</div>
</form>
<!--we can add, only for check-->
<pre>
{{myForm?.value|json}}
</pre>
Update as always, is util use a function that received an object and create the form
getForm(data:any)
{
data=data || { name:null,
email:null,
lchart:{
name_linechart:null,
xAxis_linechart:null,
yAxis_linechart:0
}
}
return new FormGroup({
name:new FormControl(data.name,Validator.required),
email:new FormControl(data.email,[Validator.required,Validators.emailValidator),
lchart:new FormGroup({
name_linechart: new FormControl(data.lchart.name_linechart),
xAxis_linechart: new FormControl(data.lchart.xAxis_linechart),
yAxis_linechart: new FormControl(data.lchart.yAxis_linechart),
})
}
And use as
myForm=this.getForm(data) //<--if we has an object "data"
//or
myForm=this.getForm(null) //if we want a empty form
I guess your [(ngModel)] binding wrong for name_linechart,xAxis_linechart,yAxis_linechart fields.
It should be
[(ngModel)]="newConfig.lchart.name_linechart"
[(ngModel)]="newConfig.lchart.xAxis_linechart"
[(ngModel)]="newConfig.lchart.yAxis_linechart"
change your constructor to-
constructor(private service : MyserviceService, private configservice:ConfigurationService) {
this.newConfig.lchart=new ComponentLinechart(); //add this line
}

How to fix a ExpressionChangedAfterItHasBeenCheckedError

I have an application with a mat-autocomplete element in it with cities as options. The cities come from a database. There is a button to add an input element. This is done by having a div element with a *ngFor property that loops over a property in the component.ts file. When the add-button is clicked an element is added to the property and so an input is added. But when i type in a city name in the first input and then try to add an input element i get the ExpressionChangedAfterItHasBeenCheckedError.
I have found that a lot of people get this error but i've tried all the given solutions but non of them of them seem to work. Like :
startWith(''),
delay(0),
map(value => this._filter(value))
);
ngAfterViewInit(){
this.cd.detectChanges();
}
var request = new CityCreationRequestModel();
request.name = "";
Promise.resolve(null).then(() => this.reportGroupCreationRequest.cityReportGroups.push(request));
and trying with timeOuts.
this is my html file :
<div class="container">
<mat-card>
<mat-card-header>
<mat-card-title>Voeg een reportgroep toe</mat-card-title>
<mat-card-subtitle>Geef emailadressen op die verbonden worden met de rapporten van bepaalde steden</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<form (ngSubmit)="onSubmit()" #addReportGroup="ngForm" *ngIf="!isLoading">
<div *ngFor="let city of reportGroupCreationRequest.cityReportGroups" class="form-row">
<mat-form-field>
<input placeholder="Stad" required [(ngModel)]="city.name" type="text" matInput [formControl]="myControl" [matAutocomplete]="auto" name="cityInput">
<mat-autocomplete autoActiveFirstOption #auto="matAutocomplete">
<mat-option *ngFor="let cityName of filteredOptions | async" [value]="cityName">
{{ cityName }}
</mat-option>
</mat-autocomplete>
</mat-form-field>
</div>
<button mat-raised-button (click)="addCity()">Voeg een stad toe</button>
<div class="form-row">
<button mat-raised-button type="submit" [disabled]="!addReportGroup.valid" class="submitButton">Opslagen</button>
</div>
</form>
<mat-spinner [style.display]="isLoading ? 'block' : 'none'"></mat-spinner>
</mat-card-content>
</mat-card>
and this is my .ts file :
the filter if for the mat-autocomplete element
export class AddReportGroupComponent implements OnInit {
myControl = new FormControl();
filteredOptions: Observable<string[]>;
public reportGroupCreationRequest: ReportGroupCreationRequestModel = new ReportGroupCreationRequestModel();
public cities: Array<CityResponseModel>;
public cityNames: Array<string>;
private isLoading: boolean;
constructor(private reportGroupService: ReportGroupService,
private cd: ChangeDetectorRef) {
}
ngOnInit() {
this.isLoading = true;
this.reportGroupService.getCities().subscribe(result => {
this.cities = result;
this.cities.sort((a,b) => a.name.localeCompare(b.name));
this.cityNames = this.cities.map(c=>c.name);
}).add(()=> {
this.isLoading = false;
this.filteredOptions = this.myControl.valueChanges.pipe(
startWith(''),
delay(0),
map(value => this._filter(value))
);
});
this.reportGroupCreationRequest.cityReportGroups.push(new CityCreationRequestModel());
this.reportGroupCreationRequest.emailReportGroups.push(new EmailCreationRequestModel());
}
//doesn't seem to do anything
ngAfterViewInit(){
this.cd.detectChanges();
}
private _filter(value: string): string[] {
const filterValue = value.toLowerCase();
return this.cityNames.filter(cn => cn.toLowerCase().indexOf(filterValue) === 0);
}
addCity(){
var request = new CityCreationRequestModel();
request.name = "";
Promise.resolve(null).then(() => this.reportGroupCreationRequest.cityReportGroups.push(request));
}
and these are my models that I use :
export class ReportGroupCreationRequestModel {
public cityReportGroups: Array<CityCreationRequestModel> = new Array<CityCreationRequestModel>();
public emailReportGroups: Array<EmailCreationRequestModel> = new Array<EmailCreationRequestModel>();
}
export class CityCreationRequestModel {
public name: string;
}
Thanks in advance
I found a solution for the problem I had :
I used both [(ngModel)] and [FormsControl] and apparantly the [(ngModel)] was part of the problem so I only used [FormsControl] and the error is gone.
addCity(){
this.reportGroupCreationRequest.cityReportGroups[this.reportGroupCreationRequest.cityReportGroups.length - 1].name = this.myControl.value;
Promise.resolve(null).then(() => this.reportGroupCreationRequest.cityReportGroups.push(new CityCreationRequestModel()));
}
<div *ngFor="let city of reportGroupCreationRequest.cityReportGroups" class="form-row">
<mat-form-field>
<input placeholder="Stad" required type="text" matInput [formControl]="myControl" [matAutocomplete]="auto" name="cityInput">
<mat-autocomplete autoActiveFirstOption #auto="matAutocomplete">
<mat-option *ngFor="let cityName of filteredOptions | async" [value]="cityName">
{{ cityName }}
</mat-option>
</mat-autocomplete>
</mat-form-field>
</div>