Modular platform using Angular — Part 1

Saif Jerbi
6 min readDec 18, 2018

--

It’s my first article in medium so i will take the opportunity to introduce my self in few lines before starting.

I’m Saif Jerbi, i’m a frontend tech lead in a in-house framework team at Vermeg. I joined the team as a frontend developer at 2014, supporting an old made presentation layer based on Google Web Toolkit (GWT), after a short period we started a new project using new technology stack. We started using Angular and NodeJS. The plan was to implement a modular Web Integrated Development Environment (IDE) using modern web frameworks. In this article, i will share with you some architectural design and technical solution that we have implemented to deal with modularity in Angular and NodeJS.

An IDE is typically an application that should handle some development workspace via different plugins that are developed and plugged in at runtime. Think about vs-code, eclipse, intelliJ… and all the extensions that you install according to your needs.
To design an IDE you need to think in modularity, it’s a pluggable application that load and run modules (plugins) at runtime.
To learn modularity we will develop together a modular application like an IDE and provide an extension (plugin) that can be installed and loaded in the main application.

We will start with the main application that we will call “platform”, it will contain the plugins loader and plugins runner. Next, we will develop a plugin that displays some information that are injected by the platform. So you will checkout how this will be developed and loaded into our modular system.

This articles describe the different steps to develop a modular web application using Angular. We will focus on the frontend layer.
Part 1: The main modular application and the shared api
Part 2: The extension module to be deployed
Part 3: The extension loader mechanism using SystemJs loader

Let’s start the project:

You can skip all this steps by cloning this repository from Github.

// Let's generate the platfrom app
ng new platform
// move into generated application
cd platform
// add bootstrap & font-awesome
yarn add bootstrap font-awesome
// generate main component (entry point)
ng g c main
// generate layouts components
ng g c layouts/header
ng g c layouts/body
ng g c layouts/side-bar
ng g c layouts/footer
// generate a module to handle pluggins
ng g m plugins-management
// generate required components
ng g c plugins-management/available-plugins-displayer --export
ng g c plugins-management/plugins-container --export
// generate a plugin loader service
ng g s plugins-management/plugins-loader

Import the generated project into your preferred IDE and let’s start work.

Edit your angular.json file and add the required import of bootstrap and fontawesome files.

It’s time for coding

Let’s start preparing the global layouts components.

Put the code below into the layouts/header.component.html

Header component tempalte

The code below into for the layouts/side-bar.component

Put the code below into layouts/footer.component.html

Transform the body component to a container by updating the layouts/body.component :

Now, we will focus on the plugins-management components. First we will customize the plugins displayer.

An IDE plugin is a remote angular module that will be loaded at runtime into the platform main application. It has a name, a version, an icon and a remote deploy url.

To handle plugins we need to define what’s a plugin model in our application and what are the plugin model attributes. So we add a new interface that define a plugin under models/ide-plugin.model.ts

Ok, we are ready to focus on the plugin displayer.
Add the code below to available-plugins-displayer.component, don’t forget about css file to get the right look&feel

In this component we need an input that contains an array of ide-plugins to display and an event emitter in order to propagate the click to load action. So the component class should be as below:

Now go to the main component in the platform app: main/main.component.html and update the template with the code below:

In the app.component.ts you need to add the code below, and we will follow with router configuration in app.module.ts

Let’s focus on the sidebar

A loaded module should be added to the sidebar, so we need to push the selected plugin into the side bar component. In this case of behavior, generally, we add a service that contain a Subject. We push the required data into that Subject, and it will be broadcasted to all subscribers. In our case, we will do the same thing, but we will get inspired from the store implementation of @ngrx.

A store in @ngrx is a service that implement the Observer interface, so it implement a next() method. It contains a BehaviorSubject so all subscribers that comes after pushing the data, will get the latest cached one in the observable. So we will create a plugin store:

PS: don’t forget to provide the PluginStore into app.module.ts

Now we will use this store to fetch the different selected plugins and display them. So let’s update the side-bar.component and inject the store

PS: the filter(Boolean) pipe is used to filter the undefined data. The first value that will be pushed into this observable will be theinitialization value of the store’s behaviorSubject, which is “undefined”. So there are no need to let pass the undefined value. We can use also the skip(1) operator to skip the first emission.

Let’s update the main.component to push the selected plugins into the plugin store.

At this point all visual components are ready to embark the different plugins.

A modular application must perform some shared API to be used by the different extension plugins. The extensions must consume the API and be decoupled than the implementation of the API. So we will add new npm module that will contains only interfaces. It will be called “shared-api”. The platfrom app will implement this interfaces and will provide this implementation to the different plugin at runtime using the Dependency Injection mechanism.

You can skip all this steps by cloning this repository from Github.

// create new folder
mkdir shared-api
// move into this folder
cd shared-api
// init the new npm package
npm init
// add required dependencies
npm install --save typescript ts-node rxjs gts
// create new api folder, it will contains the api interfaces
mkdir api

Create new typescript file into the api folder named “file.api.ts”.

PS: Interfaces in angular can’t be injected, so we will create an abstract class instead of interface

Let’s assume that our shared API will contain only this method. We need to implement this interface in the platform. So we add this npm package as a dependency.

Our npm module is written with typescript, so it must be compiled and built.
Add this tsconfig file and run tsc command to compile and build this typescript files.

We will install it locally from disk path

// move to platform directory
npm install [PATH_TO_SHARED_API_PACKAGE_DIST] --save

Now, we add new folder to our platform application called shared-api-impl.
And we create new injectable service that will implement the fileApi interface.

We need to update the app.module.ts in order to provide this implementation for the specific api interface.

At this point, each component that will ask for the fileAPI will get the FileApiImpl as injected service. So we have decoupled the implementation from the declaration.

The FileApi interface (or abstract class) will be visible for each plugin application because it’s declared into separate npm package (shared-api). All plugins component/service can use this abstract classes and consume its different methods. The real implementation will be provided at runtime when the plugin module will be loaded in the core application (platform).

Now that we have finished the platform application and the shared-api, let’s move to the next chapter to create the plugin for our Web IDE.

--

--

Saif Jerbi

JavaScripter, Angular lover, Speaker and Senior frontend developer @vermeg