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 do Cypress component testing for Angular apps with MSW

In this post, we will cover how to do Cypress Component testing with MSW (mock service worker) and why it’s beneficial to have a mock environment with MSW. The mock environment My recommendation for most enterprise projects is to have a mocking environment as it serves the following purposes : * The front end can

Read More »

Handling Authentication with Supabase, Analog and tRPC

In this video, I cover how to handle authentication with Supabase, Analog and tRPC. It’s based on my Angular Global Summit talk about the SPARTAN stack you can find on my blog as well. Code snippets Create the auth client Do you want to become an Angular architect? Check out Angular Architect Accelerator.

Read More »