We've successfully created a working Application. When we run it, data is presented to the browser as intended. But what happens if we want to change the data? Currently, if we change the model, nothing. Lucky for you, Cascade makes this simple.

Observable Properties

Cascade provides Observable properties for objects. Once established, these special properties may be subscribed to. Then, if the value of the property changes, the subscribers will be notified with the updated value. These special properties can be read and written to exactly like regular properties.

We can use the TypeScript decorator @observable in the class definition to make a property observable.

@observable property: type = value;

If we don't want to use the decorator, we can attach an observable property to an object with the call:

Cascade.createObservable<T>(obj: any, property: string, value?: T);

Let's take our User example from the last chapter, and make it observable. So, simply import the @observable decorator, and add it in front of any properties you want to observe.

import { observable } from 'cascade';

export default class User {
    @observable firstName: string;
    @observable lastName: string;
}

And there we have it! Our firstName and lastName properties are now Observables!

Handling Input

Now that we can watch for changes in our data, let's set up some inputs!

export default class UserView extends Component<IUserViewProps> {
    render() {
        let {user} = this.props;
        return (
            <div>
                <p>First name: {user.firstName}</p>
                <p>Last name: {user.lastName}</p>
                <p>First name: <input type="text" value={user.firstName} /></p>
                <p>Last name: <input type="text" value={user.lastName} /></p>
            </div>
        );
    }
}

There are a couple of things to note:

  1. We have included two <input> tags.
  2. We inject the value using {} notation.

When you run it, you will see your data now displayed in two text fields. But what happens when you type in the inputs? Currently, still nothing.

Cascade works with one way data binding, meaning that changes to data flow from Application State to Components not the other way around. This is to prevent circular references, where an update moves from A to B to C and so on, but somehow goes back to A. If that happens, we will end up in an infinite loop.

So, any changes to our User will show up in our UserView, but changes to our UserView don't automatically go back to our User. In order for that to happen, we need to handle Events.

An Event is triggered when something happens, like when a user clicks the mouse, or presses a key. It can even happen when an AJAX call completes. Normally, a program will execute its instructions until there is nothing left to do, and it will either end, or wait for input. So, we need to handle that input.

For our UserView add these two methods above the render method.

updateFirstName = (event) => {
    this.props.user.firstName = event.target.value;
}

updateLastName = (event) => {
    this.props.user.lastName = event.target.value;
}

There are a couple of things to note:

  1. These methods will handle the Event, and store the value of the target into our User.
  2. We are using the Arrow Function notation, as the this value may be changed while executing.

But how do we hook these handlers up to our inputs?

<p>First name: <input type="text" value={user.firstName} oninput={this.updateFirstName} /></p>
<p>Last name: <input type="text" value={user.lastName} oninput={this.updateLastName} /></p>

And that's it! Any time the user updates the text inputs, the Event is triggered, and the User is updated.

We could also use regular methods instead of Arrow Functions for updateFirstName and updateLastName. In that case, when we inject them, we must use Function.bind().

<p>First name: <input type="text" value={user.firstName} oninput={this.updateFirstName.bind(this)} /></p>
<p>Last name: <input type="text" value={user.lastName} oninput={this.updateLastName.bind(this)} /></p>

In many cases, this syntax is harder to read, but it is personal preference. In some cases, where you must inject a specific value into the method, Function.bind(this, value) is useful.

Running our Application

So, we can now display and update our User. Try running it and see what happens!