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 I migrated my Course Platform to Analog (step by step)

Analog is a full-stack framework for Angular ala NextJS that offers server-side rendering, static site generation, and API routes. Analog empowers Angular with server rendering tools making it optimal for public websites. Otherwise, Angular has often been neglected in favor of NextJS/NuxtJS for these purposes (due to the subpar SSR/SSG experience). I recently migrated my

Read More »

The Future of Angular and the Latest Features in Angular

In this video, I’ll be discussing the future of Angular and the latest features in Angular 17, including standalone components, signals, new control flow, the deferred operator, and server-side rendering improvements. I’ll also touch on the use of Tailwind CSS for styling and the benefits it offers. Join me to learn more about the exciting

Read More »

How to Invest as a Software Developer

In this video, I share my personal investment strategy as a software developer, focusing on putting money to work and owning assets that generate cash flow and appreciation. I discuss the snowball effect of building an investment portfolio over time and the importance of compounding. I also touch on the allocation of investments in stocks,

Read More »

Angular 17: What’s new?

Angular has since the latest major version started what the Angular team calls a renaissance, which means a radical renewal of the framework. The main goals of the latest updates have been to improve the developer experience and performance so it aligns more with the other leading front-end frameworks in the space by introducing new

Read More »

How to Set up a CI pipeline with Azure Pipelines and Nx

It goes without saying that having a CI pipeline for your Angular apps is a must. Setting one up for regular Angular apps is fairly straightforward but when you have an Nx monorepo there are certain other challenges that you have to overcome to successfully orchestrate a “build once, deploy many” pipeline. This post will

Read More »