2

I have a variable that stores the available cars at any moment. Is there a way to automatically re-evaluate this function on every change?

Just using this.carFactory.available in this case is not a solution, because this example I'm showing is simplified - the real calculation in my project is alot more complex.

calculateAvailableCars(){
    this.carFactory.available.forEach(function(item){
      this.availableCars.push(car.id);
    }.bind(this));
}

How could I do this in Angular 2? In Angular JS there was the possibility to $watch a function.

I could of course manually call this function everytime something changes, but it would be nice not to have to call this function in every part of the application that can change the data.

GeForce RTX 4090
  • 3,091
  • 11
  • 32
  • 58
  • Can you be more precise, please? Who triggers this method and when? Why can't you just re-call it with every new car? –  Jul 20 '18 at 06:50
  • https://stackoverflow.com/questions/34569094/what-is-the-angular-equivalent-to-an-angularjs-watch – artista_14 Jul 20 '18 at 07:01

3 Answers3

3

Using template function reference with auto change detection

You can use this function output on template:

carOutput(): cars[] {
  this.calculateAvailableCars()
  return this.availableCars;
}

and use output on template:

<p>My car ratio is {{ carOutput() }} </p>

However this will trigger very aggressive change detection strategy on this variable. This solution is the simpliest one, but from engineering perspective rather worst: consumes tons of unnecessary function calls. One note, that hosting element must not be set to detect changes onPush.

Separate data model to parent component and pass as property to child

You can store car list display in separate component, and pass new car array as input property to this component:

<car-display [cars]="availableCars"></car-display>

Then you can set changeDetetcion policy in this component to onPush, and each time input property bind to availableCars will change, <car-display> will re-render.

If update relays on some host binding

If some external host action is triggering new cars calculation, then hostBinding may help:

@hostListener(`hover`) recalculateCars() {
  this.calculateAvailableCars()
}

And finally, (because you describe your use case quite cryptically, without many details, thus I'm scratching all possible scenarios) if some external component action shall trigger re-calculation, you can hook to ngLifecycle ngOnChanges() if for example external input property change shall re-trigger cars calculation.

In other words and summing all that up, it depends who and from where triggers changes, that shall re-trigger available cars recalculation.

And very important, see an answer from @chiril.sarajiu, because what we are trying to work around here can be handled automatically by single observable. This requires additional setup (service, provide observable to components, e.c.t.) but it's worth.

--- EDIT ---

If each variable change shall retrigger data

As OP clarified, that changes are related with model bound to component. So another option with mentioned by @marvstar is using set, where each model variable change will retrigger fetching function:

modelSchangeSubject: Subject<Model> = new Subject<Model>();

ngOnInitt() {
  this.modelSchangeSubject
  .subscribe((v: Model) => {
    this.calculateAvailableCars()
  })
}

/* Rest of controller code */
set modelBounded(v: Model) {
  this.modelSchangeSubject.next(v);
}
Tomas
  • 3,269
  • 3
  • 29
  • 48
  • And you should go the extra mile with Observables, as if you want to see how many unnecessary calls to your function are being made - try adding `console.count()` inside of it. And for that matter, you can combine the two answers and use `onPush` with `async` pipe and get the best experience. [here's a good read on the subject](https://blog.angular-university.io/angular-2-smart-components-vs-presentation-components-whats-the-difference-when-to-use-each-and-why/) – k.s. Jul 20 '18 at 08:38
  • Totally agree. Even if there is no network action, you can still dress up your components variables in subjects and `next`/`subscribe` them to create kind of mini event-bus in your component. – Tomas Jul 20 '18 at 09:16
1

You need RxJS. What you do is you create a data service, which will store an Observable (in my case a BehaviorSubject, which is mostly the same, but in my case I start with a value).

export class DataService {
  private dataStorage$ = new BehaviorSubject(null); //here is the data you start with

  get getDataStorage() {
     return this.dataStorage$.asObservable(); // so you won't be able to change it outside the service
  }

  set setDataStorage(data: any) {
    this.dataStorage$.next(data);
  }
}

Then you subscribe to this data changes everywhere you need to:

constructor(private dataService: DataService){}
ngOnInit() {
  this.dataService.getDataStorage.subscribe((data) => this.calculateAvailableCars(data));
}
calculateAvailableCars(){
   this.carFactory.available.forEach(function(item){
     this.availableCars.push(car.id);
   }.bind(this));
}

Read more about best practices of using RxJS in Angular, as there can be quite a bit of pitfalls and problems.

k.s.
  • 2,964
  • 1
  • 25
  • 27
  • 1
    Using RxJS is superior, but I think you might miss the case, that OP want's to auto-recalculate "on every change", which is a quite blurry statement. Anyway, if this means "on every change on data" this is very best solution. – Tomas Jul 20 '18 at 07:08
  • Well I understood this as "on every change - recalculate", and I don't know a better solution than RxJS really. It might be achievable by promises, but RxJS is more elegant I think :) – k.s. Jul 20 '18 at 07:36
  • Sorry if I wasn't clear enough - what I meant was on every change of the model bound to component, not only when new data is retrieved from a service. – GeForce RTX 4090 Jul 20 '18 at 08:48
0

Try using setter and getter.

private _YourVariable:any;

public set YourVariable(value:any){
this._YourVariable = value;
//do your logik stuff here like. calculateAvailableCars
}

public get YourVariable():any{
return this._YourVariable ;
}
marvstar
  • 417
  • 5
  • 21