Synchronization with Domain Models in Angular Applications

On the plateaus of Ethiopia is where coffee was first grown. From there, Arab traders took the plant and started cultivating it in the Middle East. Yemen is where it is now believed they started drinking it. From the Middle East, it spread to different parts of North Africa, and to the rest of the world.

Today, coffee is grown in many places around the world, and through it, many families in Africa get their livelihood. Both small-scale family farms as well as large scale commercial establishments cultivate this crop for enjoyment in various parts of the globe.

When cellphones started becoming ubiquitous, one of the early uses for small scale farmers was discovering what the market price of the coffee was, before selling it to customers. These prices are determined at coffee exchanges (such as the Nairobi Coffee Exchange), similar to other commodity exchanges, where large quantities of the beans are traded at prices negotiated based factors such as the quality of the bean, the scarcity or the abundance of it, as well as the demand. Without the synchronization of prices to the price established at the coffee exchange, the market is opened up to arbitrage, where the farmer doesn't get a fair value for their produce, and the trader gets excessive profits from the blindness of the farmer.

In a previous blog post, I wrote about domain modeling in Angular applications, where the business domain with its common and understood terms and language. The purpose of this domain modeling is to make the code easier to understand and reason about, thereby making it more maintainable over the long run.

A potentially tricky situation can occur when one component initiates an action on the domain model, thereby changing the state of the domain model. If another component is simultaneously accessing this state, it also needs to be made aware of the change that has arisen.

For example, imagine we model the domain of a coffee exchange, where coffee is traded by buyers and sellers.

export class Trade {  
  constructor(private quantity: number, private individualPrice: number) {}

  getQuantity() {
    return this.quantity;
  }

  getPrice() {
    return this.individualPrice;
  }

  getTotalPrice() {
    return this.quantity * this.individualPrice;
  }
}

@Injectable()
export class Exchange {  
  private trades: Trade[] = [];

  makeTrade(quantity, individualPrice) {
    this.trades.push(new Trade(quantity, individualPrice));
  }

  getTotalValueTraded() {
    return this.trades.map(s => s.getTotalPrice()).reduce((sum, current) => sum + current);
  }
}

The Trade class models an individual trade, whereas the Exchange class models the trade activity of the organization.

These classes are then used in 2 components: a component to make a trade:

@Component({
  template: `
    <form>
      <label>Individual Price:</label>
      <input #price type="number" (keyup)="onPriceChanged(price.value)"  />

      <label>Quantity</label>
      <input #quantity type="number" (keyup)="onQuantityChanged(quantity.value)" />

      <button (click)="onSubmit(); false">Make Trade</button>
    </form>
  `,
  selector: `app-new-trade`
})
export class NewTradeComponent {  
  private indidivualPrice: number;
  private quantity: number;

  constructor(private exchange: Exchange) {}

  onPriceChanged(price) {
    this.indidivualPrice = parseFloat(price);
  }

  onQuantityChanged(quantity) {
    this.quantity = parseFloat(quantity);
  }

  onSubmit() {
    this.exchange.makeTrade(this.quantity, this.indidivualPrice);
  }
}

and a component that displays the total value of trades:

@Component({
  template: `
    Total Trades: <b>{{ totalTrades }} KES</b>
  `,
  selector: `app-total-trades`
})
export class TotalTradesCounterComponent {  
  private totalTrades: number;

  constructor(private exchange: Exchange) {
    this.totalTrades = exchange.getTotalValueTraded();
  }
}

These components happen to be displayed at the same time. (Yes, I know it would be better to pass the values as inputs into the component and events emitted from the component; and I know that having various child components accessing central state makes for hard to understand code, but bear with me for the purpose of this example).

Now, when the NewTradeComponent initiates a new trade, this isn't picked up by the TotalTradesCounterComponent, because a primitive type of number is returned when initially retrieved. So what can we do to rectify this? Enter observables.

Observables are things that emit values. Other things subscribe to the values that these observables emit. You can find a better and more comprehensive example here.

How would we change our code to cater for this? Firstly, change the Exchange class to return an observable instead of an actual value as follows:

import { Observable } from 'rxjs/Observable'  
import { Observer } from 'rxjs/Observer'

@Injectable()
export class Exchange {  
  private trades: Trade[] = [];
  private totalSalesObserver: Observer<number>;
  private totalSalesObservable: Observable<number>;


  makeTrade(quantity, individualPrice) {
    this.trades.push(new Trade(quantity, individualPrice));
    this.totalSalesObserver.next(this.calculateTotalTrades());
  }

  getTotalValueTraded() {
    if (!this.totalSalesObservable) {
      this.totalSalesObservable = Observable.create(observer => {
        this.totalSalesObserver = observer;
      });;
    }

    return this.totalSalesObservable;
  }

  private calculateTotalTrades() {
    return this.trades.length > 0 ?
      this.trades.map(s => s.getTotalPrice()).reduce((sum, current) => sum + current) :
      0;
  }
}

The getTotalValueTraded method now creates and returns a new Observable which can be subscribed to. The Observer is retrieved when creating the Observable. This will be used to emit various values to the subscribers. A check is done to ensure one instance of the observable and observer are created, thereby ensuring that all subscribers remain 'n sync like a boy band (BOOM!). (For simplicity, different implementations of the Observer such as BehaviorSubject were not used here). The calculation of the total trades has been moved to a helper method for readability.

Whenever a new trade is made, the Observer.next function is called, which then emits the new total trades to all subscribers to getTotalValueTraded.

The TotalTradesCounterComponent component is then adjusted as follows:

@Component({
  template: `
    Total Trades: <b>{{ totalTrades }} KES</b>
  `,
  selector: `app-total-trades`
})
export class TotalTradesCounterComponent {  
  private totalTrades: number;

  constructor(private exchange: Exchange) {
    this.exchange.getTotalValueTraded().subscribe(t => {
      this.totalTrades = t;
    });
  }
}

In subscribing to getTotalValueTraded, whenever a new value is emitted, the totalTrades is then updated to match this new value. This way, the different components that depend on the Exchange domain entity are now synchronized to its state. Boom.

Patrick Kayongo

I create and maintain software. Pan-African.

Johannesburg, South Africa