What The Facade?!
Are your components hundreds of lines long? Are your services bare? Have you ever copy and pasted a function from one component in to another? If you answered yes to any of these, stick around. Today we'll be taking a look at how the Facade design pattern can help us write more maintainable code in an Angular application.
What is the Facade Design Pattern?
The Facade design pattern is a Gang of Four structural design pattern. The objective of the design pattern per the Gang of Four is to, "Provide a unified interface to a set of interfaces in a subsystem. Facade Pattern defines a higher-level interface that makes the subsystem easier to use." How does this translate to Angular? Imagine we're creating a todo list app and that we've already created the backend/API and we now need to make the UI. We could just make the calls to the API directly from our components, or, we could create a service that handles the API calls, handles any complex business logic, and just gives our component the data it needs to handle the view.
What are the benefits of the latter? Well, for starters, our component/s will be cleaner. They'll only need to interact with the service vs interacting with the API directly. Another benefit is that we can reuse the same methods over and over again, and only define them once. A third benefit is that if we need to swap out our API, or make any changes to the business logic, we only have to make those changes in one place.
Angular + Facade Pattern, A Perfect Match
Angular advocates for keeping your components lean and outsourcing your business logic to your services. Per the Dependency Injection section of the docs, "Angular distinguishes components from services to increase modularity and reusability. By separating a component's view-related features from other kinds of processing, you can make your component classes lean and efficient."
The Angular Style Guide reinforces this concept by stating, "Do limit logic in a component to only that required for the view. All other logic should be delegated to services."
We can now see that the Facade design pattern isn't some obscure pattern or buzzword you can throw around the office to sound like you know what you're doing. It appears that the Angular team is pretty much advocating for its use. Sure, you can go and create behemoth components and make services an afterthought. While the Angular docs do state that Angular doesn't enforce this separation of view and business logic, they've given us a framework that allows us to easily implement the Facade pattern.
Facade Pattern in Action
Let's use the Free Dictionary API and create a component that displays the definition of a word.
Here is the dictionary component template:
<h1>Coolest Dictionary Ever</h1>
<form #form="ngForm" (submit)="submit()">
<label for="search-term">Search: </label>
<input
type="text"
id="search-term"
name="searchTermText"
[(ngModel)]="searchTerm"
/>
<button type="submit">Search</button>
<div *ngIf="definitionArr">
<h2>Definition:</h2>
<ul>
<li *ngFor="let definition of definitionArr">{{ definition }}</li>
</ul>
</div>
</form>
And here is the dictionary component class:
import { HttpClient } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { map } from 'rxjs/operators';
@Component({
selector: 'app-dictionary',
templateUrl: './dictionary.component.html',
styleUrls: ['./dictionary.component.css'],
})
export class DictionaryComponent implements OnInit {
constructor(private http: HttpClient) {}
ngOnInit() {}
searchTerm: string | null;
definitionArr: string[] | null;
submit() {
if (this.searchTerm) {
this.getDefinition();
}
}
getDefinition() {
this.http
.get(`https://api.dictionaryapi.dev/api/v2/entries/en/${this.searchTerm}`)
.pipe(
map((result) =>
result[0].meanings.map((meaning) => meaning.definitions[0].definition)
)
)
.subscribe((result) => (this.definitionArr = result));
}
}
As you can see, we've got a template-driven form. We display the definitions in the definitionArr
array, if any exist.
We have two methods in the class, submit
and getDefinition
. The submit
method is called when we submit the form by clicking the search button, which in turn calls getDefinition
. The getDefinition
method makes a call to the free dictionary API, then maps the result to the definitionArr
property.
The code works, that means we can call it a day...right?
Let's ask a couple of questions:
- What happens if we want to use the
getDefinition
elsewhere in the codebase? - How hard would it be to swap out the Free Dictionary API with a different API? Now imagine that we copy and pasted the
getDefinition
in a couple of other components and answer the question.
Let's Refactor
I'm going to create a dictionary service and move our business logic from the dictionary component to the dictionary service. Let's check it out:
We'll start with the dictionary component class:
import { Component, OnInit } from '@angular/core';
import { DictionaryService } from './dictionary.service';
@Component({
selector: 'app-dictionary',
templateUrl: './dictionary.component.html',
styleUrls: ['./dictionary.component.css'],
})
export class DictionaryComponent implements OnInit {
constructor(protected dictionaryService: DictionaryService) {}
ngOnInit() {}
searchTerm: string | null;
submit() {
if (this.searchTerm) {
this.dictionaryService.getDefinition(this.searchTerm);
}
}
}
This looks a lot slimmer. We are no longer importing the HttpClient
or the map
operator. The getDefinition
method is completely gone. We also see that the definitionsArr
property is gone. We've injected the newly created DictionaryService
and are using it in the submit
method.
Next, let's look at the template:
<h1>Coolest Dictionary Ever</h1>
<form #form="ngForm" (submit)="submit()">
<label for="search-term">Search: </label>
<input
type="text"
id="search-term"
name="searchTermText"
[(ngModel)]="searchTerm"
/>
<button type="submit">Search</button>
<div>
<h2>Definition:</h2>
<ul>
<li *ngFor="let definition of dictionaryService?.definitions$ | async">
{{ definition }}
</li>
</ul>
</div>
</form>
What's new here? Rather than iterating over the definitionsArr
property, we're now iterating over the dictionary service's definitions$
observable using the async pipe.
Now let's check out the new DictionaryService
:
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable({
providedIn: 'root',
})
export class DictionaryService {
constructor(private http: HttpClient) {}
private definitionsSubj = new Subject<string[]>();
definitions$ = this.definitionsSubj.asObservable();
getDefinition(searchTerm: string) {
this.http
.get(`https://api.dictionaryapi.dev/api/v2/entries/en/${searchTerm}`)
.pipe(
map((result) =>
result[0].meanings.map((meaning) => meaning.definitions[0].definition)
)
)
.subscribe((res) => this.definitionsSubj.next(res));
}
}
We were able to use most of the logic from the component's getDefinition
method. However, we're now calling next
on the definitionsSubj
Subject with the result. We've also created an Observable, definitions$
. This is the Observable that we are using in the template file with the async pipe.
With this refactor we'd be able to use the getDefinition
method anywhere in our app. Our dictionary component is cleaner. If we ever needed to swap out the Free Dictionary API with a different API, we'd only have to do that in one place. Our component should be easier to test. There's quite a few benefits that come along with the refactor.
Conclusion
If you're feeling like your components are too complex, maybe you've got the same method copy and pasted across several components, or maybe you're bringing in too many dependencies in your component which is making it harder to test and/or make changes to existing logic, it may be time to consider using the Facade pattern.
The example used today was just the tip of the iceberg. This pattern can be used to separate complex business logic from your component, make working with state management easier, handle communication with a backend service, and more.
If you want to check out the Stackblitz for this post, here's the link.
Thanks for reading.
Top comments (0)