How to do DOM Manipulation properly in Angular?

Post Editor

Often when we are using JavaScript techniques inside Angular, we almost forget about the framework’s features. Let's utilize them.

8 min read
post

How to do DOM Manipulation properly in Angular?

Often when we are using JavaScript techniques inside Angular, we almost forget about the framework’s features. Let's utilize them.

post
post
8 min read
8 min read

Often when we are using JavaScript techniques inside Angular, we almost forget about the framework’s features. Let's utilize them.

One of the interesting topics in web development is DOM manipulations. There are many ways to manipulate the DOM in Angular. Let's use them instead of straight forward JavaScript approaches.

Usually, there are two concepts in DOM Manipulations.

  1. Modifying DOM Elements.
  2. Modifying DOM Structure.

Modifying DOM Elements:Link to this section

We are familiar with a lot of JavaScript methods that modify the DOM element. Here are some of them:

  1. classList.add()
  2. setAttribute()
  3. style.setProperty()

These all are JavaScript native DOM element methods.

There are multiple ways to modify the DOM elements in Angular. We will discuss almost every method and choose the best among them.

Method: 1Link to this section

Concepts:Link to this section

  1. Template reference variables.
  2. ElementRef.
  3. @Viewchild/@Viewchildren.
  4. AfterViewInit.

Definitions:Link to this section

  1. Template reference — Reference to a particular DOM element.
  2. ElementRef — ElementRef is a class, which consists of all native DOM elements. Using nativeElement object we can access all DOM elements in Angular.
  3. @Viewchild/@Viewchildren — Select child or all children elements from the DOM.
  4. AfterViewInit — One of this Lifecycle hook is called after the Angular component initialized its view.

Example:Link to this section

<>Copy
@Component({ selector: 'app-root', template:`<span #el>I am manoj.</span> <span>I am a web developer.</span>`, styles:[`[highlight]{background: green; color: white}`] }) export class AppComponent implements AfterViewInit{ @ViewChild('el') span:ElementRef; ngAfterViewInit(){ this.span.nativeElement.setAttribute('highlight', ''); } }

Steps:Link to this section

Step 1 : In this example, I am using template reference and @viewchild query to get an HTML element.

`<span #el>I am manoj.</span> <span>I am a web developer.</span>`
@ViewChild(‘el’) span: ElementRef;

Step 2: setAttribute of native DOM element is using to add the attribute.

this.span.nativeElement.setAttribute(‘highlight’, ‘’);

Output:Link to this section

Output

So, it is working fine. But there is a problem with this method. Here, we are mixing rendering and presentation logic.

Usually, components have presentation logic such as defining arrays, objects, and iterations, and so on.

Rendering logicsare actually modifying the DOM elements. We should maintain rendering logics in a separate directive.

We can communicate components and directives through Data Binding Mechanism.

Now let's consider another method to overcome this problem.

Method: 2Link to this section

Let’s create a directive and put all rendering logic inside that directive.

Concepts:Link to this section

  1. ElementRef
  2. @Input() — Decorator
  3. ngOnInit()

Definitions:Link to this section

  1. ElementRef — It helps to access DOM elements.
  2. @Input() — Data binding to communicate component and directive.
  3. ngOnInit() — Initial Life-cycle hook, which is called after Angular created the component.

Example:Link to this section

Directive:

<>Copy
@Directive({ selector: '[appHighlight]' }) export class HighlightDirective implements OnInit{ @Input() appHighlight; constructor( private element: ElementRef) { } ngOnInit(){ this.element.nativeElement.setAttribute(this.appHighlight, ''); } }

Component:

<>Copy
@Component({ selector: 'app-root', template:`<span [appHighlight]="'highlight'">I am manoj.<span> <span>I am a web developer.</span>`, styles:[`[highlight]{background: green; color: white;}`] }) export class AppComponent{}

Steps:Link to this section

Step 1: Inject ElementRef into a directive file’s constructor.

constructor( private element: ElementRef) { }

Step 2: Add @input decorator to the directive.

@Input() appHighlight;

Step 3: Use setAttribute() native element method to add an attribute in ngOnInit() lifecycle hook.

ngOnInit(){this.element.nativeElement.setAttribute(this.appHighlight, ‘’)}

Step 4: Apply the directive to the span element in the component template.

<span [appHighlight]=”’highlight’”>I am manoj.</span>

Output:Link to this section

Output

The output is the same as previous but here render logics are moved from component to directive. It helps to reuse the corresponding component and directives anywhere. (code reuse)

But here we are accessing the DOM elements directly using ElementRef Class. Permitting direct access to the DOM can make our applications more vulnerable and XSS attacks.

Most of the people use ElementRef in all places. So now our question is what should we use to access DOM elements safely? Yeah, we have a solution for that. Let’s see another method.

Method: 3Link to this section

Concepts:Link to this section

  1. Renderer

Definitions:Link to this section

  1. Renderer — Makes direct DOM access safe and it is Platform independent. It modifies the DOM elements without touch the DOM directly. A renderer is a service that consists of some methods. It helps to manipulate the DOM.

I listed out some renderer methods below,

  1. addClass()
  2. removeClass()
  3. setStyle()
  4. removeStyle()
  5. setProperty()

Example:Link to this section

<>Copy
constructor( private element: ElementRef, private renderer: Renderer2) { } ngOnInit(){ this.renderer.setAttribute(this.element.nativeElement,this.appHighlight, '') }

StepsLink to this section

Step 1: Inject renderer into directive file’s constructor.

constructor( private renderer: Renderer2) { }

Step 2: Use renderer setAttribute() method to add an attribute in ngOnInit() lifecycle hook.

ngOnInit(){ this.renderer.setAttribute(this.element.nativeElement , this.appHighlight, ‘’)}

Output:Link to this section

Output

The output is the same here. But we modified the DOM properly and more securely.

Attribute directives such as ngClass, ngStyle are modifying the DOM elements based on the renderer.

So, hereafter, we will use Renderer to modify the DOM. It is a more proper way too.


Modifying DOM Structure:Link to this section

  1. createElement()
  2. remove()
  3. appendChild()
  4. removeChild()

These are some examples of JavaScript methods that modify DOM structures. We are already familiar with those methods. But now we are going to see how to modify the DOM structure in Angular.

Remove a child component from the DOM:Link to this section

Method: 1Link to this section

Concepts:Link to this section

  1. Template Reference
  2. ElementRef
  3. @ViewChildren()
  4. AfterViewChecked
  5. Renderer
  6. QueryList

Definitions:Link to this section

  1. Template Reference — Reference of particular DOM Element.
  2. ElementRef — Helps to access DOM elements.
  3. @ViewChildren() — Returns the specified elements or directives from the viewDOM as QueryList.
  4. AfterViewChecked — It invoked after the default change detector has completed one change check cycle.
  5. Renderer — Makes direct DOM access safer.
  6. QueryList –Return type. It just a list of items. Angular updates QueryList while add or remove list items. It initialized only before the ngAfterViewInit() lifecycle hook.

Example:Link to this section

<>Copy
@Component({ selector: 'app-parent', template: `<p>parent works!</p> <app-child #child></app-child> <button (click)='removeChild()'>remove child</button>`, styleUrls: ['./parent.component.css'] }) export class ParentComponent implements AfterViewChecked{ @ViewChildren('child', {read: ElementRef}) childComp:QueryList<ElementRef> constructor(private renderer: Renderer2, private host: ElementRef) { } ngAfterViewChecked() { console.log(this.childComp.length) } removeChild(){ this.renderer.removeChild(this.host.nativeElement, this.childComp.first.nativeElement); } }

Steps:Link to this section

  1. Use template reference and ViewChildren() to get HTML element.
  2. Inject Renderer and ElementRef into the constructor.
  3. Use the removeChild() method of the renderer to remove the child component.
  4. To get the host element, we need to use ElementRef.

Output:Link to this section

Method 1 Output

After Click ‘Remove Child’ Button:

Result

So we have removed the child component successfully. That’s great.

But, I want to show you the output screen once again with the console log.

Child Count
Question: we have removed the child component, but still, the child components counts remain 1. Why? How to resolve it?

Yes, obviously this question will come to our mind. Don't worry, we have a solution.

DOM and View Relationship:Link to this section

Angular View and DOM relationship

Angular has a view concept. Look at the diagram; it will give a clear idea about the view and DOM relationship. The view is nothing but the reference of DOM. While we run the Angular application, it will create multiple views.

For each component creation, view also created by Angular. We are seeing the component hierarchy outside. But under the hood view hierarchy also created based on components hierarchy.

If we made any changes in DOM such as drag, add, or remove the DOM element means, view should be updated immediately. Otherwise, it will be a problem.

Change Detection runs based on the Hierarchy of views.

Using Method 1, we removed the child component from the DOM. But view hierarchy remains the same, and the view is not updated. So it shows one child. Here this is a small scenario that’s fine, but if we removed the ten components and view is not updated means, Angular still runs change detection for all deleted components. That will badly affect our application.

View Container (viewContainerRef):Link to this section

View Container makes DOM structure changes safe.

Methods:Link to this section

  1. insert()
  2. move()
  3. remove()
  4. createEmbeddedView()

Initializing a View Container:Link to this section

We can make any DOM element as a view container. But usually, all the people commonly take <ng-container> as a view container. ng-container is nothing but an HTML tag but it is the Angular specific.

There are two types of views.

  1. Embedded Views — Always be a part of the view container.
  2. Host Views — Always be a part of a view container and also be standalone.

Let see how to make the DOM element a view.

Steps:Link to this section

Step 1: Add ng-container tag into the template file.

<ng-container #viewcontainer></ng-container>

Step 2: Add viewchild query to select element.

@ViewChild(‘viewcontainer’, {‘read’: ViewContainerRef}) viewcontainer;

Here, ViewContainerRef is a very important part, which makes the DOM node as a view container.

Step 3: This step creates the view and adds the view into view container.

Viewcontainer.CreateEmbeddedView(TemplateRef);

Create a Template:Link to this section

It is very similar to creating a view container. Let see how to create them.

Steps:Link to this section

Step 1: Add ng-template tag into the template file.

<ng-template #t></ng-template>

Step 2: Create a template using this.

@ViewChild(‘t’, {‘read’: TemplateRef}) template: Templateref;

Example:Link to this section

Template file:

<>Copy
<p>parent works!</p> <ng-container #viewcontainer></ng-container> <ng-template> <app-child #child></app-child> </ng-template> <button (click)='removeChild()'>remove child</button>

Class File:

<>Copy
@ViewChildren('child', {read: ElementRef}) childComp:QueryList<ElementRef> @ViewChild('viewcontainer', {'read': ViewContainerRef}) viewcontainer; @ViewChild(TemplateRef) template: TemplateRef<null>; constructor(private renderer: Renderer2, private host: ElementRef) {} ngAfterViewChecked() { console.log("Child components count", this.childComp.length) } ngAfterViewInit(){ this.viewcontainer.createEmbeddedView(this.template); } removeChild(){ this.viewcontainer.remove(); }
Solution: This example is nothing but a solution to our question. Now, If we remove the DOM element Angular directly update the views.

Let see the solution below,

Result

Finally, we did it. I hope we all are clear about views.

Structural directives such as *ngIf, *ngFor, *ngSwitch are modifying the DOM structure based on view containers.
Hereafter, we will use viewContainerRef and templateRef to change the DOM structure.

Note:Link to this section

We can separate rendering logics into separate directive as we did in Modify DOM elements section. It will be very useful. We can use our directive such as *ngFor, *ngIf.

If DOM elements created by Angular, views will be created by default. If we use Jquery or JavaScript to change the DOM structure directly, then no views will be updated. Angular doesn’t know that the DOM element is created. So change detection not works for that DOM element.

Conclusion:Link to this section

  1. Modify DOM elements — Use Renderer service.
  2. Modify DOM Structure — Use ViewContainerRef and TemplateRef classes.
  3. Separate rendering logics into directives. It helps to reuse the code.
  4. Avoid straight forward DOM manipulation using JavaScript or Jquery. Try to use Framework’s available features.

I hope now everything is clear with the DOM manipulation concept in Angular. If you have any suggestions please leave it in the comment.

Reference:Link to this section

I have learned these concepts from Maxim Koretskyi's ngConf video.

Discuss with community

Share

About the author

author_image

Web Developer. I write more about Web / Mobile Applications Development. I do applications using MEAN Technology. For More, https://manoj-selvam.com/

author_image

About the author

Manoj Selvam

Web Developer. I write more about Web / Mobile Applications Development. I do applications using MEAN Technology. For More, https://manoj-selvam.com/

About the author

author_image

Web Developer. I write more about Web / Mobile Applications Development. I do applications using MEAN Technology. For More, https://manoj-selvam.com/

NxAngularCli
NxAngularCli
NxAngularCli

Featured articles