Taking TodoMVC For Granted

The TodoMVC project has numbed us all to the importance of the Todo use case. We don’t even value it anymore. Of course your framework has a TodoMVC implementation. Why wouldn’t it? Everyone does! Angular makes this all so easy! React has a virtual DOM!

JavaScript developers are spoiled. I would love to see a TodoMVC implementation in Objective-C. That would be one giant bowl of bracket soup.

We have completely taken for granted what it takes to effectively manage complex state. I’m not saying that’s a bad thing. However, we’ve got to stop and make sure that we understand at least what a Todo List can teach us about the way our chosen library handles state before we start writing a blog post all about how we added Redux to our Angular application that we built on React. All this does is add noise and confusion to an already intimidating landscape of hype. The simple Todo List is, in fact, still the canonical example of handling complex state and should be respected as such.

I realized all of this during my attempt to re-create the TodoMVC project in NativeScript.

NativeScript Todo

One of the great things about NativeScript is that it brings familiar web concepts (such as observables) to native development. NativeScript is not a traditional web JavaScript library. It is a runtime for building native mobile applications, but the way that you would structure those apps and handle state is nearly identical to the way you would do it on the web.

Out of the box, NativeScript offers a very raw binding implementation. It’s powerful, but it’s raw. The net result of that is a lot more power, and with great power comes overly used movie quotes.

great-power

Before I built the Todo List project, I made a lot of really stupid mistakes simply because I did not understand what NativeScript did and did not offer in terms of constructs to manage state. I’m going to go over those here in the hope that you don’t make the same mistakes, and that you will at least peruse the code of this project.

Note that I am using what we call Vanilla NativeScript in this example. There is also an Angular 2 implementation of this exact same project that can be used for reference.

TodoMVC implementations are single page apps, which means that the native version is purposefully simple in it’s application structure. Native apps usually have more than one screen and that’s no big deal. You don’t hear native developers sitting around talking about how cool it is that their app never posts back. It simply isn’t a thing for native apps. That’s one place where this example will not help you, but passing data between views in NativeScript is rather trivial.

The first mistake that I made in NativeScript was not understanding computed properties.

Computed Properties

In NativeScript, computed properties are essentially expressions which are bound to methods that calculate some value. That value can be a string, a boolean, and integer – really anything. The point is that whatever value we are looking for cannot be gleaned from simply looking at a property on the model at any given point in time.

For instance, when there are no items showing on the screen, the page footer of the application which contains all of the different filtering choices for the list, as well as how many items are remaining, should not be visible. In order to do that, we have to know if there are any items in the collection, no matter what state they are in.

todos-no-todos

When I first tried to implement this, I thought I would just check the length of the collection array in the visibility binding and be done with it.

<StackLayout class="summary" visibility="{{ todos.length > 0 ? 'visible' : 'collapsed' }}">

Unfortunately, this isn’t something that NativeScript expressions support. So then I began using flags. I created a flag called hasItems which would return a boolean.

<StackLayout class="summary" visibility="{{ hasItems ? 'visible' : 'collapsed' }}">

This worked, but now I had to constantly update the hasItems flag. And this wasn’t the only place I was using a flag. I think, at one point, I had about 5 of them and I began to wonder how things had gone so wrong in my life.

Flags Are an Anti-Pattern

Flags are like duct tape – if it isn’t working, you aren’t using enough of it. The moment you start using flags, you have just signed up to manage all of your state manually. That’s not a good use of a binding framework.

A better way to solve this problem is with computed properties. You can create a method on the view model which returns a true or false based on the length of the array. If anything changes in the view model, this function will be called again and the UI updated.

<StackLayout class="summary" visibility="{{ hasItems() ? 'visible' : 'collapsed' }}">
hasItems() {
    return this.todos.length > 0;
}

With that problem solved, I quickly ran up against my next challenge, which was trying to figure out when NativeScript’s observables actually notify the UI that something has changed.

Observable Property Changes

For instance, when an item is marked completed on the list, the main container needs to update the “Items Left” number in the footer accordingly.

todos-items-left

The problem here is that it’s the item itself that is changing, not the entire collection. The collection itself is an observable array, and each item inside that collection is also observable. That means that when an item is marked complete, it’s super easy to just toggle the completed property on that item.

check(todo: Todo) {
    todo.set('completed', !todo.completed);   
}

That works just fine for toggling the status of the item, but there is no way for the collection to know that a specific property on an individual item has changed, and therefore no way for the actual view model to know that information either. NativeScript provides a mechanism for dealing with this, though. It’s called “Property Change Notification”.

notifyPropertyChanged

At any time, it is possible to call the notifyPropertyChanged event on an observable to let the object know that something has changed, so the UI should be notified. This method expects two parameters: the name of the property that changed and the data that changed. These values can really be anything if we are just trying to get the observable to notify the UI to check and update itself.

check(todo: Todo) {
    todo.set('completed', !todo.completed);   
            
    this.notifyPropertyChange('todos-change', null);
}

The last, and possibly hardest lesson that I learned, was how to use NativeScript’s filters.

NativeScript Filters

There is an segmented bar in the footer that toggles between all items, active items, and items that are done. Any time an item is changed, these views have to update.

todo-segmented-bar

At first, I was trying to do this manually in my code because those were the examples that I found on the internet. As it turns out, the internet is not always right.

An observable array can be filtered at any time using the filter method which is available via the ObservableArray object. However, the return value is a plain array. This means that in order to implement this filter functionality in code, you basically have to keep two copies of the same collection and manually filter them. This totally sucks.

Frameworks like Angular 2 solve this with pipes. NativeScript has a similar concept that it calls “filters”.

NativeScript Filters

Filters in NativeScript allow you to apply some filtering to a collection via a binding expression. That filter is then reevaluated every time the observable changes, which is exactly what we want in this scenario.

Filters in NativeScript are also sometimes called “formatters”. The idea is the same: you have a collection of items and you want to perform some filtering or formatting on each item without actually changing the underlying collection. That’s what filters and formatters do.

Filters are provided with a pipe in NativeScript the same way that they are in Angular 2 and Polymer. For the Todo App, it looks like this:

<Repeater items="{{ todos | filterCompleted() }}">

The repeater will now call a function called filterCompleted on the bound view model and whatever that function returns is what the repeater will use as it’s list of items. It’s important to note here that in NativeScript, you can provide bi-directional instructions for filters/formatters. You can format data coming from the model, and data going to the model. In my case, I just needed to format the data coming from the model to the view, so that’s the only object that I need to specify.

filterCompleted = {
    toView: (todos: ObservableArray<Todo>) => {            
        let filteredTodos = todos.filter((todo: Todo) => {
            if (this.theFilter == undefined) {
                return true;
            }
            else {
                return todo.completed == this.theFilter;
            }
        });

        return filteredTodos;
    }
}

The filter simply iterates over the full collection of items and compares each item to a property on the view model called theFilter. This is the property that is set when items in the segmented bar are selected. Simply setting the value of theFilter causes the filterCompleted filter to recalculate. This is the beauty of observables: when you change one part of the observable, any computed properties, filters or formatters automatically re-run themselves to make sure that the UI is always up to date.

Learn By Todo’ing

I learned a lot by building the Todo List. Far more than I had in the previous 4 months when I was just building applications willy nilly. We tend to think that we don’t need “Hello World” or “TodoMVC” because those are “too simple” and we “already know that stuff”. This is a fallacy of epic proportions. I promise you that if you take the time to do the basics, you will learn that there is a lot you don’t know. In the case of JavaScript libraries, building the TodoMVC example is still the best way to make sure that you at least understand the fundamentals.

You can grab the complete code for this application on GitHub and have a look at the Angular 2 version for comparison.

Comments