'How to store local filter variables on session store in a generic way?

I'm hitting a problem in the application I work on in my job.

To describe the issue: Our application has pretty much a standard look and feel with a couple of different pages (Accessible via an Angular menu on the left) which show a list of the entities (accesstokens, invoices etc.) Most of these pages have one or more filters to limit the results, these filters are on the top of the page and consist of a dropdown box (for enumeration fields) or free text fields.

Now these filters are lost (set to empty) when navigating through the application's different pages. We want the filters to be stored during the current session (until the user closes the browser or tab). So this is definitely something we can use SessionStorage for. Also we want to make the implementation as generic as possible, we want to mark each control for which we want this feature. So I was thinking about a directive that we add to each control element we want to temporary store the state for. TO be able to uniquely identify each control, we ensure that each control has the id poperty set, making it guaranteed unique within the current page. If we also want to make it unique across the whole site, we would need a combination of the page name (or maybe template class name) and the control id.

My idea was about this:

  • Create a Directive which wil be placed on all controls we want to maintain state for (during the session)
  • On initalization, the Directive loads the currently stored value from the sessionStore and copies it to the value property of the control
  • The Directive listens to the onChange event and stores the new value after each change in the sessionStore (a probably easier alternative is handling the storage in the onDestroy event method).

There is however a problem with this approach. The field variable linked to the filter is declared in the Component and initilialized to an empty string/value in its onInit. The component.onInit is called after the onInit of the Directive and thus overrides the value obtained from the sessionStore. Leaving out the initialization gives an undefined error.

An alternative could possibly be storing the the current filter values in the SessionStorage in the onDestroy method, and reading them in the onInit method. This is maybe a working approach but I currently do not see a way to implement that in a generic way without the need of implementing onInit and onDestroy for all components involved. I am open for some hints. A friend of mine suggested looking at NgRx application state. Is that something I could use?

@Directive({
  selector: '[appStoreFormFieldValue]'
})
export class StoreFormFieldValueDirective implements OnInit {

  private key: string;

  constructor(private elementRef: ElementRef) {}

  @HostListener('change', ['$event.target']) onChange(target: any) {
    console.log('Element changed!', target.value);
    this.storeElementValue(target.value);
  }


  ngOnInit(): void {
    console.log('OnInit directive');
    this.elementRef.nativeElement.value = 'gezet in onInit ';
    this.key = 'accesstoken-' + this.elementRef.nativeElement.id;
    if (sessionStorage.getItem(this.key)) {
      console.log('key found in session storage, set value in control'); ///THIS IS OVERRIDDEN IN THE COMPONENT
      this.setElementValue(sessionStorage.getItem(this.key));
    }
  }

  private storeElementValue(value: string) {
    if (!value || value == '') {
      sessionStorage.removeItem(this.key);
    } else {
      sessionStorage.setItem(this.key, value);
    }
  }

  private setElementValue(value: string) {
    this.elementRef.nativeElement.value = value;
  }
}


export class AccessTokenComponent extends PaginationComponent implements OnInit {
  filter = {
    status: null,
    accesstoken: undefined
  };  //When omitting this initalization, errors are shown on the console

As described below, I tried to update the control value in the ngAfterViewInit method, though it is indeed called after the onInit of the main component, the valueis not updated on the screen.

  ngAfterViewInit(): void {
    this.renderer.setProperty(this.elementRef.nativeElement, 'value',  'GEzet in directive ngAfterViewInit met renderer');
    this.elementRef.nativeElement.value = "GEzet in directive ngAfterViewInit zonder renderer";    
  }


Solution 1:[1]

You can use the AfterViewInit hook instead of OnInit in your directive. It will be triggered after the view is initialized, and the default values have been set.

@Directive({
  selector: '[appStoreFormFieldValue]'
})
export class StoreFormFieldValueDirective implements AfterViewInit {
  // ...

  ngAfterViewInit(): void {
    console.log('AfterViewInit directive');
    // ...
  }

  // ...
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Pádraig Galvin