Reactive Properties

Discover the world of IT

Reactive Properties

As a developer using angular you probably faced the fact that working with observables is indeed handy, when developing a reactive UI. The other day I stumble upon a great post (Craftsmen.nl) that illustrated the use of Object.defineProperty, to make a function that allows for any property to be observed, with just a single line!  

The function creates a behavioral-subject and modifies the getter and setter of the parsed property. The property setter compares the previous value with the current value and sets the value of a behavioral-subject if the value has changed. Lastly, it retunes the behavioral-subject as an observable object.

There are listed three examples below, each illustrating an approach to observe an object. This is carried out using one of Angulars’ lifecycle hook and by using the objects’ interceptors (setter and getter). Lastly, an illustration of the utility function in use is provided, followed by the utility function itself.

Disclaimer

Not all objects should be observed, picking an object that can benefit from the observable structure is beneficial. The examples below illustrate its use on a Map, allowing the observable object to return a list of keys. The list of keys can then be used to iterate over and display the values of the Map.

Examples 

The examples below illustrate different approaches to create an observable object from an input.

This includes the use of the Angular lifecycle-hook ngOnChanges, which is triggered every time a data-bound property changes and therefore makes a lot of unnecessary checks which is not suitable for this purpose.

A better approach is therefore to utilize the property getter and setter which is the second example. However, that might generate a lot of code if you want to observe multiple inputs.

The third examples use the idea of the getter and setter functionality and create a function around it allowing us to listen to the property changes, with just a single line. It does so by applying a function that that is included in the fourth example, which modifies the getter and setter to create an observable.

Observable using LifeHook  

  @Input() map: Record<string, string>;
  private map$: BehaviorSubject<Record<string, string>> = new BehaviorSubject<Record<string, string>>(undefined);

  ngOnChanges(changes: SimpleChanges): void {
      if (changes.map.currentValue !== changes.map.previousValue) {
        this.map$.next(changes.map.currentValue)
      }
  }

Observable using getter and setter  

  @Input() set map(map: Record<string, string>) {
    this.map$.next(mapAToB);
  }

  get map(): Record<string, string> {
    return this.map$.getValue();
  }

  map$: BehaviorSubject<Record<string, string>> = new BehaviorSubject<Record<string, string>>(undefined);

Observable using observe-property 

  @Input() map: Record<string, string>;
  map$: Observable<Record<string, string>> = observeProperty(this, "map");

Observe-property

function observeProperty<T, K extends keyof T>
(target: T, key: K): Observable<T[K]> {
  const subject = new BehaviorSubject<T[K]>(target[key]);
  Object.defineProperty(target, key, {
    get(): T[K] {
      return subject.getValue();
    },
    set(newValue: T[K]): void {
      if (newValue !== subject.getValue()) {
        subject.next(newValue);
      }
    }
  });
  return subject.asObservable();
}

Leave a Reply

Your email address will not be published. Required fields are marked *