[Angular] Strategy pattern with dynamic components
Introduction:
Strategy pattern: behvioural pattern which promotes the selection of an algorithm at runtime. It comes from the book ‘Design Patterns’ from the ‘Gang of Four’.
Dynamic component instanciation: loading ‘manually’ a component. We will use for this the
ComponentFactoryResolver
class: an Angular Core injectable able to map a component type to its factory.
The idea: use the factory resolver to instanciate one relevant component (strategy pattern) among a selection of components.
An example usecase:
Let’s take the example of a dashboard displaying several widgets.
The first architecture idea which comes in mind is this: one component displaying all the widget components.
This could do the job, but of course there are non negligible limitations with this approach. One is particularily obvious: the list of widgets is fixed 👎. In real life, we would probably expect the list to change according to the usecase.
Threrfore, we need something more flexible. A good solution seems to be wrapping our widgets into a ‘dump’ component, a ‘wrapper’, whose job would be to instantiate the relevant componant, according to a value passed as input.
Let’s review our DashboardComponent:
So now let’s see how to instantiate the relevant widget component in the WidgetWrapperComponent class.
The pragmatic approach:
This is already better. But this approach is still not very satisfying:
- no good separation of concerns (SoC) : the wrapper template is not the ideal place to store the widget list
- coupling between the wrapper and the widgets => it requires to modify the template each time the widget list changes
- not elegant
The ‘strategy’ approach:
Here is where the strategy pattern and the factory resolver come into play. The idea is, instead of listing all the possible widgets in the template, to directly instanciate the relevant component.
More accurately, we will :
- pass the relevant component type to the
ComponentFactoryResolver
, which will fetch the required component factory - pass this component factory to a
ViewContainerRef
(a representation of an HTML container), which will instantiate our component and append it to the container
Nb: before anything else, please add the dynamically loaded components into the
entryComponents
meta-property (?) of the NgModule to prevent the blocking errorNo component factory found for Widget1Component
.
1. Map components name keys to components types :
First we need a mapping between this :
and the widget component types. So we can do a simple mapping in a separate file :
Which will be imported in the WidgetWrapperComponent
.
2. Get the component factory :
Now, in each instance of WidgetWrapperComponent
, we need to pass the component references to the ComponentFactoryResolver
, which will fetch the component factory.
3. Create a new component instance and append it to the template :
For this, we need to:
- define a ‘container’ in our template, which will host our new component instance
- represent this container as a
ViewContainerRef
instance, and use its API to create the widget component instance and insert it in the template
we cound use any html / other (?) element - point sur ngTempte ??
we need to do this in the context (?) of the AfterViewInit hook, because we need the view to have been inited for our
ViewContainerRef
to have been defined.
A complete demo can be found at …