So, I have a component only for the autocomplete by angular material and a parent component which has a form and uses this autocomplete once.
When I click on a value to choose from the autocomplete, it doesn't get saved anywhere and is empty. Also, I would expect the onSelection event to fire, but it doesnt. I tried several ways to get the current value from the form field, but whatever way I try, its always empty.
Here is the ts file for the autocomplete:
interface GroupOption {
group: string;
items: OptGroupItem[];
}
#Component({
selector: 'sh-ui-forms-mat-autocomplete-optgroup-control;',
templateUrl: './mat-autocomplete-optgroup-control.component.html',
styleUrls: ['./mat-autocomplete-optgroup-control.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MatAutocompleteOptgroupControlComponent),
multi: true,
},
],
})
export class MatAutocompleteOptgroupControlComponent
implements OnInit, OnChanges, ControlValueAccessor
{
#Input()
label = '';
/** Liste der Roh-Optionen */
#Input()
groupOptions: OptGroupItem[] = [];
/** Observable für die GUI */
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))
);
}
ngOnChanges(c: SimpleChanges): void {
this.options = this.groupByGroup(this.groupOptions);
}
/** Hilfsfunktion zum Umwandeln der Rohoptionen */
private groupByGroup(options: OptGroupItem[]): GroupOption[] {
const groups = groupBy(options, 'group');
return Object.entries(groups).map(([group, items]) => ({
group,
items,
}));
}
/** Filtern und erstellen der Optionen für die GUI */
private createGroupOptions(filter: any): GroupOption[] {
let options = this.groupOptions;
if (filter != null) {
const searchValue = (filter.name || filter).toString().toLowerCase();
options = this.groupOptions.filter(option =>
option.name.toString().toLowerCase().includes(searchValue)
);
}
const groups = this.groupByGroup(options);
return groups;
}
/** Liefert ein Roh-Item aus der Liste der Optionen. */
private getOptionFromId(options: GroupOption[], id: string): OptGroupItem {
let retVal = this.emptyOption;
options.forEach(o => {
const item = o.items.find(i => i.id === id);
if (item != null) {
retVal = item;
}
});
return retVal;
}
onChange = (_: string) => {};
onTouched = () => {};
registerOnChange(fn: (_: string) => void): void {
this.onChange = fn;
}
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
// Behandelt eine Control-Wertänderung von "außen".
writeValue(value: string) {
let currentValue = this.getOptionFromId(this.options, value);
if (currentValue === null) {
currentValue = this.emptyOption;
}
this.control.setValue(currentValue);
}
//Auswahl im autocomplete
onDropdownChange(e: MatAutocompleteSelectedEvent) {
const selectedValue = e.option.value.id;
console.log('onDropdownchange! => selectedValue:');
console.log(selectedValue);
this.onChange(selectedValue);
}
displayName(option: any): string {
return option ? option.name : '';
}
/** Auswahl im Input-Feld */
onValueChange(event: any): void {
event?.preventDefault();
const value = this.control.value;
if (!value) {
this.control.setValue(this.emptyOption);
this.onChange(this.emptyOption.id);
}
}
}
Thats the html file for the autocomplete:
<mat-form-field appearance="outline">
<mat-label>{{ label }}</mat-label>
<ng-container>
<input
type="text"
matInput
[formControl]="control"
[matAutocomplete]="auto"
(blur)="onValueChange($event)"
(keyup.enter)="onValueChange($event)"
/>
<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>
And thats how I call the component from the parent form component:
<sh-ui-forms-mat-autocomplete-optgroup-control
[label]="'Einsatz'"
[groupOptions]="openEinsatzListe$ | async"
formControlName="einsatzId"
></sh-ui-forms-mat-autocomplete-optgroup-control>
openEinsatzListe$ is an observable.
In the picture you can see how the data from the observable/the optionGroups/options for the autocomplete looks like
Question: Why is no value saved when I select a value?
(Btw, there is another bug if you are interested. If you click in the form field, no values are shown. You have to type a space and remove it again to see the values. Maybe you know the solution for that aswell?)
Your form control implementation is wrong, Please check this manual: https://blog.angular-university.io/angular-custom-form-controls/
Basic implementation check here: https://stackblitz.com/edit/angular-ivy-cdvhyz?file=src/app/app.component.ts
Related
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();
}),
);
}
I am trying to make an Update method and everything works fine except for a value that is selected from a drop-down-list. When the PUT method is called, the API always receives the old value instead of the newly selected one.
This is the list I am talking about:
edituser.component.html
<mat-form-field>
<mat-label>Choose a role </mat-label>
<mat-select *ngIf="roles" [(ngModel)]="userRoleID" name="role">
<mat-option *ngFor="let role of roles" [value]="role.id" [(ngModel)]="el.userRoleID">{{role.role}}</mat-option>
</mat-select>
</mat-form-field>
edituser.component.ts
export class EdituserComponent implements OnInit {
id: string;
sub: any;
user: User[];
roles: Role[];
userRoleID: string;
constructor(private route: ActivatedRoute, private userService: UserService, private router:Router, private roleService:RoleService) {
this.roleService.getRoles().subscribe((result) => {
this.roles = result
this.useRoles(this.roles);
});
}
ngOnInit(): void {
this.sub = this.route.params.subscribe(params => {
this.id = params['id'];
console.log(this.id);
this.userService.getUserById(this.id).subscribe((result) => this.user = result);
});
}
updateUser(user: User){
this.userService.updateUser(user).subscribe(
(result: User) => {
this.userService.getUsers();
});
this.router.navigateByUrl("");
}
useRoles(roles:any)
{
console.log(roles);
}
}
Why does it keep passing the wrong, outdated value to the API when trying to update?
I am trying to filter my datatable by clicking a checkbox. Checking one checkbox filters it properly, but checking multiple is the problem.
The Angular Material documentation is very vague regarding proper filtering where a lot of elements are involved. It has something to do with filterPredicate, but there is almost no (or just awfully vague) online documentation.
<mat-form-field appearance="legacy">
<mat-label>Select Province(s)</mat-label>
<mat-select placeholder='Provinces' formControlName="provinceSelector" multiple>
<mat-option *ngFor="let p of provinces" [value]='p.provinceName' (click)='addfilter()'>
{{p.provinceName}} ({{p.adsInProvince}})
</mat-option>
</mat-select>
</mat-form-field>
this.registeredUserService.GetAllAdverts().subscribe(val => {
this.dataSource = new MatTableDataSource<Card>(val);
this.dataSource.paginator = this.paginator;
const myPredicate = (myObject:IProvince,filterString:any)=>
{
let filterObj:IProvince = JSON.parse(filterString);
if(!filterObj.provinceName.includes((obj)=>obj=myObject.provinceName))
{
return false;
}
else
{return true;}
}
this.dataSource.filterPredicate=myPredicate;
myFilter:IProvince={
provinceName:[]
}
addfilter() {
this.myFilter.provinceName=this.search.value;
this.dataSource.filter = JSON.stringify(this.myFilter);
}
export interface Card {
advertDate: any;
advertDescription: any;
advertID: any;
cityName: any;
provinceName: any;
sellerID: any;
sellingPrice: any;
vehicleColor: any;
vehicleMake: any;
vehicleMileage: any;
vehicleModel: any;
vehicleYear: any;
}
export interface IProvince{
provinceName:any[];
}
it should just filter through the selected values...
it does not do it properly.
You are right with the filter predicate. You can define how to filter you dataSource. It is a function which returns true(if filter matches) or false.
const myPredicate = (myObject, filterString) => {
let filterObj: MyFilterObj = JSON.parse(filterString);
if(!filterObj.attributeA.find((obj) => obj == myObject.name) ||myObject.attributeB != filterObject.attributeB) { // <-- edit includes to find
return false;
} else {
return true;
}
}
Put the following code after generating your MatTableDataSource:
this.dataSource.filterPredicate = this.myPredicate.
Use the following method for setting your filter. I always give a type string for determining which filter I want to set.
myFilter: MyFilterObject = { // <-- define this variable in your ngOnInit method
attributeA: [], // <-- this is the place where you can put your selected options
attributeB: null
}
applyFilter(value: any, type: string) {
switch(type) {
case "attributeA":
this.myFilter.attributeA = value;
break;
case "attributeB":
this.myFilter.attributeB = value;
break;
default:
break;
}
this.dataSource.filter = JSON.stringify(this.myFilter);
}
I am trying to get a value from nested array from a Form Values
[['A'], ['B']]-- from this array
['A','B'] --trying to get value like this
stackblitz example
https://stackblitz.com/edit/angular-4d5vfj-p5adyk?file=main.ts
<button (click)="addNewChipList()">Add new Chip</button><br><br>
<form [formGroup]="myForm">
<ng-container formArrayName="names"
*ngFor="let item of myForm.get('names').controls; let i = index;">
<mat-form-field class="example-chip-list" [formGroupName]="i">
<mat-chip-list #chipList >
<mat-chip *ngFor="let val of item.value.val"
[selectable]="selectable"
[removable]="removable"
(removed)="removeChip(item, val)">
{{val}}
<mat-icon matChipRemove *ngIf="removable">cancel</mat-icon>
</mat-chip>
<input [placeholder]="item.value.name"
[matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="addOnBlur"
(matChipInputTokenEnd)="addChip($event, item)">
</mat-chip-list>
<mat-error>Atleast 1 name need to be added</mat-error>
</mat-form-field>
</ng-container>
<button (click)="save()">save</button><br><br>
</form>
component.ts
The componet file where I am trying to get form value
export class ChipListValidationExample implements OnInit {
#ViewChild('chipList') chipList: MatChipList;
public myForm: FormGroup;
// name chips
visible = true;
selectable = true;
removable = true;
addOnBlur = true;
readonly separatorKeysCodes: number[] = [ENTER, COMMA];
// data
data = {
names: [this.initName('name1'), this.initName('name2', [['A'],
['B']])]
}
constructor(private fb: FormBuilder) {
this.myForm = this.fb.group({
names: this.fb.array(this.data.names, this.validateArrayNotEmpty)
});
}
ngOnInit() {
this.myForm.get('names').statusChanges.subscribe(
status => this.chipList.errorState = status === 'INVALID'
);
}
initName(name: string, val: string[] = []): FormControl {
return this.fb.control({ name, val});
}
validateArrayNotEmpty(c: FormControl) {
if (c.value && c.value.length === 0) {
return {
validateArrayNotEmpty: { valid: false }
};
}
return null;
}
addChip(event: MatChipInputEvent, ctrl: FormControl): void {
const input = event.input;
const value = event.value;
// Add name
if ((value || '').trim()) {
const control = ctrl;
control.value.val.push(value.trim());
console.log(control.value);
}
// Reset the input value
if (input) {
input.value = '';
}
}
removeChip(ctrl, val) {
const idx = ctrl.value.val.findIndex(item => item === val);
ctrl.value.val.splice(idx, 1);
}
addNewChipList() {
const items = this.myForm.get('names') as FormArray;
items.push(this.initName(`name${items.length + 1}`));
}
save(){
console.log("FormValues",this.myForm.value)
}
}
I am trying to get the form value as ['A','B']
Flattening some arrays is a trivial task with plain JS. Using Array.flat, you can do this in one line.
The flat() method creates a new array with all sub-array elements concatenated into it recursively up to the specified depth.
Syntax
var newArray = arr.flat([depth]);
Parameters
depth Optional
The depth level specifying how deep a nested array structure should be flattened. Defaults to 1.
Return value
A new array with the sub-array elements concatenated into it.
//one level deep
var x = [['A'], ['B']];
console.log(x.flat());
// => ['A', 'B']
//2 levels deep
var y = [[['A', 'B'], ['C']], ['D']];
console.log(y.flat(2));
// => ['A', 'B', 'C', 'D']
I have an ag-grid which is rendering table from a .json file and an external Quick filter that is searching through ag-grid on key input on the filter. After someone searches the search term is displayed in the form of angular material chip with a "X" sign to close the chip with remove function. I want to reload the ag-grid to its default state once someone cancel/close the chip and also to include multiple filters in it using the chip. Here is my sample code, but I'm struggling with setting it up.
Html-
<div class="container">
<mat-form-field class="demo-chip-list" *ngIf="gridApi">
<mat-chip-list #chipList>
<div style="width:100%; margin-left:10%;"><label><span class="search-button">Search Funds</span></label>.
<input class="search-input"
[ngModel]="filterText"
(ngModelChange)=
"gridApi.setQuickFilter
($event)"
[matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="addOnBlur"
(matChipInputTokenEnd)="add($event)" />.
</div><br/><div style="width:100%; margin-left:10%;"><mat-chip *ngFor="let fruit of fruits"
[selectable]="selectable"
[removable]="removable"
(click)="onGridReady(params)"
(remove)="remove(fruit)">
{{fruit.name}}
<mat-icon matChipRemove *ngIf="removable" ><sup>x</sup></mat-icon></mat-chip></div></mat-chip-list>.
</mat-form-field>
<div class="header" style="display:inline"></div><div> <ag-grid-angular
style="position:absolute;padding-left:5%; bottom:0px;width: 90%; height: 350px;" #agGrid id="myGrid" class="ag-fresh" [columnDefs]="columnDefs"
[animateRows]="true"
[enableRangeSelection]="true"
[enableSorting]="true"
[enableFilter]="true"
[pagination]="true"
(gridReady)="onGridReady($event)">
</ag-grid-angular></div></div>
Component-
#Component({
selector:
'app-funds-table',
templateUrl:
'./funds-table.component.html',
styleUrls:
['./funds-table.component.css']
})
export class
FundsTableComponent
implements OnInit {
visible: boolean = true;
selectable: boolean = true;
removable: boolean = true;
addOnBlur: boolean = true;
// Enter, comma
separatorKeysCodes = [ENTER, COMMA];
fruits = [
{ name: 'ABC' }
];
add(event: MatChipInputEvent): void
{
let input = event.input;
let value = event.value;
// Add our fruit
if ((value || '').trim()) {
this.fruits.push({ name:
value.trim() });
}
// Reset the input value
if (input) {
input.value = '';
}
}
remove(fruit: any): void {
let index =
this.fruits.indexOf(fruit);
if (index >= 0) {
this.fruits.splice(index, 1);
}
}
private gridApi;
private gridColumnApi;
private columnDefs;
private filterText = "";
ngOnInit() {}
constructor(private http:
HttpClient ){
this.columnDefs = [{headerName:
"Ticker", field: "Ticker"},
{headerName: "Id", field: "Id"},
{headerName: "Utilities", field:
"Utilities"}
];
}
onGridReady(params) {
this.gridApi = params.api;
this.gridColumnApi =
params.columnApi;
this.http.get
("/fundsData/fund_info.json". )
.subscribe
(data =>
{this.gridApi.setRowData(data);
});
}
}
According doc:
You can reset filter via direct api call
api.setQuickFilter(''); - empty for reset filter