I am building an angular app. In the template I have button, on clicking this it adds new autocomplete element. But problem arises when I select an option, I see other auto complete shows the previously filtered value. I filter method and form control is common for all the autocomplete elements. I am trying to figure out, how to make the formelement and filter method unique for each auto complete element.
Any help or suggestion will be really appreciated, thanks.
Here is the code snippet,
template.html
<button (click)="addelement()"> Add New Elemnt </button>
<ng-container *ngFor="let name of names; let j = index">
<form class="example-form">
<mat-form-field class="example-full-width">
<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>
</form>
</ng-container>
template.ts
import {Component, OnInit} from '#angular/core';
import {FormControl} from '#angular/forms';
import {Observable} from 'rxjs';
import {map, startWith} from 'rxjs/operators';
export interface User {
name: string;
}
/**
* #title Display value autocomplete
*/
#Component({
selector: 'autocomplete-display-example',
templateUrl: 'autocomplete-display-example.html',
styleUrls: ['autocomplete-display-example.css'],
})
export class AutocompleteDisplayExample implements OnInit {
myControl = new FormControl();
options: User[] = [
{name: 'Mary'},
{name: 'Shelley'},
{name: 'Igor'}
];
names=[{}]
filteredOptions: Observable<User[]>;
ngOnInit() {
this.filteredOptions = this.myControl.valueChanges
.pipe(
startWith(''),
map(value => typeof value === 'string' ? value : value.name),
map(name => name ? this._filter(name) : 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().indexOf(filterValue) === 0);
}
addelement() {
this.names.push({});
}
}
In the code snippet above you will 'names' array, based on the object pushed to this array, autocomplete will appear in the template. If you need more info please let me know.
Related
I have an Input with MatChip connected thru my Firestore, I've been following these documentations MatChip Documentation, StackBlitz Sample 1, StackOverflow Issue 1.
StackOverflow Issue 2 also credits to #Chris Hamilton for the initial guides.
Everything is running okay besides the removal of a specific Chip when added and the method [removable]="true" is also there.
AutoComplete:
Array Storing and interpolation:
Firestore DB:
However, when I click the close icon it doesn't remove the chip. No errors are being shown as well. I also want to know how to two way bind it using NgModel that when I reload the page / component the add value is also there as chips.
Here's my
HTML Code:
<mat-form-field class="form-date" appearance="standard">
<mat-label>Type Plumber Name Here</mat-label>
<mat-chip-list #chipList>
<mat-chip
*ngFor="let plumber of plumbers"
[selectable]="selectable"
[removable]="removable"
(removed)="remove(plumber)">
{{plumber}}
<mat-icon matChipRemove *ngIf="removable">cancel</mat-icon>
</mat-chip>
<input
placeholder="Add Plumber"
#plumberInput
[formControl]="plumberCtrl"
[matAutocomplete]="auto"
[matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="addOnBlur"
(matChipInputTokenEnd)="add($event)">
</mat-chip-list>
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)">
<mat-option *ngFor="let plumber of filteredPlumbers| async" [value]="plumber">
{{plumber.plumberName}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
TS Component Code:
import { Component, ElementRef, OnInit, ViewChild } from '#angular/core';
import { CustomerService } from 'src/app/services/customer.service';
import { Customer } from 'src/app/models/customer.model';
import { Observable, of} from 'rxjs';
import {COMMA, ENTER} from '#angular/cdk/keycodes';
import {FormControl} from '#angular/forms';
import {MatAutocompleteSelectedEvent} from '#angular/material/autocomplete';
import {MatChipInputEvent} from '#angular/material/chips';
import {map, startWith} from 'rxjs/operators';
#Component({
selector: 'app-update-jo',
templateUrl: './update-jo.component.html',
styleUrls: ['./update-jo.component.scss']
})
export class UpdateJOComponent implements OnInit {
visible = true;
selectable = true;
removable = true;
addOnBlur = false;
separatorKeysCodes: number[] = [ENTER, COMMA];
plumberCtrl = new FormControl();
filteredPlumbers: Observable<any[]>;
plumbers: any[] = [];
allPlumber: plumberModel[] =[]
plumberList: plumberModel[];
#ViewChild('plumberInput') plumberInput: ElementRef;
constructor(
public afs: AngularFirestore,
public dialogRef: MatDialogRef<UpdateJOComponent>,
private customerService: CustomerService)
{
this.plumberCtrl.valueChanges.subscribe(search => {
this.filteredPlumbers = of(this.allPlumber.filter(item =>
item.plumberName.toLowerCase().includes(search)))})
}
ngOnInit(): void {
this.customerService.getPlumbers().subscribe(plubObs => {
this.plumberList = plubObs;
this.allPlumber = plubObs;
})
}
add(event: MatChipInputEvent): void {
const value = (event.value || '').trim();
if(value) {
this.plumbers.push(value.trim());
}
event.chipInput!.clear();
this.plumberCtrl.setValue(null)
}
remove(plumber: string) {
console.log(plumber)
const index = this.plumbers.indexOf(plumber);
console.log(this.plumbers)
console.log(index)
if(index >= 0) {
this.plumbers.slice(index,1);
}
}
selected(event: MatAutocompleteSelectedEvent): void {
this.plumbers.push(event.option.viewValue);
this.plumberInput.nativeElement.value = '';
this.plumberCtrl.setValue(null);
}
_filter(value: any) : plumberModel[] {
return this.allPlumber.filter(plumber => plumber.plumberName.toLowerCase().includes(value))
}
}
Thank you for those who could help.
I am working on autocomplete, it is not filtering options. Problem is in _filter(). Everything works when _filter() is modified to
return this.names.filter(option => option.toLowerCase().includes(filterValue));
But I wonder why it is not working with the below code snippet.
Here is what I tried
template.html
<label>Search names</label>
<input type="text"
placeholder="search name"
aria-label="Number"
matInput
[formControl]="myControl"
[matAutocomplete]="auto">
<mat-autocomplete #auto="matAutocomplete">
<mat-option *ngFor="let option of filteredOptions | async" [value]="option">
{{option}}
</mat-option>
</mat-autocomplete>
template.ts
import { Component, OnInit } from '#angular/core';
import { FormControl } from '#angular/forms';
import { Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators';
#Component({
selector: 'app-matautocom',
templateUrl: './matautocom.component.html',
styleUrls: ['./matautocom.component.css']
})
export class MatautocomComponent implements OnInit {
names: string[] = ['ghui', 'ustu', 'caty','momo', 'rekh', 'john', 'kemp'];
myControl: FormControl = new FormControl();
filteredOptions: Observable<string[]> | undefined;
constructor() { }
ngOnInit(): void {
this.filteredOptions = this.myControl.valueChanges.pipe(
startWith(''),
map(value => this._filter(value))
);
}
private _filter(value: string): string[] {
const filterValue = value.toLowerCase();
return this.names.filter((option) => {
console.log(filterValue)
option.toLowerCase().includes(filterValue);
return option;
})
}
}
The problem is the value you are returning from the function provided to filter
option.toLowerCase().includes(filterValue) // returns a boolean that works with filter
// thats why this works
// return this.names.filter(option => option.toLowerCase().includes(filterValue));
In your other example you are returning the option variable that is not a boolean value:
return this.names.filter((option) => {
console.log(filterValue)
// the result of includes is lost
option.toLowerCase().includes(filterValue);
// this is actually returning the option, not a boolean expected by filter
return option;
// To fix this, just return the result of includes
// return option.toLowerCase().includes(filterValue);
})
Updated in reference to German's answer
I am trying to pass the instrumentName field from the instrument value selected by the user to the function in the submit button. The instrumentName value shows up properly, but it never seems to be saved upon submitting. All of the other input values save fine. How can I fix this?
HTML:
<form [formGroup]="createForm" class="create-form">
</mat-form-field -->
<mat-form-field class="field-full-width">
<mat-label>Instrument</mat-label>
<mat-select matInput formControlName="createForm.get('Machine_ID')" #Machine_ID>
<mat-option *ngFor="let instrument of instruments" [value]="instrument.instrumentName">
{{instrument.instrumentName}}
</mat-option>
</mat-select>
</mat-form-field>
<button mat-raised-button color="accent" routerLink="/list">Back</button>
<button (click)="addSample()"
[disabled]="createForm.invalid" color="primary">Save</button>
</form>
TS code:
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormBuilder, Validators } from '#angular/forms';
import { Router } from '#angular/router';
import { FileService } from '../../file.service';
import { Instrument } from '../../instrument.model';
#Component({
selector: 'app-add-new-sample',
templateUrl: './add-new-sample.component.html',
styleUrls: ['./add-new-sample.component.css']
})
export class AddNewSampleComponent implements OnInit {
createForm: FormGroup;
instruments: Instrument[];
constructor(private fileService: FileService, private fb: FormBuilder, private router: Router) {
this.createForm = this.fb.group({
Machine_ID: '',
});
}
addSample() {
const machineIdValue = this.createForm.get('Machine_ID').value;
this.fileService.addSample(machineIdValue).subscribe(() => {
this.router.navigate(['/instruments']);
});
}
fetchInstruments(){
this.fileService.getInstruments()
.subscribe((data: Instrument[])=>
{
this.instruments = data;
})
}
ngOnInit() {
this.fetchInstruments();
}
}
You don't need to pass the Machine_ID reference through the template.
You can call the formGroup and get the Machine_ID formControl value like this:
this.createForm.get('Machine_ID').value
So in addSample you can do:
addSample(/*other inputs*/) {
const machineIdValue = this.createForm.get('Machine_ID').value;
this.fileService.addSample(/*other inputs*/, machineIdValue).subscribe(() => {
this.router.navigate(['/instruments']);
});
}
Then you should update your submit button:
<form [formGroup]="createForm" class="create-form">
<mat-form-field class="field-full-width">
<mat-label>Instrument</mat-label>
<mat-select matInput formControlName="createForm.get('Machine_ID')" #Machine_ID>
<mat-option *ngFor="let instrument of instruments" [value]="instrument.instrumentName">
{{instrument.instrumentName}}
</mat-option>
</mat-select>
</mat-form-field>
</form>
<button (click)="addSample()"
[disabled]="createForm.invalid" mat-raised-button color="primary">Save</button>
I'm trying to populate an array with all selected values from a mat-select dropdown. The goal is to create an input field for every selected value like this:
Image
I thought about calling a method every time I select a value, but I'm not really sure on what to do next...
This is what I have:
MyComponent.component.html
<mat-form-field>
<mat-select placeholder="Products" [formControl]="products" multiple>
<mat-option *ngFor="let product of productsList">{{ product }}</mat-option>
</mat-select>
</mat-form-field>
MyComponent.component.ts
import { Component, OnInit } from '#angular/core';
import { FormControl } from '#angular/forms';
#Component({
selector: 'm-mycomponent',
templateUrl: './mycomponent.component.html',
styleUrls: ['./mycomponent.component.scss']
})
export class MyComponent implements OnInit {
products = new FormControl();
productsList = ['Prod1', 'Prod2', 'Prod3', 'Prod4', 'Prod5', 'Prod6'];
productsToReturn = [];
constructor() { }
ngOnInit() {
}
fillProductsToReturn(product){
if(!this.productsToReturn.includes(product)){
this.productsToReturn.push(product);
}
}
}
How can I call a method in the html file and populate the productsToReturn array?
Thanks!
You didn't specify when you want to access the selected objects.
Its simple if you want to access it when the form is submitted, like on a click of a button. Then you can use this.products.value to get the selected options.
If you want it at the time of selection, then you can bind to selectionChange() event
<mat-select placeholder="Products" [formControl]="products" multiple (selectionChange)="onSelectionChange($event) >
then in ts file you can get the selected options
onSelectionChange(e){
console.log(e.value); //or
console.log(this.toppings.value);
}
You don't need to do this manually. Just bind the value property of the mat-option to the current product string and your FormControl will contain the array of selected options.
Template
<mat-form-field>
<mat-select placeholder="Products" [formControl]="products" multiple>
<mat-option *ngFor="let product of productsList" [value]="product">{{ product }}</mat-option>
</mat-select>
</mat-form-field>
And in your component you can access the selected options via this.products.value.
Here
is a minimal stackblitz with a working solution. The selected values
are directly displayed next to the mat-select.
I solved my problem...
I made the following changes based on j4rey's answer
MyComponent.component.html
<mat-form-field>
<mat-select placeholder="Products" [formControl]="products" multiple>
<mat-option *ngFor="let product of productsList" [value]="product" (onSelectionChange)="onSelectionChange(product)">{{ product }}</mat-option>
</mat-select>
</mat-form-field>
<div class="row" *ngFor="let product of productsToReturn">
<div class="col-4">
<mat-form-field>
<input matInput disabled="disabled" [value]="product">
</mat-form-field>
</div>
<div class="col-2">
<mat-form-field class="prod-qty">
<input matInput step="1" value="1" min="1" type="number">
</mat-form-field>
</div>
</div>
MyComponent.component.ts
import { Component, OnInit } from '#angular/core';
import { FormControl } from '#angular/forms';
#Component({
selector: 'm-mycomponent',
templateUrl: './mycomponent.component.html',
styleUrls: ['./mycomponent.component.scss']
})
export class MyComponent implements OnInit {
products = new FormControl();
productsList = ['Prod1', 'Prod2', 'Prod3', 'Prod4', 'Prod5', 'Prod6'];
productsToReturn = [];
constructor() { }
ngOnInit() { }
onSelectionChange(product: string) {
if (!this.productsToReturn.includes(product)) {
this.productsToReturn.push(product);
} else {
let index = this.productsToReturn.indexOf(product);
this.productsToReturn.splice(index, 1);
}
}
}
In my angular project have angular material and use mat-select. Mat-select is the first element for my form in my case set auto focus while page was loaded successfully but I wasn't able to set auto focus on mat-select. Anyone can help me to find the way to set auto focus in mat-select.
#ViewChild("name") nameField: ElementRef;
ngOninit() {
this.nameField.nativeElement.focus();
}
html
<div>
<mat-select [(ngModel)]="nameField" #name>
<mat-option *ngFor="let option of options2" [value]="option.id">
{{ option.name }}
</mat-option>
</mat-select>
</div>
HTML :
<mat-select #someRef >
<mat-option *ngFor="let item of items;" [value]="item">
{{item.name}}
</mat-option>
</mat-select>
.ts :
make sure you import MatSelect
import { MatSelect } from '#angular/material';
#ViewChild('someRef') someRef: MatSelect;
ngOnInit() {
if(this.someRef) this.someRef.focus();
}
Hope this helps.
If I understand it correctly, you want to focus select element on load. If this is the case, your code is perfectly fine, you just need to move focus logic in to another life cycle event which is
ngAfterViewInit
HTML:
<mat-select #fff>
<mat-option *ngFor="let food of foods" [value]="food.value">
{{food.viewValue}}
</mat-option>
</mat-select>
TS:
export class SelectOverviewExample implements AfterViewInit{
foods: Food[] = [
{value: 'steak-0', viewValue: 'Steak'},
{value: 'pizza-1', viewValue: 'Pizza'},
{value: 'tacos-2', viewValue: 'Tacos'}
];
#ViewChild("fff", {static: false}) nameField: ElementRef;
ngAfterViewInit() {
this.nameField.focused = true;
}
}
Find working demo here. You can see select is highlighted. comment code inside ngAfterViewInit() and see this difference.
As this is the First hit that shows up on Google I'll provide what I found:
Note that I did this specifically for a mat-select as there is no real inner html element that the reference could be attached to.
What I found works is getting a reference to the element through view-child and then calling
reference._elementRef.nativeElement.focus();
Hope this helps at least someone :)
We can use default angular attribute for autofocus
<mat-form-field>
<mat-select formControlName="xyz" cdkFocusInitial>
<mat-option value="abc">Abc</mat-option>
</mat-select>
</mat-form-field>
Try using MatSelect on viewChild to access focused attribute, then onInit set it to true.
<mat-form-field>
<mat-select #mySelect [(ngModel)]="nameField">
<mat-option *ngFor="let option of options2" [value]="option.id">{{ option.name }}
</mat-option>
</mat-select>
</mat-form-field>
and ts file import import { MatSelect } from '#angular/material';
import { MatSelect } from '#angular/material';
export class SelectExample implements OnInit {
#ViewChild(MatSelect) mySelect: MatSelect;
ngOnInit() {
this.mySelect.focused = true;
}
}
You can call the focus on OnInit
ts:
options2 = ['A', 'B'];
#ViewChild('name')
nameField: MdSelect;
ngOnInit() {
setTimeout(() => {
this.nameField.open();
}, 0);
}
html:
<div>
<md-select [(ngModel)]="nameField" #name>
<md-option *ngFor="let option of options2" [value]="option.id">{{ option }}</md-option>
</md-select>
EDIT: Sorry, I think you can not get the nativeElement from mat-select and md-select. You need to get the object and call open().
Workning project here in stackblitz
First, let’s create the Directive
auto-focus.directive.ts
import { AfterContentInit, Directive, ElementRef, Input } from '#angular/core';
#Directive({
selector: '[autoFocus]' }) export class AutofocusDirective implements AfterContentInit {
public constructor(private el: ElementRef) {
}
public ngAfterContentInit() {
setTimeout(() => {
this.el.nativeElement.focus();
}, 500);
}
}
Next we need to tell our AppModule that this new directive exists and to declare it for availability by updating our app.module.ts :
#NgModule({
declarations: [
AutoFocusDirective
]
})
Now you can use it in a component template:
app.component.html
<div> Autofocus? <input appAutoFocus> </div>
You can adapt this example to your own project. Clicking on the button becomes focus.
focusing on form elements the Angular way
show more