Implementing a Plugin Architecture with Angular and OpenLayers

Share on facebook
Share on google
Share on twitter
Share on linkedin

As an Angular app is growing, it becomes more important to care about the design of the app. I have previously written about how to apply the adapter pattern in Angular apps and how to refactor Angular apps, but this is a more general topic about a technique to scale the app without adding more complexity, using what is called a plugin architecture.

What does having a plugin architecture in Angular mean?

Having a plugin architecture means that you have a concise core that can be extended through plugins. In Angular context, the core is the parent component and the plugins are child components, that is extending the parent’s functionality and is responsible for its own encapsulated logic and its corresponding lifetime management using Angular lifetime hooks. The beauty of the plugin architecture is that the base is unaware of the plugins, but only the plugin knows about the base. This is making it easy to extend the parents functionality without modifying the parent’s code. This is related to segregation of responsibilities principle (plugins have it’s own encapsulated logic), open/close principle (unmodified core and extending child components when adding new functionality) and dependency inverse principle (child knows about parent) from the SOLID principles.

How to implement a plugin architecture in an Angular app that uses OpenLayers

A good example of a use case for using a plugin architecture is if you are working on an app that uses a map and you want that map to be extended with different kind of map features. You want these map features to be plugins to the map to obtain the above-mentioned benefits of a plugin architecture. I have created a demo on my Github of how to create a plugin architecture using Angular and OpenLayers. OpenLayers is an open source javascript library for working with maps.

Creating a plugin architecture using Angular components

For creating a plugin architecture with Angular components you simply need to do these two things:

  1. Make the parent support plugins by using ng-content in the parent’s template
  2. Make the plugin be inner content to the parent component such that it can reference its parent using Angular dependency injection

The first one is accomplished simply by allowing the parent to render the content in its inner Html and the second is accomplished by leveraging that Angular supports accessing parent components using the DI engine. When the child has a reference to the parent, it can extend the parent behavior, eg. by applying a new map feature to the parent map.

Creating the OpenLayers wrapper component

Similar to my post about using the adapter pattern in Angular apps, we want to create an adapter for the OpenLayers wrapper to ensure that it has a simple interface that is wrapping the logic of working with the OpenLayers map in a convenient way for the application, making it easy to change as you only need to change the adapter to affect all usages of the map.

The map parent is set up using an Angular wrapper for OpenLayers called ngx-openlayers. This plugin already implements the plugin architecture according to the guidelines I’m preaching in this post, but for demonstration purpose, we are gonna create an extra plugin for displaying and controlling drawing controls on the map.

Our custom map plugin is called app-draw-controls and is a child of aol-source-vector, which is creating a data source to the map for applying drawings. Bear in mind, that this is possible because all the parent components of app-draw-controls has a template containing ng-content, getting the content of the plugin component passed down the components tree.

Creating a plugin to the map

For creating a plugin to the map, we want the plugin to get a reference to the map component, so it can extend it with map features. In Angular, there is a neat way to access a parent component by simply inject the parent in the plugin components constructor. By doing this Angular will search up the DOM tree until it finds a matching parent. When we have access to the base map, we can start adding new functionality to it.

We want to create buttons for controlling the drawing of points and polygons:

These are styled as:

For implementing the actual plugin component, we inject the MapComponent parent for accessing the OpenLayers map instance. When we have access to the parent map instance, we can now plug in new functionality into the map, such as draw point and polygon buttons, which are getting activated on click:

That way we can easily add new functionality to the map and because these are child component, we also ensure proper handling of the component life cycle such as onInit and onDestroy events.

Now we can run the app with the draw map feature:

Using the router-outlet to inject plugins

Image, you have a map-based app with lots of different map features. You don’t want the parent map to load every time and you might not want to have the map features being “hardcoded” in the template. Instead, you want to keep the parent map and “swap” the map features based on the current route. This can be done by having the parent map as the parent route /map using the router-outlet in the inner map component and set up the correct router configuration for inserting the map features (plugins) under /map/*map-feature*.

We create the following navigation to change between basic map features and a map with the drawing buttons we just created:

We create the root routing as:

Each map feature provide its own routing, which is a sub-route of map:

And finally the map.component is injecting map features using router-outlet:

Everything else stays the same!

Now we should be able to toggle the map features dynamically using the routing:

Plugging out (pun intended)

In this post we looked at the architectual benefits of using a plugin architecture for ensuring a maintainable and scalable architecture of your Angular apps. In Angular a plugin architecture is ensured by following two simple steps: have parent component allow rendering of inner components by using ng-content and by plugging the child component, which contains a features extending the parent by getting a reference to the parent component using Angular dependency injection.

We looked at a practical implementation of how to use this with the map library OpenLayers for adding new map features without modifying the map core. We created a plugin architecture by injecting the plugin component into the inner HTML of the parent tag and later we looked at how to inject map features into the map using router outlet, allowing the route to set the map features without reloading the parent map.

If you liked this post make sure to comment, follow me on Twitter and subscribe for weekly posts about how to become a better Angular developer.

Do you want to become an Angular architect? Check out Angular Architect Accelerator.

Related Posts and Comments

How to Handle Errors in a Reactive Angular App

In this post, we will cover how to handle errors in a reactive Angular app. To provide a good user experience you should always let your users know what state your application is in. That includes showing a loading spinner when it’s loading and showing error messages if there are any errors. It is a

Read More »

How to Set Up Git Hooks in an Nx Repo

Git hooks can be used to automate tasks in your development workflow. The earlier a bug is discovered, the cheaper it is to fix (and the less impact it has). Therefore it can be helpful to run tasks such as linting, formatting, and tests when you are e.g. committing and pushing your code, so any

Read More »

The Stages of an Angular Architecture with Nx

Long gone are the times when the frontend was just a dumb static website. Frontend apps have gotten increasingly complex since the rise of single-page application frameworks like Angular. It comes with the price of increased complexity and the ever-changing frontend landscape requires you to have an architecture that allows you to scale and adapt

Read More »

The Best Way to Use Signals in Angular Apps

Since Angular 16, Angular now has experimental support for signals and there is a lot of confusion in the community about whether this is going to replace RxJS or how it should be used in an app in combination with RxJS. This blog post sheds some light on what I think is the best way

Read More »

High ROI Testing with Cypress Component Testing

Testing is one of the most struggled topics in Angular development and many developers are either giving up testing altogether or applying inefficient testing practices consuming all their precious time while giving few results in return. This blog post will change all this as we will cover how I overcame these struggles the hard way

Read More »