Kendo UI MVVM with TypeScript - Making ViewModels as “classes”

耗尽温柔 提交于 2019-12-12 12:42:10

问题


I am transitioning over a project to Typescript, and it uses Kendo UI's MVVM architecture. However I am having a bit of a problem with the concept of classes and its relationship to the view models.

I will establish a class and extend kendo.data.ObservableObject, which is what you create a view model from, and populate it with my fields, like this.

export class ViewModelSample extends kendo.data.ObservableObject {
   Id: string = null;
   Name: string = null;
   Items: kendo.data.ObservableArray = [];
   // other fields
   constructor() {
      super();
   }

   map(params){
      // some code
   }
}

Basically this class represents my view model, nicely encapsulated. So I summon it up like this;

var viewModel = new ViewModelSample();
kendo.bind($('#binding-area'), viewModel);

This works fairly well, but the behavior becomes kind of awkard. For instance, if I have a collection within the class and I push something to it, the user interface does not update.

If I do this in normal javascript, it works;

viewModel.Items.push(new Item(/* parameters */));
// view model updates, and user interface updates

however if I do this in typescript, the view model updates, but the DOM does not. I have to manually type in ...

viewModel.Items.trigger('change');

In order to get the UI to update.

Can anyone help me understand why this would happen?


回答1:


You need to instantiate the items property as a Kendo ObservableArray. Currently you're only defining its type in TypeScript (I'm surprised the compiler does not complain about this?).

Update your code to:

Items: kendo.data.ObservableArray = new kendo.data.ObservableArray([]);

For more information, consult the Kendo docs.




回答2:


I had run into this problem too, and it is actually due to the way TypeScript generates the JS.

In normal JS, you can do:

new kendo.ObservableObject({
    items: []
});

and the constructor will automatically wrap the items[] into a new ObservableArray for you.

However the behavior fails in TypeScript because the code:

export class ViewModelSample extends kendo.data.ObservableObject {
    Items: kendo.data.ObservableArray = [];

    constructor() {
       super();
    }

actually generates something like:

function ViewModelSample() { // constructor
    _super.call(this);       // call to ObservableObject constructor (super())
    this.Items = [];         // add Items to this.
}

So the Items property isn't added to the ViewModel until after the ObservableObject constructor is called, so Kendo doesn't know about it, and doesn't wrap it into an ObservableArray.

The ordering is just backwards from what Kendo expects out of normal JavaScript.

@Anzeo's suggestion to manually init the Items to an ObservableArray will mostly work

Items: kendo.data.ObservableArray = new kendo.data.ObservableArray([]);

however the ObservableArray's parent won't be correctly set to the ViewModel, so sometimes if you try to MVVM bind a function or property on the ViewModel inside a ListView template that is bound to Items, it won't be able to find the properties or functions on the ViewModel.

You can prove it by doing this in a devtools console:

x = new kendo.data.ObservableObject({
    items: []
});
x.items.parent(); // returns the object 'x' (correct)

y = new kendo.data.ObservableObject();
y.items = new kendo.data.ObservableArray([]);
y.items.parent(); // return undefined (but should be the object 'y')

This is really a case where TypeScript just gets in the way more than it helps...


The correct solution is to do the initial assignment of your class variables inside your constructor, before the call to super().

class ViewModelSample extends ObservableObject {
    Items: kendo.data.ObservableArray;

    constructor() {
        this.Items = [];
        super();
    }
}

which generates the JS in the correct order:

function ViewModelSample() {
    this.Items = [];  // Items assigned before call to ObservableObject constructor
    _super.call(this);
}

Interestingly, the playground on typescriptlang.org doesn't seem to care that the line:

this.Items = [];

assigns an Array to a variable that is supposed to be of type ObservableArray, but if the real compiler throws an error, then you could also just do

this.Items = new kendo.data.ObservableArray([]);

and that should work. As long as the generated JS adds the Items property to the object before the ObservableObject constructor is called.




回答3:


You have to call super.init(this) at the end of your constructor to properly set up the dependency tracking. As per the documentation:

constructor() {
    ...

    super();
    super.init(this);
}


来源:https://stackoverflow.com/questions/21240818/kendo-ui-mvvm-with-typescript-making-viewmodels-as-classes

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!