Tuesday, December 1, 2015

Knockout with TypeScript - Observable Arrays

In my first post about using Knockout with TypeScript I talked about the basics of using these two together. This time I want to look at using Knockout's observable arrays. Observable arrays allow you to track changes to arrays, like adding or removing elements. Then you can use a foreach binding in your markup to output the elements of the array.

A lot of times your application will have lists of items that will grow or shrink depending on user interaction. For this example we'll expand upon the first post and create a list of tasks. We'll create an observable array and then output it onto the page.

First let's define the Task class. It has task name, percent complete and is complete fields.

class Task {
    public taskName = ko.observable("");
    public pctComplete = ko.observable("0");
    public isComplete: KnockoutComputed<boolean>;
    constructor() {
        this.isComplete = ko.computed(() => this.pctComplete() === "100");

Now let's take a look at the view model for our app. It has app name and tasks fields.

class AppViewModel {
    public appName = "Task List";
    public tasks = ko.observableArray<Task>();

Notice that the "tasks" field is defined as an observable array of Task objects. That means it is now being tracked by Knockout so whenever we add another task to the list it will update the page. The next thing we need is a way to add a task to the array. Let's create addTask and reset functions.

class AppViewModel {
    public addTask(): void
        var task = new Task();
        task.taskName("Task " + this.tasks().length);
    public reset(): void

In our addTask function we create a new Task object, set its name to the length of the tasks array, then add it to the end of the array using push(). Notice that to get the underlying array from a Knockout observable you have to execute the tasks function, e.g. tasks(). Just like any other observable it's a getter/setter.

We also added a reset function. This removes all of the tasks from the array by setting the tasks property to an empty array.

NOTE: There are some methods on the observable array object such as push() and pop(), which makes it seem like you're dealing with an actual array, but you're not. It is an object that wraps an array. This can get a little confusing at first, but you just need to remember that if you want to directly access the array you need to get it from the property first. Be careful there also! You must use the push() method of the observable object, not the underlying array, or it won't be observed by Knockout. My advice is don't access the underlying array unless you need something that the observable can't give you, like the length of the array.

Now let's go and write some markup that will display the task list on our page.

    <h1 data-bind="text: appName"></h1>
    <button data-bind="click: addTask">Add Task</button>
    <button data-bind="click: reset">Reset</button>
    <div data-bind="foreach: tasks">
            <input type="text" data-bind="value: taskName" />
            % Comp:<input type="number" min="0" max="100" step="1" data-bind="value: pctComplete" />
            <input type="checkbox" data-bind="checked: isComplete" disabled />Completed

In our markup we define two buttons with click events. One calls the addTask() function in our view model and the other calls reset(). Next we define a div element with a foreach binding set to the tasks array. Now everything inside that div will be repeated for each Task object in the array.

Notice that when inside of a foreach we are in the context of the current element of the array. Therefore we can get the name of the current task by using "value: taskName" as well as the other properties of the Task object.

OK, we have everything we need so let's try it out. If we click the Add Task button a new task will appear on the page with the name "Task 0". If we click the button again another task will appear and so on.

Now change the percent complete of one of the tasks to 100. The completed checkbox will become checked because of our isComplete computed observable.

If you click the Reset button all of the tasks will be removed and the page updated.

Observable arrays in Knockout make it easy for us to create dynamic lists if elements on a page. With only a few lines of code we were able to create an interactive list of tasks. Just remember, an observable array object is a wrapper over a JavaScript array, not an array itself.