Bubbling up collectionObserver


#1

I have an array I need to observe for changes to property on elements and additions / removals from the array itself.

I have a collectionObserver working - and I get the .subscribe callback but don’t understand how I can get that to “bubble up” to properties on other models which depend on my array values? Something like

export class Model {
    httpResult: Response[];

    constructor(httpResult, bindingEngine) {
        this.httpResult = httpResult;
        bindingEngine.collectionObserver(this.httpResult).subscribe(() => {
          // nothing needed
          console.log('binding said it changed!');
        });
    }
}

export ViewModel {
    model: Model;

    constructor(model: Model) {
        this.model = model;
    }

    get projectData() {
        return this.model.httpResults.map(x => {
            id: x.Id,
            name: x.Name,
            displayData: x.DisplayData
        });
    }

I then have certain things updating httpResult on the model when addition callbacks from the server come. projectData isn’t updated - I’ve tried putting a @computedFrom('model.httpResult') ?

Is there a solution for this?


#2

If I’m understanding, you have a model and you want users of that model to respond to changes on it?
In that case I’d expose a callback function like this:

// property
onChange: Function;

// in constructor
bindingEngine.collectionObserver(this.httpResult).subscribe(() => {
  this.onChange();
});

But I also wouldn’t take this approach with something as basic as an array. In this case (and I appreciate it might be an abstraction from your real case) then I don’t believe the Model class is required at all. Instead the ViewModel, which I’m guessing from its name is the one with an html template, can have the array directly. Personally I tend to put data requests in the created() lifecycle method - not sure if that is best practice, but it has worked fine for me.

e.g.

import { bindable, autoinject } from "aurelia-framework";
import { HttpClient } from "aurelia-fetch-client";

export class ProjectModel {
  id;
  name: string;
  displayData: any;
}

@autoinject
export class ViewModel {
  
  projectData: ProjectModel[]; 
  // use @bindable before the above if another view includes this view-model and you want to share that data
  // in which case any additions made by push, insert, splice will bubble to the using view
  
  constructor(private httpClient: HttpClient) {}

  created() {
    this.httpClient.fetch("your-url").then(resp => {
      // respToHttpResults() is some function that extracts the data you actually want from the resp object.
      this.projectData = respToHttpResults().map(x => {
        id: x.Id,
        name: x.Name,
        displayData: x.DisplayData
      });
    });
  }
}

#3

Thanks for the help - I tried using sort of a callback, I put a “Changed” bool in the model class and toggled it each time the array changed. Using @computedFrom on the view model “bubbled up” the change notifications alright but it was kind of messy so I gave up on trying to observe arrays like this.

I have a parent model class because there’s backend stuff I want to do with the data without necessarily being in the view model. My app responds to change notifications from the server - logic I don’t want to put in every view model that uses the data.

Haven’t found a perfect solution yet but I’ll eventually figure out enough of the observer api to get it done.