Reactive Programming with Angular by Example (Part 2)

Posted on Posted in Angular2

In the first part of this series, we’ve seen how to call a REST service and how to display the result asynchronously. Now we’re going one step further. How to work with data that isn’t really there, but can only be observed as a volatile stream of data events? Because that’s what an Observable is. Reactive programming is stream processing, and the strictest form of reactive programming uses Observables without memory.

Working with the data

For instance, let’s return to our address example. We’d like to implement the “edit address” functionality. How to implement the “save” button? How to bind the form data to the Observable address in the first place?

The challenge is that we don’t have the address at hand. We only have an Observable of this address. The older (but sort of deprecated) API makes this even clearer: we don’t have an address, we have a Promise of an address. We can’t assume that the promise has been fulfilled.

At this point, many Angular developers recommend using Redux or @ngrx/store. But it’s also possible to do this without using a third-party library.

Working with snapshots

Until now, we’ve used the Observable returned by the REST call directly:

export class UserService {
  public address$: Observable<Address>;

  constructor(private http: Http) {
    this.address$ = http.get("https://example.com/rest/user/address")
      .map(response => response.json() as Address);
  }
}

The disadvantage of this approach is that there’s no way to access the data when the user clicks the “save” button. The Observable returned by the Http service is volatile. When the data arrives, it’s posted as an event. If you’ve missed the event, it’s lost.

The solution is to use a BehaviorSubject. This is an Observable which doesn’t forget its last message. In our case, we can simply access the address by calling getValue():

export class UserService {
  public address$: BehaviorSubject<Address> 
         = new BehaviorSubject<Address>({});

  constructor(private http: Http) {
    http.get("https://example.com/rest/user/address")
      .map(response => response.json() as Address)
      .subscribe(newAddress => { this.address$.next(newAddress);});
  

  public save(): void {
     const addressToSave = this.address$.getValue();
     // REST call posting the new address
     // omitted for brevity
  }
}

Note that the constructor of the BehaviorSubject takes an initial value. We can use this to get rid of the Elvis operators we’ve struggled with in the previous post.

Also note that the REST call populates the ReplaySubject by calling its next() method. The ReplaySubject itself is a constant.

ReplaySubject

Another option I’ve seen and used in a project is the ReplaySubject. This is an Observable replaying the entire history of events each time someone subscribes it. So we don’t have to access the last value of the Observable. When we need the data, we simply subscribe to the Observable. This, in turn, makes the Observable replay every event it has sent before. The last event is the snapshot of the data we’re interested in.

In our case, it suffices to limit the length of the history to one (i.e. only the latest event is replayed):

export class UserService {
  public address$: ReplaySubject<Address> 
         = new ReplaySubject<Address>(1);

  constructor(private http: Http) {
    http.get("https://example.com/rest/user/address")
      .map(response => response.json() as Address)
      .subscribe(newAddress => { this.address$.next(newAddress);});
  

  public save(): void {
     address$.subscribe ( addressToSave =>
        {
          // REST call posting the new address
          // omitted for brevity
        }
  }
}

Don’t forget to cancel the subscription after finishing the action. Otherwise, the data is immediately saved each time it’s modified. So in general, I’d prefer the simpler solution using the BehaviorSubject.

There’s an exception to the rule: sometimes you want to implement a long transaction. The idea is that the user makes various edits without saving them immediately. At some point in time, the user clicks the “save” button, and the entire list of changes is stored in the database in a single transaction. The ReplaySubject is a nice tool for that. It allows us to keep track of the changes, and to replay them by simply adding a new subscription.

Immutability

It’s a good idea to treat the data (i.e. the address of our example) as an immutable object. Mind you: if you modify the address object, Angular (probably) won’t notice. It won’t update the screen. It only redraws the screen after calling next():

export class UserService {
  public address$: BehaviorSubject<Address> 
         = new BehaviorSubject<Address>({});

  public modify(): void {
     const address = address$.getValue();
     const newAddress = Object.assign({}, address);
     newAddress.city = "London";
     this.address$.next(newAddress);
  }
}

Truth to tell, when I tested the example, Angular did update the screen even without sending the new address by calling next(). But of course, the subscribers didn’t notice the change, so it’s a good idea to stick to immutability and post updated data as modified copies of the old data.

Forms

This is where I’m sort of at a loss. There doesn’t seem to be an official guideline on how to implement forms with asynchronous data sources. So I’ve concocted my own approach. Probably I’m confused because working with Observables implies that everything should be considered a volatile stream of events. Most likely, that’s rubbish. There’s no rule forbidding the form or the component to store the snapshot of the address as an ordinary variable. In a way, that’s inevitable: the DOM tree is such a snapshot when the input fields show the current data.

So, from a technical point of view, you can use both template-driven forms and reactive forms. In general, I prefer template driven forms because of the elegant and simple API. However, in this case, it felt right to use reactive forms.

Until now, we’ve simply displayed the result of the HTTP GET request. Now we’d like to make the address editable. This means that we want to add input fields, populate them, and add a “save” button updating the server side.

<form [formGroup]="addressform" novalidate>
  <dl>
     <dt>Street:</dt>
    <dd><input formControlName="street"></dd>
    <dt>Zip code:</dt>
    <dd><input formControlName="zipcode"></dd>
    <dt>City:</dt>
    <dd><input formControlName="city"></dd>
  </dl>
  <button (click)="save()">save</button>
  <br />
  Form currently stored in the BehaviorSubject: 
  {{userFormService.address$ | async | json}}
</form>

The last line represents the server side. The idea being that each time we send a POST request to the server, we also update the BehaviorSubject.

Let’s have a look at the controller class:

export class UserFormComponent {

  public addressform = new FormGroup({
    street: new FormControl(),
    zipcode: new FormControl(),
    city: new FormControl()
  });

  constructor(private userFormService: UserFormService) {
    this.userFormService.address$.subscribe(
      address => {
        if (address.street) { 
          // dirty hack checking for the empty default value
          this.addressform.setValue(address);
        }
      }
    )
  }
  
  public save(): void {
    const newAddress = {
      street: this.addressform.value.street,
      zipcode: this.addressform.value.zipcode,
      city: this.addressform.value.city
    };
    this.userFormService.save(newAddress);
  }

The constructor contains a dirty hack. The initial value of the BehaviorSubjet is simply an empty class. If we pass this empty class to the form, this would raise exceptions. So we check if the parameter is likely to match the Address interface. If it doesn’t, we know that we’re dealing with the default value, so we can silently ignore it.

Let’s have a look at the service. You already know most of it. The only thing that’s new is the save() method. But then, it’s plain vanilla code, too. All we have to do is to send the REST POST request and to update the Observable.

export class UserFormService {

  public address$: BehaviorSubject<Address> 
         = new BehaviorSubject<Address>({} as Address);

  constructor(private http: Http) {
    http.get("https://example.com/rest/user/address")
      .map(response => {
        this.address$.next(response.json() as Address);
      })
      .subscribe();
  }

  public save(newAddress: Address): void {
    // REST POST omitted for brevity
    this.address$.next(newAddress);
  }
}

Wrapping it up

Working with Observables makes the programming more abstract because we can never assume the data is there when we need it. However, in most cases, the BehaviorSubject makes the programming model almost as simple as working synchronously.

The next part of this series covers the topic I wanted to blog about in the first place. How to deal with multiple REST calls? For instance, how to implement multiple REST calls if your algorithm needs the result of both, or how to call two REST calls when the second REST call depends on the result of the first REST call?

Dig deeper

Angular University on using data encapsulated in an Observable

Leave a Reply

Your email address will not be published.