I am trying to create a select dropdown where a user selects multiple days of the week and it filters the table to show only the rows that are selected for those days of the week. The filter is called Delivery Days. The data in the table row shows as 'Mo' for Monday, 'Tu' for Tuesday, etc. because that is how it will show in the database.
The delivery days will only be displayed for the expanded/children rows not the main row. So when filtering by delivery days it will only filter for the expanded rows because those are the rows that will show delivery days data.
This is the code that I added for the Delivery Days filter in HTML and TypeScript.
I commented out the html for the select for days of the week because I'm getting a
Template parse errors: Unexpected character "EOF"'.
I'm not sure why I'm getting this error.
I updated the dependencies from Angular 8 to 20 and it is taking a long time to compile.
HTML
<mat-select [formControl]="days" multiple>
<mat-select-trigger>
{{days.value?.[0] || ''}}
@if ((days.value?.length || 0) > 1) {
<span class="example-additional-selection">
(+{{(days.value?.length || 0) - 1}} {{days.value?.length === 2 ? 'other' : 'others'}})
</span>
}
</mat-select-trigger>
@for (topping of daysList; track day) {
<mat-option [value]="day">{{day}}</mat-option>
}
</mat-select>
TypeScript
days = new FormControl('');
dayList: string[] = [
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday',
'Sunday',
];
I'm also unable to see the Delivery Days drop down to click off of the options to go back to the dropdown after making selections.
Update the mat-tigger should be like
<mat-select-trigger>
{{days.value && days.value.length?days.value[0].text:''}}
...
</mat-select-trigger>
Yet corrected in the answer
If we want to see the days selected, we can use a new variable
daysSelected:string="all";
//and is days.valuesChange
this.sub=this.days.valueChanges.subscribe((res:any[])=>{
this.dataSource.filter = res.map((x:any)=>x.value).join(',');
//add this line
this.daysSelected = res.map((x:any)=>x.text).join(',')|| "all";
})
To filter a dataSource we need take account two properties: filter
(that is a string) and filterPredicate
that is a function with two arguments, the object of the row, and a string, and return true or false.
If we not define the "filterPredicate" the function return true if any property of the object contains the string, but we can define e.g.
filter(data:T,filter:string)
{
return data.symbol==filter; //only if the "symbol" contains the filter
}
Remember that the function filter only execute if filter has value, so we needn't check in the function "filter"
But the first is use a more confortable dayList. If you define
dayList: any[] = [
{value:'Mo',text:'Monday'},
{value:'Tu',text:'Tuesday'},
{value:'We',text:'Wednesday'},
{value:'Th',text:'Thursday'},
{value:'Fr',text:'Friday'},
{value:'Sa',text:'Saturday'},
{value:'Su',text:'Sunday'}
];
Your mat-select like, see that the "value" of day is an array of object
<mat-form-field>
<mat-label>Delivery Day</mat-label>
<mat-select [formControl]="days" multiple>
<mat-select-trigger>
<!--see you show days.value[0].text-->
{{days.value && days.value.length?days.value[0].text:''}}
<span
*ngIf="((days.value?.length || 0) > 1)"
class="example-additional-selection"
>
(+{{(days.value?.length || 0) - 1}} {{days.value?.length === 2 ? 'other'
: 'others'}})
</span>
</mat-select-trigger>
<!--see that the value is the whole "day"-->
<!--e.g. days.valus will be the array [{value:'Mo',text:'Monday'},
{value:'Sa',text:'Saturday']
if you select Monday and Saturday-->
<mat-option *ngFor="let day of dayList" [value]="day"
>{{day.text}}</mat-option
>
</mat-select>
</mat-form-field>
in a subscription to day.valueChange you give value to this.dataSource.filter
(don't forget unsubscribe)
See that dataSource.filter is a String.
sub:Subscription;
ngAfterViewInit() {
this.dataSource.sort = this.sort;
this.sub=this.days.valueChanges.subscribe((res:any[])=>{
this.dataSource.filter = res.map((x:any)=>x.value).join(',');
})
}
//to unsubscribe
ngOnDestroy()
{
this.sub && this.sub.unsubscribe()
}
Well the function filter
filter(data:any,filter:string)
{
//data will be, e.g. {position: 10,Name: 'Neon',...DeliveryDay: ['Su', 'Mo']}
//filter wil be, e.g. Su,Tu
const days=filter.split(',') //e.g. days=['Su','Tu']
for (let i=0;i<days.length;i++)
{
if (data.DeliveryDay.includes(days[i])
return true;
}
return false;
}
Well, really we can make more "compact" the function filter
filter(data:any,filter:string)
{
const days=filter.split(',') //e.g. days=['Su','Tu']
let result=false;
days.forEach(x=>result=result||data.DeliveryDay.includes(x))
return result;
}
or even more compact using reduce
filter(data:any,filter:string)
{
const days=filter.split(',') //e.g. days=['Su','Tu']
return days.reduce((a:boolean,b:string)=>a||data.DeliveryDay.includes(b),false)
}
The last piece of the jigsaw is indicate that our dataSource use this filter function
ngAfterViewInit() {
this.dataSource.sort = this.sort;
this.dataSource.filterPredicate=this.filter; //<--this line
this.days.valueChanges.subscribe((res:any[])=>{
this.dataSource.filter = res.map((x:any)=>x.value).join(',');
})
}