BLoC design pattern with Angular

Learn new design pattern born in Flutter / AngularDart.

Japanese edition (original):
https://lacolaco.hatenablog.com/entry/2018/05/22/194805

In this article, I introduce a method to understand and implement the BLoC pattern which is spreading as a new component design pattern in the context of AngularDart / Flutter by Angular vocabulary.

What is BLoC pattern?

BLoC stands for Business Logic Component. The application implementation pattern of using BLoC is called BLoC pattern.

First of all, as a misunderstanding point, this “Component” is not a “ component “ that builds views like React and Angular because it is “Component” meaning a set of elements that make up an application as a general word. As a level to be contrasted, it becomes like “UI Component” vs “Business Logic Component”.

BLoC is like a refactoring guideline to increase code share coverage when developing applications for multiple environments. Specifically, it gives the following guidelines.

  • BLoC’s input and output interfaces are all Stream / Sink
  • BLoC’s dependencies are always injectable and environment independent
  • There is no conditional branch for each environment in BLoC
  • Implementation is free as long as it complies with the above rules

For details , see this session which is the first appearance of the BLoC pattern.

BLoC pattern in Angular

There is room for discussion about the benefits of BLoC pattern in Angular, but important thing for sustainable Angular application development is dividing the parts that do not depend on the framework. The BLoC pattern seems to be a good rule for keeping the Angular dependency of the application proper, even though it is considered use the server-side rendering, NativeScript, Ionic or replacing to React / Vue.

Now, let’s implement BLoC with Angular. Dart has language standards Stream and Sink, but since it does not exist in JavaScript , non-standard implementation is necessary. Fortunately Angular is interoperable with RxJS, so you can implement BLoC with RxJS Observable as a Stream.

First of all, an example of a state in which the UI component has business logic is given below.

@Component({
selector: 'my-app',
template: `
<div cdkTrapFocus [cdkTrapFocusAutoCapture]="false">
<mat-form-field appearance="outline" style="width: 80%;">
<input
matInput
placeholder="Search for..."
ngModel (ngModelChange)="onInputChange($event)">
</mat-form-field>
</div>
<span> {{preamble}} </span>
<ul>
<li *ngFor="let result of results">
{{ result }}
</li>
</ul>
`,
})
export class AppComponent {
private query = '';
results: string[] = [];
get preamble() {
return this.query == null || this.query.length == 0 ? '' : `Results for ${this.query}`;
}
constructor(private repository: SearchRepository) {}
onInputChange(query: string) {
this.query = query;
this.executeSearch(query);
}
private async executeSearch(query: string) {
const results = await this.repository.search(query);
this.results = results;
}
}

Since the UI component has various business logic such as calling API and holding state , if you want to expand this application to another platform, you can not share the code.

Create a BLoC

In consideration of portability, BLoC is mostly declared as a simple class. Here we create a class, SearchBloc. When moving the all business logic that AppComponent originally had, SearchBloc becomes as follows:

class SearchBloc {
private query = '';
results: string[] = [];
get preamble() {
return this.query == null || this.query.length == 0 ? '' : `Results for ${this.query}`;
}
constructor(private repository: SearchRepository) {
}
async executeSearch(query: string) {
this.query = query;
const results = await this.repository.search(query);
this.results = results;
}
}

And AppComponent depending on SearchBloc on is as follows;

@Component({
selector: 'my-app',
template: `
<div cdkTrapFocus [cdkTrapFocusAutoCapture]="false">
<mat-form-field appearance="outline" style="width: 80%;">
<input
matInput
placeholder="Search for..."
ngModel (ngModelChange)="bloc.executeSearch($event)">
</mat-form-field>
</div>

<span> {{ bloc.preamble }} </span>

<ul>
<li *ngFor="let result of bloc.results">
{{ result }}
</li>
</ul>
`,
})
export class AppComponent {
bloc: SearchBloc;

constructor(private repository: SearchRepository) {
this.bloc = new SearchBloc(this.repository);
}
}

Refactoring to use Observables

As mentioned above, in the BLoC pattern, all interfaces of BLoC must be Stream. This is because the approach of UI’s reaction to data change is different between Flutter’s Stateful widget and AngularDart’s Change Detection. In synchronous state management, special processing is required for each platform.

Meanwhile Flutter if it is a Stream, StreamBuilder has a mechanism for redrawing each time a flowing data from Stream. And AngularDart also have a similar reaction mechanism by a async pipe. To draw an asynchronous value independent of platform, Dart ‘s BLoC pattern uses Stream.

In the case of Angular, RxJS helps implement BLoC.

Replacing Dart ‘s Stream /Sink with Observable / Observer, SearchBloc becomes as the following:

class SearchBloc {
private _results$: Observable<string[]>
get results$(): Observable<string[]> {
return this._results$;
}
private _preamble$: Observable<string>
get preamble$(): Observable<string> {
return this._preamble$;
}
private _query$ = new BehaviorSubject<string>('');
get query(): Observer<string> {
return this._query$;
}
constructor(private repository: SearchRepository) {
this._results$ = this._query$
.pipe(
switchMap(query => observableFrom(this.repository.search(query)))
);
this._preamble$ = this.results$.pipe(
withLatestFrom(this._query$, (_, q) => q ? `Results for ${q}` : '')
);
}
dispose() {
this._query$.complete();
}
}

results: string[] becomes to results$: Observable<string[]>` , preamble: string also becomes topreamble$: Observable<string>.
These are expressed as asynchronous values ​​that change in response to changes in query.

query is exposed asObserver<string> to the outside, to allow the addition of new value to the UI. Within SearchBloc, we have _query$: BehaviorSubject<string> as the stream source, and the constructor declares _results$ and _preamble$` to respond to _query$.

When used from AppComponent , it becomes as follows. Using an async pipe in the template, redrawing the view will be performed in response to changes in Observable.

@Component({
selector: 'my-app',
template: `
<div cdkTrapFocus [cdkTrapFocusAutoCapture]="false">
<mat-form-field appearance="outline" style="width: 80%;">
<input
matInput
placeholder="Search for..."
ngModel (ngModelChange)="bloc.query.next($event)">
</mat-form-field>
</div>
<span> {{ bloc.preamble$ | async }} </span>
<ul>
<li *ngFor="let result of bloc.results$ | async">
{{ result }}
</li>
</ul>
`,
})
export class AppComponent {
bloc: SearchBloc;
constructor(private repository: SearchRepository) {
this.bloc = new SearchBloc(this.repository);
}
ngOnDestroy() {
this.bloc.dispose();
}
}

This completes BLoC implementation.

Consideration

  • If you are designing and implementing an Angular application based on RxJS, it should look like nature.
  • BLoC is a name the pattern and imposes interface constraints on business logic classes.
  • Angular ‘s component also plays a role of supplying the dependency in Angular environment using DI.
  • Maybe, A component with BLoC is a Container , and other hand is Presentational.
  • Since UI components are hard to test, it is one of the visible benefits of increasing testability by escaping business logic to BLoC.

Summary

  • BLoC is a design pattern to promote code sharing in cross platform development.
  • Born for code sharing at Flutter / AngularDart, but it is not just for Dart.
  • With Angular, it can be implemented using Observable of RxJS.
  • Even with a single platform of only Angular, you can improve the testability of business logic.