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:

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: 1

Concepts:

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

Definitions:

  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:

@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:

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:

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: 2

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

Concepts:

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

Definitions:

  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:

Directive:

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

Component:

@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:

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:

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: 3

Concepts:

  1. Renderer

Definitions:

  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:

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

Steps

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:

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:

  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:

Method: 1

Concepts:

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

Definitions:

  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:

@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:

  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:

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:

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):

View Container makes DOM structure changes safe.

Methods:

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

Initializing a View Container:

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:

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:

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

Steps:

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:

Template file:

<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:

  @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:

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:

  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:

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