How to split monolith app with Angular 6

Let`s imagine we have a application where squirrels jump in the forest, seek nuts and gather them into one big Bucket. One day customer come and say this is great app and we have another app with hedgehogs and we want that Big Bucket for hedgehogs too. One way to implement is — copy and paste. But bucket can be BIG and changes to bucket in one application will not be applied to bucket in another one, so the solution is — split part of the application 1 to separate bundle and attach it to both applications, even more — with strongly typed abstractions you can share your bucket with the World and everyone can place the Bucket in its own zoo.

So here is small example.

The goal: Create a BucketWidget, that is reusable between animal apps and can display A FOOD.



ng new squirrelApp --prefix sqrl ng generate library bucketLib --prefix bckt

ng generate application hedgehogApp --prefix hdg

Angular CLI 6 will create a project, then generate a library and add another application in workspace.

projects structure

We can specify ports for apps:

port for squirrel app

port for hedgehog app

Now both apps can be started with

ng serve hedgehogApp

ng serve squirrelApp

Lets create our Library API

Library has 3 packages:

model — Interface for data that will be rendered in bucket component

service — Interface of service that will provide model

view — Components that will render model

export interface Food {

name: string;

image: string;

}

service — let the app provide real implementations

import {Observable} from 'rxjs';

import {Food} from '../model/models';

import {InjectionToken} from '@angular/core';



export const FOOD_PROVIDER: InjectionToken<FoodService> = new InjectionToken<FoodService>('food.service');



export interface FoodService {

getFood$(): Observable<Food[]>;

}

component — food provider is injected, works only with abstractions, real data will be injected by the Apps

import {Component, Inject, OnInit} from '@angular/core';

import {FOOD_PROVIDER, FoodService} from '../service/food.service';

import {Observable} from 'rxjs';

import {Food} from '../model/models';



@Component({

selector: 'bckt-bucketLib',

template: `

<div>A Bucket</div>

<div *ngFor="let foodItem of food | async">

<div>{{foodItem?.name}}</div>

<img [src]="foodItem?.image">

</div>

`,

styles: []

})

export class BucketLibComponent implements OnInit {

food$: Observable<Food[]>;



constructor(@Inject(FOOD_PROVIDER) private foodService: FoodService) {

}



ngOnInit() {

this.food$ = this.foodService.getFood$();

}



}

Now we need a way to let application provide concrete implementations of FoodService. Let`s define module with providers.

@NgModule({

imports: [

CommonModule

],

declarations: [BucketLibComponent],

exports: [BucketLibComponent]

})

export class BucketLibModule {

static forRoot(providers: Provider[]): ModuleWithProviders {

return {

ngModule: BucketLibModule,

providers

};

}

}

— public API: view+model abstraction+service abstraction

export * from './lib/service/food.service';

export * from './lib/model/models';

export * from './lib/bucket-lib.module';

The last part. Use our library.

ng build bucketLib --prod

After build our library, we can test it creating symlink.

cd dist/bucket-lib

npm link

then back to project root and

npm link bucket-lib

node_modules

our library was added as symlink and is ready to use.

SquirrelApp

If lib was published to repo then install it (in case of symlink — its already in node_modules)

npm install bucket-lib

In Squirrel app we created NutsProvider which implements FoodProvider and provide… nuts :)

import {Injectable} from '@angular/core';

import {Food, FoodService} from 'bucketLib';

import {Observable, of} from 'rxjs';



@Injectable({

providedIn: 'root'

})

export class NutsProviderService implements FoodService {



constructor() {

}



getFood$(): Observable<Food[]> {

return of([{

name: 'Squirrel found a Nut!',

image: 'https://vignette.wikia.nocookie.net/mystiqueisland/images/6/68/Acorn_Body.png/revision/latest?cb=20130623190324'

}]);

}

}

and wire Bucket with NutsProviderService in the app.module

import {BrowserModule} from '@angular/platform-browser';

import {NgModule} from '@angular/core';



import {AppComponent} from './app.component';

import {BucketLibModule, FOOD_PROVIDER} from 'bucketLib';

import {NutsProviderService} from './service/nuts-provider.service';



@NgModule({

declarations: [

AppComponent

],

imports: [

BrowserModule,

BucketLibModule.forRoot([

{

provide: FOOD_PROVIDER,

useClass: NutsProviderService

}

])

],

providers: [],

bootstrap: [AppComponent]

})

export class AppModule {

}

Hedgehog App

The same steps:

Create ApplesProvider as a FoodService and provide apples to the Bucket

import {Injectable} from '@angular/core';

import {Observable, of} from 'rxjs';

import {Food, FoodService} from 'bucketLib';



@Injectable({

providedIn: 'root'

})

export class ApplesProviderService implements FoodService {



constructor() {

}



getFood$(): Observable<Food[]> {

return of([{

name: 'Hedgehog found an apple',

image: 'https://images.pexels.com/photos/102104/pexels-photo-102104.jpeg?auto=compress&cs=tinysrgb&h=350'

}]);

}

} @NgModule({

declarations: [

AppComponent

],

imports: [

BrowserModule,

BucketLibModule.forRoot([

{

provide: FOOD_PROVIDER,

useClass: ApplesProviderService

}

])

],

providers: [],

bootstrap: [AppComponent]

})

export class AppModule {

}

Thats it! The last part is just add bucket to the app:

And as result:

Two apps share the same piece of Functionality

So, conclusions: with Angular 6 we can easily move parts of monolith application logic to separate shared packages which can be used across multiple applications.

PS: during writing, no animals were harmed

PPS: GitHub

PPPS: highly recommended as high level architecture view http://atomicdesign.bradfrost.com/chapter-2/