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
Creating a plugin architecture using Angular components
For creating a plugin architecture with Angular components you simply need to do these two things:
- Make the parent support plugins by using ng-content in the parent’s template
- 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.