Monday, February 8, 2016

Knockout with TypeScript - Embedded View Models

In my last post about Knockout and TypeScript I talked about interacting with observable arrays. This time we'll take a look at using embedded view models.

Knockout uses the concept of view models at its core. View models link to the view to provide data binding and functionality. There are times when you may want a more complicated structure for your view model. You may want to have child view models inside of view models so you can partition your application more effectively. So today I'll talk about how you do that.

What I sometimes like to do is create a master view model for my application in general, and then add view models underneath it for specific parts of the application. Say we have an application with two different views that have different data. In this example one view shows a list of favorite books and another shows favorite authors. You could have one view model with everything in it, but you can get a problem with namespacing if both views have similar properties.

class AuthorsViewModel1 {
    public appName = "Two views demo";
    public currentView = ko.observable("authors");
    public authors = ko.observableArray<string>();
    public books = ko.observableArray<string>();
    public sortAuthorsAscending = ko.observable(true);
    public sortBooksAscending = ko.observable(true);
    constructor() {
        this.sortAuthorsAscending.subscribe(() => this.authors.reverse());
        this.sortBooksAscending.subscribe(() => this.books.reverse());
    }
}

In this case we need to keep track of the sort order for both lists. Since they are both in the same view model we are required to give them unique names. We could make this easier by using child view models. Let's make a view model for authors and one for books.

class AuthorsViewModel {
    public authors = ko.observableArray<string>();
    public sortAscending = ko.observable(true);
    constructor() {
        this.sortAscending.subscribe(() => this.authors.reverse());
    }
}
class BooksViewModel {
    public books = ko.observableArray<string>();
    public sortAscending = ko.observable(true);
    constructor() {
        this.sortAscending.subscribe(() => this.books.reverse());
    }
}
class FavoritesViewModel {
    public appName = "Two Views Demo";
    public currentView = ko.observable("Authors");
    public authorsVM = new AuthorsViewModel();
    public booksVM = new BooksViewModel();
}

What we did was move the properties that were specific to each view into their own view model. Then we reference those view models inside of our main view model, FavoritesViewModel. This makes it a lot easier to determine what data goes with which view.

Now we need a way to use those view models in our HTML file. Knockout has a control flow binding that can help us out with that. It's the "with" binding. The "with" binding specifies a view model to use for a specific section of HTML.

<body>
    <h1 data-bind="text: appName"></h1>
    <select data-bind="value: currentView">
        <option>Authors</option>
        <option>Books</option>
    </select>
    <div data-bind="visible: currentView() === 'Authors', with: authorsVM">
        <h2>Favorite Authors</h2>
        <label><input type="checkbox" data-bind="checked: sortAscending" /> Ascending</label>
        <ul data-bind="foreach: authors">
            <li data-bind="text: $data"></li>
        </ul>
    </div>
    <div data-bind="visible: currentView() === 'Books', with: booksVM">
        <h2>Favorite Books</h2>
        <label><input type="checkbox" data-bind="checked: sortAscending" /> Ascending</label>
        <ul data-bind="foreach: books">
            <li data-bind="text: $data"></li>
        </ul>
    </div>
</body>

If you look at the <div> for our favorite authors section it has a "with: authorsVM" binding. From that point down in the DOM the context for the data will be the AuthorsViewModel object. So now if you're in the authorsVM context and you click the sort order checkbox it's going to refer to the sortAscending property on the authorsVM model. Same for the favorite books section, it has a "with: booksVM" binding.

The "with" binding in Knockout makes it easy for us to separate our view models by view which make it easier to read and maintain our code when dealing with multiple views. We start with a primary application view model and add child view models to it then associate them with different views.

No comments:

Post a Comment