3 Steps to Creating Dynamic Views in Angular

Dynamically Loading Components Without Directives

Zeng Hou Lim
Nov 30 · 6 min read
Photo by Émile Perron on Unsplash

Context

Often times, we need to render a child view dynamically. For instance, we might have a parent view that has a tab menu and we would like to render the views in accordance which tab was being selected.

Being new to Angular, this was something that I have experience with and it took me a fair amount of time to get the implementation working despite following the official Angular guide. Reading Maxim Koretskyi’s article on DOM manipulation definitely helped my understanding, so I’ll recommend you give it a read too if you want more in-depth explanations. In this article, I’ll highlight and summarize 3 key concepts that will quickly get you running.


1. Creating the View

Before we can insert a view, we would have to first know how to create one. Angular supports two types of views, namely host views and embedded views.

a. Host Views

This is the view that we use most frequently and are probably most familiar with. When Angular creates a component, we get a view that also contains the data of the component. When we talk about host views, they’re essentially the instantiated component views.

To create a component, we simply include its selector in the template of another component and Angular automatically does the rest. However, when we are dynamically creating views, there are two intermediary steps that are necessary.

We would needComponentFactory, which will be used to manually (i.e. dynamically) create a component. In order to get a ComponentFactory, we would need to to use the ComponentFactoryResolver by passing in the Component as a parameter. Let’s say we wish to create a dynamic component upon initialization:

<-- parentComponent.component.ts -->import { ..., ComponentFactoryResolver } from '@angular/core';
import { FirstComponent } from '<path>/firstcomponent.component.ts'
...export class ParentComponent implements OnInit {constructor(private resolver: ComponentFactoryResolver) {}
}
ngOnInit() {
const componentFactory = this.resolver.resolveComponentFactory(
FirstComponent);
const dynamicallyCreatedComponent = componentFactory.create();
}

Important:

You need to define the Component in NgModules‘s entryComponents within the app.module.ts file. In the Angular doc’s dynamic component loading, this portion was not included and it took me a while to figure it out.

<-- app.module.ts -->
...
providers: [],
bootstrap: [AppComponent],
entryComponents: [FirstComponent]
});

An entry component is any component that Angular loads imperatively, (which means you’re not referencing it in the template), by type. (Angular.io)

b. Embedded Views

<-- parentComponent.component.html -->
<div>This is rendered</div>
<ng-template #templateName>
<div> This is not rendered</div>
<ng-template>

ATemplateRefis a blueprint for creating DOM elements, just like how a Class is to an instance of itself. When you reference a ng-template, you get a TemplateRef. If you include a ng-template in your the component.html file, it will not be rendered, since you need to be initialize the view and embed it after. In the following section, we will talk about how to to reference a DOM element.

For now, we simply need to know that we are able to select the ng-template via its selector name, #templateName, create an instance of it, which is what we call the Embedded View.

If you are interested, you can read more about the difference between host and embedded views.

2. Selecting the DOM element

If you want to dynamically render a view, you can imagine that there is an element that serves as an anchor point to tell Angular where the view should be inserted. If you had experience with jQuery, it would be similar to:

<div id="selector></div> // HTML$('.selector').html('Dynamic Content Here') // jQuery selector

In Angular, we use the decorators @ViewChild , and @ViewChildren to search for the element(s). They both provide similar functionality but the former returns only a single reference, and the latter returns a QueryList object that has multiple references to elements.

Syntax

@ViewChild(selector, [{read: any}], [{static: boolean}])

A little more explanation on the metadata properties here. read and static are both optional parameters, but I’ll quickly talk about them.

In Angular’s documentation, it says that read should be set to “true to read a different token from the queried elements”. However, a more practical usage would be to include the class of the element that you are interested in. In this StackOverflow post, it says that there can be several instances tied to the element selector. For instance, for each element, there are at leastElementRef and ViewContainerRef. Don’t worry if these two classes sound foreign to you now, I’ll touch on the latter in just a bit.Static property is more straightforward. Set it to true if you want the query results to run before the change detection occurs.

Let’s look at an example to see how we can get access to the ViewContainerRef of the element.

Example

<-- parentComponent.component.html --><div>
<ng-container #viewContainer></ng-container>
</div>
<-- parentComponent.component.ts -->export class ParentComponent implements OnInit {
@ViewChild('viewContainer', read: ViewContainerRef) viewContainer: ViewContainerRef;
...
}

3. Attaching the View

Now that we are able to selector an anchor element, we would need to attach view(s) to it. The ViewContainerRef represents the container of the element that we can attach views after.

Anchor Elements

In the above example, we are using ng-container as the anchor element. Alternatively, we could also use the ng-template. I suspect some folks would be curious as to whether or not we can use other elements, such as a div. Theoretically, we could use any elements we wanted.

However, it is important to note that Angular does not actually insert views in element, but rather after the element bounded to the ViewContainer. Both ng-container and ng-template do not actually get rendered in the DOM, but rather, as comments that Angular knows to find. As such, using these elements avoid inserting excessive elements into the DOM, such as an unwanted div.

Inserting the View

In the above code block, we already have an instance property calledviewContainer and we simply have to inject the view.

<-- parentComponent.component.ts -->import { ..., ComponentFactoryResolver } from '@angular/core';
import { FirstComponent } from '.../firstcomponent.component.ts';
import { SecondComponent } from '.../secondcomponent.component.ts';
...export class ParentComponent implements OnInit {
@ViewChild('viewContainer', read: ViewContainerRef) viewContainer: ViewContainerRef;
constructor(private resolver: ComponentFactoryResolver) {}
}
...onTabChange(selectedTab) {
this.viewContainer.clear(); // clear all views
if (selectedTab === 'firstComponent') {
const componentFactory = this.resolver.resolveComponentFactory(
FirstComponent
);
this.viewContainer.createComponent(componentFactory);
} else if (selectedTab === 'secondComponent') {
// similar idea here
}
};

Earlier, we used the .create() method from the ComponentFactory object, but the ViewContainerRef has a method that takes in a ComponentFactory, creates the host view by internally calling its create function, and inserts the view automatically.

Passing Data to The Dynamic Component

Right now, we’ve the ability to dynamically create host views. One useful thing to be able to do is to also pass in data to our component. That way, we are able to dynamically create host views with dynamic data. Let’s build on the above example.

<-- parentComponent.component.ts -->const componentRef =
this.viewContainer.createComponent(componentFactory);
(componentRef.instance).dynamicData = { a: 5, b: 3}; // passing data
<-- firstComponent.component.html -->
<div> Dynamic Content: {{ dynamicData.a }} </div>

The createComponent method returns a ComponentRef object, which we can access the instance property and add a dynamicData key. That way, in our child component, we can simply reference the data variable name via interpolation.

Creating an Embedded View

Now that we know how to create a host view, embedded views are almost similar conceptually. Recall that we talked about TemplateRef earlier? We can essentially create an embedded view with that.

<-- parentComponent.component.html --><div>
<ng-container #viewContainer></ng-container>
</div>
<ng-template #isLoggedInTemplate>
<div> You are logged in! </div>
<ng-template>
<-- parentComponent.component.ts -->export class ParentComponent implements OnInit {
@ViewChild('viewContainer', read: ViewContainerRef) viewContainer: ViewContainerRef;
@ViewChild('isLoggedInTemplate', read: TemplateRef) template:
TemplateRef<any>;
...displayLoginMessage() {
this.viewContainer.createEmbeddedView(this.template);
};

We simply add another create another TemplateRef instance property and use the ViewContainer‘s method to instantiate the view and insert it.

Final Words

By understanding these 3 concepts, you should be able to create dynamic views in Angular easily. In the official guide, the dynamic component loading is implemented sightly differently with the help of directives. I decided to talk a little more about doing it without since it seems to save the hassle of creating a directive. Nonetheless, it would be good to also look at the official docs to understand the underlying mechanisms.

Since I am fairly new to Angular, I might have overlooked some concepts or loosely used certain terms. If you noticed that, feel free to leave a comment for clarification — that will be very much appreciated! If you enjoyed this article, do leave a clap or two and share them with your friends!

JavaScript in Plain English

Learn the web's most important programming language.

Zeng Hou Lim

Written by

Software Engineer at LeanData. Excited about living my best life and becoming a better engineer. I like taking complex ideas and breaking them down.

JavaScript in Plain English

Learn the web's most important programming language.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade