Functional Reactive Programming with Angular and Sodium

Functional reactive programming (FRP) is a variant of reactive programming for the development of user interfaces based on the functional paradigm and a strict set of basic operators. In contrast to reactive frameworks, such as RxJs, using FRP enables a developer to define a pure area in her code in which some error classes, typical for event-based architectures, do not occur. Sodium is an FRP-framework, which is independent of a specific GUI-framwork and supports several different programming languages. Here, we describe how to use Sodium together with Angular.

As simple example we implemented a tiny calculator. It can compute a sequence of plus and minus operations on integer numbers. The code is available here.
picture of calculator
One nice feature of Angular is its support for modular components. To investigate how well the Angular components behave with Sodium, we divided the calculator into 14 different components, one for each button and one for the display field.
The core architectural concept of Sodium consist of cells to store state and streams to propagate events. These two elements are used to connect the components.
Architektur-Ist.png

Every time a digit button is pressed, the corresponding number is emitted as event. Thus each digit button component exports a stream of numbers. The operator buttons emit events without any additional information. An object without information is modeled with the Unit type. The application does the computation itself. It exports the value in the display field as a cell.

The components are combined in the user interface part of the app component. Each component type is used with its custom HTML tag. To distinguish them later, each gets its own id.

snap1

To access the streams and cells of a component, its controller is injected with the @ViewChild annotation using the id of the component.

export class AppComponent implements AfterViewInit {
  @ViewChild('digit1') digit1B: DigitButtonComponent;
  @ViewChild('digit2') digit2B: DigitButtonComponent;
  @ViewChild('digit3') digit3B: DigitButtonComponent;
  ...
  @ViewChild('digit0') digit0B: DigitButtonComponent;
  @ViewChild('display') displayF: DisplayFieldComponent;
  @ViewChild('plus') plusB: OperationButtonComponent;
  @ViewChild('minus') minusB: OperationButtonComponent;
  @ViewChild('compute') computeB: OperationButtonComponent;
  ...
}

With this approach a complex application can be divided in sub components. The interface to the components consists of clean FRP elements which can be accessed using standard Angular injection. This ensures that the whole application logic is free of the above mentioned error classes.

The logic for the computation itself is split into two parts. One part consists of the datatype to store the state of the calculator and functions to manipulate it. This is implemented in plain Typescript using immutable classes and functions. The other part consists of the handling of the events. This is done with Sodium.

The wiring of the cells and stream must be done after all the components are initialized. Thus, it has to be done in the ngAfterViewInit() callback:

ngAfterViewInit() {
  this.displayF.displayC = Transaction.run(() => {
   const statusC = new CellLoop();
   const updatedStateS = this.wireDigitAndOperatorStreams(statusC);

   statusC.loop(
      updatedStateS.hold(
        new CalculatorState(0, 0, 0, Operator.None)));
   const displayC = statusC.map(status => status.display);

   return displayC;
  });
}

The state of the calculator is stored in a cell statusC. Based on the incoming events, it is updated. To implement such a loop in Sodium, a CellLoop is needed. The stream updatedStateS consist of the updated states based on the streams of the components and the current state of the calculator. With the loop method the state is initialized with zeros and updated with every new event in updatedStateS.

The value in the display is just a member of the calculator state. The cell displayC is thus calculated by mapping the state to its display member. Sodium has a transaction mechanism which must be used for cell loops.

To give a glimpse of the stream handling and how to access a subcomponent, we show the processing of the digit buttons. Each digit button component exports a stream of a constant number. The first step is to combine all the streams with the simple orElse. The resulting stream consist of the pressed numbers.

 combineDigitStreams(): Stream {
    return this.digit0B.stream
      .orElse(this.digit1B.stream)
      .orElse(this.digit2B.stream)
      .orElse(this.digit3B.stream)
      .orElse(this.digit4B.stream)
      .orElse(this.digit5B.stream)
      .orElse(this.digit6B.stream)
      .orElse(this.digit7B.stream)
      .orElse(this.digit8B.stream)
      .orElse(this.digit9B.stream)
      .orElse(this.digit0B.stream);
  }

We will show the remaining part of the logic in another article. If you can not wait, just check out the code. Here, we focus on the integration of Sodium in Angular.

So far we have shown how to integrate Sodium in a modular approach with Angular components. What is left is the integration of the “pure” FRP code with the “unpure” concrete UI framework, in this case the HTML layer. In our example there are two integration points: the click event of the buttons and the presentation of the display field of the calculator.

The click event of the digit button is delegated to a Typescript function in the controller using standard Angular mechanisms.

{{digit}}

In the controller function, a Sodium StreamSink object is used to bridge into the pure FRP area. The displayed digit is send as event through the exported stream.

onClick() {
    this.streamSink.send(this.digit);
}

The presentation of the display field can be done in several ways. We decided to build on the asynchronous approach based on the included RX in Angular. Using this approach a change is pushed directly to the presentation layer and Angular does not need to pull the underlying model for changes. This is the corresponding code in the UI layer:


In the controller we have to bridge the Sodium cell to an RX observer. To do this we need a BehaviorSubject to send events each time the display cell changes. Sodium provides a utility class Operational to interface the pure FRP area with the outside world.

export class DisplayFieldComponent {

  private behaviorSubject = new BehaviorSubject(0);
  display = this.behaviorSubject.asObservable();

  set displayC(cell: Cell) {
    Operational.updates(cell).listen( num => {
      this.behaviorSubject.next(num);
    });
  }
}

This is all. With this architecture the UI logic of an Angular based application can be implemented using the FRP framework Sodium. This can provide a more maintainable code in comparison with the usage of the included RX framework. We still have to wait for more experiences from our real world projects to put this hypothesis on a sound empirical fundament.

Discover more from akquinet - Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading

WordPress Cookie Notice by Real Cookie Banner