Skip to the content.

Classic MVC and Reactive Programming, part 2

Next, let’s support saving values that a user has inputted.

In the classic MVC version, you’ll add a new save method to Model to serialize values to some form, and call it when a user clicks the save button. In this example, we’ll serialize values to a string and show it using alert instead of saving values to a file or a server.

class Model {
    ...

    save(): string {
        return JSON.stringify(this._values);
    }

    ...
}
$save.addEventListener('click', () => {
    window.alert(model.save());
});

How do we implement this with RxJS? One way of implementing this is to store values in the controller, handle a click event on the save button, then save it.

values$.subscribe(values => {
    $view.innerText = values.join(', ');
    $value.value = '';

    // Store values here
    this.values = values;
});

fromEvent($save, 'click')
    .subscribe(() => {
        // Use stored values
        window.alert(JSON.stringify(this.values));
    });

In this scenario, you use Observables as event emitters. I don’t think this is a very good approach. Instead, let’s think that a Observable is a value whose value changes.

In this approach, you can think that these two Observables are equivalent after a user has inputted 1, 2 and 3.

fromEvent($add, 'click')
    .pipe(
        map(() => $value.value),
        filter(value => value.length > 0),
        map(value => Number(value)),
        filter(value => !isNaN(value))
    );
of(1, 2, 3);

If it was of(1, 2, 3), you can get values by subscribing to it. Let’s try the same thing with values$.

fromEvent($save, 'click')
    .subscribe(() => {
        values$.subscribe(values => window.alert(JSON.stringify(values))).unsubscribe();
    });

When a user clicks the save button, subscribe to values$ to get the latest values and save the values.

Unfortunately, this doesn’t work because an Observable created by fromEvent doesn’t call next until a user clicks the add button next time. To make this work, you need to convert this Observable to BehaviorSubject which stores the latest value and emits it when it’s subscribed.

You can explicitly create BehaviorSubject or use share operator.

const values$ = fromEvent($add, 'click')
    .pipe(
        map(() => $value.value),
        filter(value => value.length > 0),
        map(value => Number(value)),
        filter(value => !isNaN(value)),
        scan((values, value, _) => [...values, value], [] as number[]),
        share({
            connector: () => new BehaviorSubject<number>([]),
        })
    );

Now you can get the latest values by subscribing to values$ when a user clicks the save button.

values$ is no longer just an event emitter, but it’s a value that changes over time, and you can get it by subscribing to it.

You can find the complete code at snakamura/mvc_rx.

In the next post, I’ll add support to load values.