Start prototyping with minimal changes to your application at integration time with Angular Injectables
This article was originally published on Bits and Pieces by Giancarlo Buomprisco
I am one of those developers who is usually fairly impatient when the Product Manager unveils the new product’s features that I can finally start working on.
After the sprint planning, I ask the dreaded question:
… is the API ready yet?
… and the answer is often times negative. Sometimes your backend colleagues are busier with other things, and can’t provide even a mock endpoint. No fear, though!
Provided you have a viable DTO interface your back-end colleague agrees with, you can start prototyping the new features with minimal changes to your application at integration time with the real API endpoints.
In order to minimize the changes needed during the integration, we will leverage the power of Angular’s Injectables and will work on both the API clients at the same time:
one is the mocked version, which we will call MockPriceApiService
the other is the actual service that will be shipped with the application when the API will be ready to be consumed, which we call PriceApiService
the two classes will implement an interface to establish an API contract between the two
A quick tip: Use Bit (Github) to share, reuse and update your Angular components across apps**. Bit track and bundles reusable components in your projects, and exports them encapsulated with their dependencies, compilers and everything else. Components can then be installed with package managers, and even updated right from any new project. Give it a try.
Yet again (!), I will build a minimal live cryptocurrency application that will work with both mocked prices and with an actual service that will pull prices from Coincap.
If you’re impatient or just TLDR; you can see the final result on Stackblitz!
The API Interface
First thing, we want to establish which API the two classes will share. So, we build an interface called ApiService
import { Observable } from 'rxjs';
export interface PriceApi {
getPrice(currency: string): Observable<string>;
unsubscribe(): void;
}
As you can see, the services that will implement this interface will only need to provide two public methods:
one to subscribe to a stream of prices
the other to unsubscribe the stream.
Mock Service
Let’s start by creating the mock service. As we have said before, we only need to implement two methods to respect the contract. In short, what we want to do is:
get a stream of random numbers (prices), and we will base this off a set of base prices we use as a reference to make the numbers close to the real ones
unsubscribe said stream
The app we will create will only support 3 currencies: Bitcoin, Litecoin and Ethereum.
const BASE_PRICES = {
bitcoin: 11000,
litecoin: 130,
ethereum: 300
};
const randomize = (price: number) => {
return (Math.random() * 2) + price;
};
@Injectable()
export class MockPriceApiService implements PriceApi {
static latency = 250;
private unsubsciptions$ = new Subject();
public getPrice(currency: string): Observable<string> {
return timer(0, MockPriceApiService.latency).pipe(
delay(1000),
map(() => BASE_PRICES[currency]),
map((price: number) => randomize(price)),
map((price: number) => price.toFixed(5)),
takeUntil(this.unsubsciptions$),
)
}
public unsubscribe() {
this.unsubsciptions$.next();
}
}
Let’s explain the method getPrice:
we receive a parameter called currency which represents the name of the asset subscribed
we create a timer that will emit an event every 250ms
we delay the emitted observable by 1 second to mimic a real network latency
we take the BASE_PRICE of the selected currency and return a number that gets randomized at each emission
we unsubscribe the stream when the subject unsubscriptions$ emits
Real Live-Prices Service
As we said before, your team may take a while before a real API is provided to you to implement in your front-end.
If your team already agreed on the interface, though, there is nothing stopping you from implementing the API beforehand and build the real service alongside the mock service.
In order to complete our example, we will build the real service and take prices from Coincap’s Service (thank you again!) and use a WebSocket to stream prices to our client.
The two public methods will:
create a WebSocket connection and stream prices using an Observable, respecting the contract in the interface
close the connection when the method unsubscribe is called. As a result, the prices stream will stop emitting prices.
const WEB_SOCKET_ENDPOINT = 'wss://ws.coincap.io/prices/';
@Injectable()
export class PriceApiService implements PriceApi {
private webSocket: WebSocket;
public getPrice(currency: string): Observable<string> {
return this.connectToPriceStream(currency);
}
public unsubscribe() {
this.webSocket.close();
}
private connectToPriceStream(asset: string): Observable<string> {
this.createConnection(asset);
return new Observable(observer => {
const webSocket = this.webSocket;
webSocket.onmessage = (msg: MessageEvent) => {
const data = JSON.parse(msg.data);
observer.next(data[asset]);
};
return {
unsubscribe(): void {
webSocket.close();
}
};
});
}
private createConnection(asset: string) {
if (this.webSocket) {
this.webSocket.close();
}
this.webSocket = new WebSocket(
WEB_SOCKET_ENDPOINT + `?assets=${asset}`
);
}
}
Consuming the Service’s Data
Now, let’s show how to actually consume the data streamed by any of the two services we created.
Let’s create a component that injects PriceApiService:
we create a property to which we assign the stream, which we call price$
when the user changes cryptocurrency, we call the method unsubscribe, unless the currency has never been selected
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent {
public price$: Observable<string | undefined>;
public currency$ = new BehaviorSubject<string | undefined>(undefined);
constructor(private api: PriceApiService) { }
ngOnInit() {
this.price$ = this.currency$.pipe(
mergeMap((currency: string | undefined) => {
return currency ? this.api.getPrice(currency) : of(undefined);
})
);
}
onCryptoSelected(currency: string) {
if (this.currency$.value) {
this.api.unsubscribe();
}
this.currency$.next(currency);
}
}
And now on to the template:
we display the price
if the price is still undefined, but currency has been selected, it means a subscription is still in progress: as a result, we show to the user a loading message
if the currency hasn’t been selected, we show an empty message
<div class='mt-5'>
<crypto-selector (selected)="onCryptoSelected($event)"></crypto-selector>
<div class='price'>
{{ price$ | async }}
</div>
<ng-container *ngIf="(price$ | async) === undefined && (currency$ | async)">
<div class='alert alert-info mt-2'>
Awaiting for Price...
</div>
</ng-container>
<ng-container *ngIf="!(currency$ | async)">
<div class='alert alert-warning mt-2'>
No Crypto Subscribed Yet
</div>
</ng-container>
</div>
Switching Services using Dependency Injection
Here is the magic part. Thanks to Angular’s Dependency Injection, we can provide which service we want to use by passing to it the type and the actual class.
As you can see below, we instruct the DI:
when the user injects PriceApiService
use the following class: if useMocks is truthy, use MockPriceApiService, otherwise, use PriceApiService.
// in a real app could be environment.useMocks ?
const useMocks = true;
@NgModule({
imports: [BrowserModule, CommonModule],
declarations: [AppComponent, CryptoSelectorComponent],
bootstrap: [AppComponent],
providers: [
{
provide: PriceApiService,
useClass: useMocks ? MockPriceApiService : PriceApiService
}
]
})
export class AppModule { }
Of course, while your real endpoints are still being built, your prototype will continue using MockPriceApiService for as long as it’s needed.
Once your team is ready to switch to the real endpoints, all you’ll have to do is switch the environment variable to false (which could be set in your CI) and your app will automatically work with the real endpoints!
Angular’s DI
Angular’s Dependency Injection is very powerful.
In addition to useClass, you can also use:
useValue: for example, if you want to pass a number, a string, etc.
useFactory: for example, when you want to pass a function and make use of the DI to inject dependencies
And you can also use InjectionToken to pass different values other than classes.
For a complete reference about dependency injection, I’d suggest you read the full documentation at Angular.io.
Result
You can see the final result on Stackblitz!
Final Words
The Dependency Injection is a powerful tool, and can be used in a variety of useful ways:
prototyping
feature flags
mocking services for tests
general configuration
Learning how to master it can improve your Angular projects’ architecture and can allow patterns you didn’t know where even possible.
If you need any clarifications, or if you think something is unclear or wrong, please leave a comment!
If you enjoyed this article, follow me on Medium or Twitter for more articles about Angular, RxJS, Typescript and more!
Top comments (0)