Dynamic Component Generation in Lazy-Loaded Routes
Tags: angular, typescript, dynamiccomponents
Published: 2021-06-15 18:30:13

Jim Armstrong | ng-conf | Jun 2019

_— Exploit Data Driven Component Layout, Loaded On-Demand in Angular_

One of the fun things about being an applied mathematician in front-end development is the never-ending stream of complex, interactive problems that users wish to solve. These range from gamification of business applications to data-driven user experiences. Two interesting applications of the latter topic include dynamic routing through an application and dynamic component layout. Both are examples of user experiences that vary based on factors such as user role and prior uses of the application. In other words, two different users may be presented with an entirely different experience based on both a _priori_ knowledge of the user and how the user interacts with the application in real time.

The general solution I’ve applied to dynamic routing is a data-driven, adaptive decision tree. This is, unfortunately, a client-proprietary solution and can not be shared in public. However, I built the solution on top of two projects that I have released to the public in my Github, a general tree data structure and a lightweight expression engine. Both are written in Typescript. I believe any enterprising developer with some fundamental knowledge of decision trees could duplicate my work. So, here is the best I can do for you at present:

[theAlgorithmist/TSTree](https://github.com/theAlgorithmist/TSTree) - Typescript Math Toolkit General Tree Data Structure on github.com

[theAlgorithmist/TSExpressionEngine](https://github.com/theAlgorithmist/TSExpressionEngine) - Typescript Math Toolkit Expression Engine on github.com

#### Dynamic, Data-Driven Component Layout

This article discusses how to layout Angular components programmatically, based on a simple JSON data structure. A simple example I’ve worked on in the past is where components are vertically stacked in an order that is server-generated. Consider a case where three components, A, B, and C could be presented to a user. They might be displayed in the order A, B, C, or A, C, B, or perhaps C, B, A. In general, there are _n_! display permutations of _n_ components (displayed _n_ at a time). One might be willing to struggle with a layout that could accommodate all possible scenarios for three components, but what about when the client later indicates there could be anywhere from three to eight components? And, we know how clients think, so that 3–8 range will not stay constant for very long. In short, this is a situation that is much better managed with an imperative instead of declarative approach.

Thankfully, the Angular team has provided everything we need to dynamically generate components at runtime. But, before we move on, here is the repo for this article so that you can follow along with the deconstruction, and have the code for experimentation and future usage in projects.

[theAlgorithmist/Angular8LazyLoadDynamic]() - Angular 8 Dynamic Component Generation in a Lazy-Loaded Route on github.com

#### The Scenario

This demo simulates a scenario where a user logs into an application and then selects a navigational element that routes to another area of the application. The user experience, however, is tailored to each specific user based on information that is known about the user after login. A service call is to be made before activating the route. The service returns some JSON data that describes the order in which child components are to be displayed in the Angular Component associated with the selected route. The JSON data also provides some simple textual and numerical data that is used for binding within each of the child components.

Since the order of the components is not known in advance and the number of components can also vary, the child components are dynamically created and then rendered into the parent component’s template. For demonstration purposes, the number of components in the code distribution is limited to three. The technique, however, is easily scaled to any number of components in any arbitrary order.

#### The Process

I’ll treat the explanation in a cookbook fashion since additional information on each step in the process is readily available online for subsequent study.

1 — Each child component that could be rendered into a parent component must be provided as an _EntryComponent_ into the _Module_ associated with the route. This is why the route should be lazy-loaded as there is no guarantee that every user will navigate to this route.

2 — A route _Resolver_ is used to ensure that the JSON data is loaded before the route activates. This is the mechanism a server would use to dynamically alter the experience for each individual user.

3 — In order for an Angular component to be dynamically displayed in a template, it must be added to a _ViewContainerRef_ associated with a DOM container after creating the component. An _Attribute Directive_ is used for this purpose.

4 — Each child component is to be generated by two _Factories_. One factory (that we write) generates component type and raw data instances based on a symbolic code and a known number of components. So, if the component range is changed from 3–8 to 2–12 at a later point, the four new items must be added to the factory. Another factory (provided by Angular and discussed below) is used to create the actual Angular component at runtime.

5 — The template for the lazy-loaded component consists of a _ng-container_ as the primary container with a _ngFor_ that loops over the number of dynamic components specified in the JSON data.

6 — Each dynamic component is associated with a _ng-template_ by using an attribute _directive_.

7 — A _QueryList_ of dynamic item attribute directives is processed by the parent component. Each child component is created by an Angular Component _Factory_ (provided by a factory resolver) and then added to the _ViewContainerRef_ of the _ng-template_. Data for each component is then added to the newly created component for binding. This requires some handshaking between the parent component code and the attribute directive. The actual separation of concerns can be experimented with and adjusted to suit your specific desires.

#### Application Layout

The application structure for this demo is rather simple. There is a single application module and component. The main app component displays a button whose markup contains a _routerLink_. That is used to route the user to the single feature module, appropriately named ‘feature’ :)

The main app module provides a single route resolver that is used to ensure the JSON data for dynamic layout is loaded before the route is activated.

All libraries, directives, and components for the single feature are provided in the _feature_ folder.

The model for dynamically generated components is provided in _src/app/models_.

There is no relevant code in the main app component and the only item worth deconstructing is the main app routing module. Relevant code from the routing module is provided below.

_/src/app/app-route-module.ts_

```
const routes: Routes = [
  {
    path: `feature`,
    resolve: { model: AppRouteResolver },
    loadChildren: () => import(`./feature/feature.module`).then(m => m.FeatureModule)
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  providers: [AppRouteResolver],
  exports: [RouterModule]
})
export class AppRoutingModule { }
```

Note the new Angular 8 dynamic import syntax for lazy-loaded routes. This module also provides the route resolver, _AppRouteResolver_. The _model_ property is used when loading data from the activated route.

Now, we can deconstruct each of the above-listed steps.

#### (1) Feature Module

Look at _/src/app/feature/feature.module.ts_. The important code is shown below.

```
export const DYNAMIC_COMPONENTS: Array<any> = [
  BaseComponent, Component1Component, Component2Component, Component3Component
];

@NgModule({
  declarations: [
    DynamicItemDirective, FeatureComponent, ...DYNAMIC_COMPONENTS
  ],
  imports: [
    CommonModule,
    RouterModule,
    RouterModule.forChild(localRoutes),
  ],
  providers: [],
  entryComponents: DYNAMIC_COMPONENTS
  exports: [
    DynamicItemDirective, ...DYNAMIC_COMPONENTS
  ]
})
```

The three dynamic components in this example are _Component1Component, Component2Component,_ and _Component3Component_. Yes, those are stupid names, but slightly better than my original choice of Moe, Larry, and Curly :) Each of these components extends _BaseComponent_.

In particular, note the declaration of all of dynamic components in the _entryComponents_ property of _NgModule_. Since there is no direct reference to any of these components in a template, Angular needs this information directly for compilation purposes. Without _entryComponents_ Angular will tree-shake out those components because they are never referenced in a template.

The attribute directive, _DynamicItemDirective_, is used to associate a _ViewContainerRef_ with a specific DOM element (_ng-template_ in this example).

#### (2) Route Resolver

The resolver is used by the main app component and is provided in _/src/app/app-route.resolver.ts_. This code implements the _Resolve<T>_ interface by providing a concrete implementation of the _resolve()_ method.

```
@Injectable({providedIn: 'root'})
export class AppRouteResolver implements Resolve<LayoutModel>
{
  constructor(@Inject(DataService) protected _service: DataService)
  {
    // empty
  }

  resolve(): Observable<LayoutModel>
  {
    // load layout model
    return < Observable<LayoutModel> > this._service.getData('/assets/layout-model.json');
  }
}
```

---

## ng-conf: Join us for the Reliable Web Summit

Come learn from community members and leaders the best ways to build reliable web applications, write quality code, choose scalable architectures, and create effective automated tests. Powered by ng-conf, join us for the Reliable Web Summit this August 26th & 27th, 2021.
https://reliablewebsummit.com/