angulardesign-patternsangular-material

How to use an Angular Material Date Picker with a partially applied filter function?


I've been trying to create an datepicker/input using Angular Materials date picker component.

I'm iterating over a set of reports and need to create an input for each one, next to it. Every report has a custom date on it, beyond which a custom filter applies, transforming the allowed dates into basically swiss cheese. In other words the rules are to complex for min/max, and I need to use the filter function for it, and hand it the individual report.

When implementing this, I stumbled over a particularly intersting behavior: Angular cannot handle lambdas for filter functions. Here is a ticket I wrote (apparently this is expected behavior)

Github Issue

Stackblitz

The gist is that a loop in the change detection will cause the expression to be reevaluated indefinitely because it's a new lambda every time. Using the OnPush change detection strategy has no effect here, it's still a cycle.

I was hoping to associate a loop variable ("@for (report of reports; track report) { }") with a filter. I need to gain access to this template loop variable in TypeScript.

This is normally possible, except that $event isn't supported for [matDatepickerFilter].

Using a function that returns a lambda compatible with the filter functions type works on paper but causes the component to stop working in practice (See stackblitz).

There doesn't seem to be a way to hand the directives over either since 'matInput' or 'matNativeControl' don't implement 'exportAs'. So '#var=matInput' is out of the question as well.

I'm out of ideas.

What design patterns can I use to associate a report with an input and set a filter for a date picker based on that report?

I can provide a more complete Stackblitz or edit the current one if it helps.


Solution

  • So basically it forces you to have a reference for the applied filter. If the filter is based on some custom logic per item, one option you can try is to keep references to the created filters.

    For example:

    Typescript:

    private readonly filters = new Map<HTMLInputElement, (arg: Moment | Date | null) => boolean>();
    
    filter2(element: HTMLInputElement): (arg: Moment | Date | null) => boolean {
      let filter = this.filters.get(element);
      if (filter) {
        return filter;
      }
      filter = (arg: Moment | Date | null) => true;
      this.filters.set(element, filter);
      return filter;
    }
    

    Html

    <mat-form-field>
      <mat-label> Borked </mat-label>
      <input
        matInput
        #dateBox2
        [ngModel]="date2"
        [matDatepicker]="datePicker2"
        [matDatepickerFilter]="filter2(dateBox2)"
      />
      <mat-datepicker-toggle matIconSuffix [for]="datePicker2" />
      <mat-datepicker #datePicker2 />
    </mat-form-field>
    

    StackBlitz