kutyel / linq.ts

๐ŸŒ€LINQ for TypeScript

Home Page:http://kutyel.github.io/linq.ts/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Question on Best Practice for Data Binding / Performance of ToArray()/new List<T>

mzhukovs opened this issue ยท comments

First off, this is awesome. As someone coming from C# picking up TypeScript, this makes me feel a lot more at home.

Question regarding usage of these magic Lists. In a js framework, e.g. Vue, obviously they would expect Arrays for binding (i.e. can't bind directly to List. So is it better to 1) keep your data as a List in the component and just call ToArray on it for binding, or 2) keep your component data as an array that you bind to, have a computed prop that turns it into a List, and use that computed prop for manipulation.

Which would be the recommended approach in general, or is there a better way I'm missing? Which in most cases would be more performant? Would appreciate any insight.

Here's a Vue example of option 2:

@Component
export default class App extends Vue {

  servers: IServer[] = [
    { name: "Ol Faithful", num: 1, age: 10, status: "Up" },
    { name: "Rackspacer", num: 2, age: 3.5, status: "Up" },
    { name: "Basement Dweller", num: 3, age: 5, status: "Down" },
    { name: "Big Rig", num: 4, age: 7, status: "Unknown" },
    { name: "Dyno-mite", num: 5, age: 1, status: "Up" }
  ];

  get serverList() {
    return new List(this.servers);
  }

  testLINQ() {
    // test updating data
    this.serverList.First().name =
      this.serverList.First().name + " - updated via LINQ";

    // test linq functions
    let maxAge = this.serverList.Max(s => s.age);
    let oldestServer = this.serverList.FirstOrDefault(s => s.age === maxAge);
    alert(`Oldest server is ${oldestServer.name} with an age of ${maxAge}.`);
  }
}

Hi @mzhukovs! I'm very glad you enjoy this library, thanks for your kind words! ๐Ÿ˜ (I also came from a C# background, and the reason you mentioned is exactly why I created this library, to make .NET folks feel more like home in the JS/TS ecosystem ๐Ÿ˜‰).

As per your question, I'm not familiar with Vue but I think you can use any data structure / library as a replacement for native Arrays, and it shouldn't be an issue:

servers: List<IServer> = new List([
    { name: "Ol Faithful", num: 1, age: 10, status: "Up" },
    { name: "Rackspacer", num: 2, age: 3.5, status: "Up" },
    { name: "Basement Dweller", num: 3, age: 5, status: "Down" },
    { name: "Big Rig", num: 4, age: 7, status: "Unknown" },
    { name: "Dyno-mite", num: 5, age: 1, status: "Up" }
]);

If you observe any performance issue coming back and forth from the native Array, please file an issue and I will try to solve it to the best of my ability! ๐Ÿ’ช

As per the most specific question regarding Vue, I have a good friend, @alexjoverm which I would value very much his insight ๐Ÿ˜‰

Hope this helps!

Hey thanks for the quick response, yes keeping the data in the component as the List would fall under my option 1 that I mentioned, but then because in the template we can't bind directly to the List object would have to call .ToArray on it for everything to render. Hoping your friend responds since I guess it is fairly Vue specific although I'm sure it's the same story with Angular.

Not sure if using a computed to create a List from an array is much viable. Not because of performance, but because if you use any method that mutates the data (say setWhatever) it wouldn't work since on every servers change that List instance will be new.

From the other hand, most likely Vue wouldn't be able to observe inner changes in the List instance if you declare it as local state.

So I'm thinking about two possible ways:

  • Keep it as a List in the component state but make every change immutable, returning a new instance of List. Then use a computed prop to turn it to a simple array in order to use it on the template
  • Just use List out of the components, wherever you handle the business logic.

This would apply to React or Angular the same way AFAIK.

Hey @alexjoverm, thanks for the quick reply.

Actually the example I provided above works, where testLinq() does mutate the original data via the computed prop (and child components also get updated). I was asking more from a performance/recommended setup perspective. Full example here.

Performance wise I don't think it should be an issue, if you have a look at the implementation of toArray():

public ToArray(): T[] {
  return this._elements
}

You'll see I'm just unwrapping the values inside the list, no further transformation of the array. Feel free to close the issue whenever you think your question is answered btw ๐Ÿ˜‰

Alright fair enough, thanks guys

Thought I would add this for future users of this great library and mobx. Linq.ts is not compatible with mobx's change detection, so to get it to work, you would always use a computed property for the Linq list, and use an array for the observable property like so:

import { List } from 'linq.ts';
import { autorun, observable, computed } from 'mobx';

class Test {
  @observable list = new List<number>([1,2,3]);
  @observable a = [1,2,3];

  @computed get list2() { return new List<number>(this.a);}

  constructor() {
    console.clear();

    autorun(() => {
      console.log("Only runs once");
      console.log(this.list);
      console.log(this.list.ToArray());
    });

    this.list.Add(4);

    autorun(() => {
      console.log("Runs three times");
      console.log(this.a);
    })

    autorun(() => {
      console.log("Should also run three times");
      console.log(this.list2);
    })

    this.a.push(4);
    this.a.push(5);    

    console.log(this.list);
  }    
}

var test = new Test();

Stackblitz example: https://stackblitz.com/edit/mobx-linqts

My fantasy is to have linq.ts modified to be compatible with Mobx's change detection (which uses Javascript proxy objects to implement its change detection). Then I'd have the dream combination :)

FIY, I just ended up passing in an observable array to the List constructor to get it to respons to Mobx's change notifications (you can use them in Angular templates now and it will automatically update itself whenever an item is added to or removed from the list):

let list = new List<SomeObject>(observable.array());