12

I know there are some similar questions but none answer my question.

Basically what is the correct way to manipulate the DOM in angular say I have this.

html

<button id="buy-now">BUY NOW</button>

and when you click the button I want the button to turn purple so in pure javascript you would write something like

javascript

changeColour() {
   const b = document.getElementById('buy-now');
   b.style.backgroundColor = 'purple';
}

now In Angular I have been doing it this way, but recently noticed people saying this is not correct??

What is the correct way to manipulate the DOM in angular, and how would I rewrite my example to reflect this corrected way??

Any help would be appreciated!

EDIT

To be clear, I know in Angular I can do this

HTML

<button (click)="changeColour()" id="buy-now">BUY NOW</button>

TS FILE

changeColour() {
   const b = <HTMLElement>document.querySelector('#buy-now');
   b.style.backgroundColour = 'purple'
}

but is this the appropriate way to manipulate the dom??

Changing the button colour was only a quick example, I mean for any type of DOM Manipulation

Thanks

Smokey Dawson
  • 8,827
  • 19
  • 77
  • 152
  • Check out the docs for NgClass: https://angular.io/api/common/NgClass – Chris Jan 29 '19 at 03:15
  • @Whatatimetobealive What you sent me is for Angular 1 – Smokey Dawson Jan 29 '19 at 03:19
  • @Chris yes I know about NgClass this isnt just for changing a button class, Its for any and all DOM Manipulation in Angular 2+ – Smokey Dawson Jan 29 '19 at 03:22
  • Possible duplicate of [How to perform DOM manipulation in Angular components?](https://stackoverflow.com/questions/40464794/how-to-perform-dom-manipulation-in-angular-components) – Peter Jan 29 '19 at 04:49
  • @SmokeyDawson I guess I don't follow your question. What type of DOM manipulation are you attempting to do that wouldn't be handled by many of the suggestions listed below? – Chris Jan 29 '19 at 16:01

3 Answers3

13

Any change to a document's aesthetics like page navigation (routing), item selection (ngIf), loop iterations(ngFor) etc. is DOM Manipulation. It can be either property driven or event triggered or reference handled.

Angular offers multiple ways to handle DOM Manipulation.

  1. EVENT BINDING:

    The flow of information from elements in a component to the corresponding component's class is event binding (HTML Template to TS). Event binding works without having to define a template reference variable. This is the best and the easiest method to manipulate DOM elements. () denotes event binding.

    Below is the snippet for our example:

    HTML

    <button (click)="changeColour()" [ngStyle]="{'background-color': buttonColor}">BUY NOW</button>
    

    TS

    buttonColor : string = 'grey'
    changeColour() {
        this.buttonColor = 'purple'
    }
    

    Angular also has a feature to allow event listener to be implemented only on a particular event, e.g when enter key is pressed, mouse clicked or a combination of keys is pressed.

    Below is the snippet for our example:

    HTML

    <button (keyup.control.shift.enter)="changeColour()" [ngStyle]="{'background-color': buttonColor}">BUY NOW</button>
    

    The colour of the button becomes purple when Ctrl+Shift+Enter is pressed.

  2. @HostListener and @HostBinding:

    This is similar to event binding and property binding in angular.

    @HostBinding('value') val; is same as [value]="val"

    and @HostListener('click') click(){ } is same as (click)="click()".

    @HostBinding and @HostListener are defined inside directive whereas [] and () are defined inside the component template.

    Below is the snippet for our example:

    HTML

    <button class="c_highlight">BUY NOW</button>
    

    TS (host.directive.ts)

    @Directive({
        // Notice the . in selector => this directive will work for DOM with the c_highlight class 
        selector: '.c_highlight'
     })
    
     export class HostDirective {
    
         @HostBinding('style.backgroundColor') c_color = "red"; 
    
         @HostListener('click') c_onclick() {
             this.c_color = "purple" ;
         }
     } 
    
  3. Renderer2:

    This is basically a wrapper over the browser API for DOM Manipulation. The Renderer2 API can be run across platforms other than the DOM and you can provide your own Renderer2 implementation, unique to a platform. There are multiple DOM manipulation methods present for the same like setStyle(), createElement(), createText(), appendChild() etc. and we can implement our own custom methods too. This is similar to the template reference variable in your example and we are using the reference to the element to modify its properties.

    Below is the snippet for our example:

    HTML

    <button (click) = "onClick()" #abcd>BUY NOW</button>
    

    TS

    @ViewChild('abcd') 
    private abcd: ElementRef;   
    constructor(private renderer: Renderer2) {
    }
    onClick() {
        this.renderer.setStyle(this.abcd.nativeElement, 'backgroundColor','purple');
    }
    

    Read More - https://angular.io/api/core/Renderer2

  4. Template Reference Variable:

    This involves creating an id (reference) for the element. This is similar to the jquery approach wherein each element can have an id and events can be defined on these elements by using the getElementById() method. Example (as shown in your question):

    HTML

    <button (click)="changeColour()" id="buy-now">BUY NOW</button>
    

    TS

    changeColour() {
        const b = <HTMLElement>document.querySelector('#buy-now');
        b.style.backgroundColour = 'purple'
    }
    
  5. fromEvent() from rxjs: This is similar to defining an Event Listener on an element. The fromEvent method creates an Observable that emits events of a specific type coming from the given element. Only the reference for the element has to be declared; the event is associated with this reference. Example:

    HTML

    <button #abcd>BUY NOW</button>
    

    TS

    @ViewChild('abcd') 
    private abcd: ElementRef;   
    ngOnInit(){
        fromEvent(this.abcd.nativeElement, 'click').subscribe(res => this.abcd.nativeElement.style.backgroundColor = 'purple');
    }
    

    SUMMARY:

    The choice for the technique used for DOM Manipulation depends solely on the developer. Each of these methods have their own benefits and trade-offs; like for Event Binding, performance can be relatively slower when a large list is being modified as the change detection cycle can only run again once this function returns. Method 1 and 2 are the best angular practices as these avoid creating references for elements which can be risky and can make your application more vulnerable to XSS attacks as pointed out by @Chellapan.

Jahnavi Paliwal
  • 1,721
  • 1
  • 12
  • 20
3

According to Angular Documentation Using Element Ref is Vulnerable

Permitting direct access to the DOM can make your application more vulnerable to XSS attacks. Carefully review any use of ElementRef in your code. For more detail, see the Security Guide.

Use Renderer2 to manipulate the DOM.

Create a Template Ref in template and pass it to the changeColour method and Use renderer2 service which provide setStyle method to set the style of the element

component.html

<button #button (click)="changeColour(button)">BUY NOW</button>

component.ts

constructor(private renderer: Renderer2) { }

changeColour(element: HTMLElement) {
  this.renderer.setStyle(element.nativeElement, 'backgroundColour ', 'purple');
}

Ref:https://angular.io/api/core/ElementRef#security-risk

Chellappan வ
  • 23,645
  • 3
  • 29
  • 60
2

You can access the DOM element in the template with a template reference variable:

<button #btn (click)="btn.style.backgroundColor = 'purple'">BUY NOW</button>

Or you can pass the variable to a method:

<button #btn (click)="changeColor(btn)">BUY NOW</button>

and modify the element in code:

changeColour(element: HTMLElement) {
   element.style.backgroundColour = 'purple';
}

See this stackblitz for a demo.

ConnorsFan
  • 70,558
  • 13
  • 122
  • 146
  • I know I could write it inline my question is, is that the correct way to manipulate the dom in Angular, I will update my question to be clearer – Smokey Dawson Jan 29 '19 at 03:15
  • Does this hold true for any and all DOM Manipulation?? I'm not too concerned with changing the colour of one button, My question is more for advance DOM Manipulation, the colour changing was just an example – Smokey Dawson Jan 29 '19 at 03:24
  • 1
    The template reference variable gives you access to the DOM element. You can do what you want with it in code. In some circumstances, you may need to access it with `@ViewChild("btn")` in code. It depends on the specific context and requirements. – ConnorsFan Jan 29 '19 at 03:26
  • Another way to manipulate the DOM is with `Renderer2`, as explained in [this article](https://alligator.io/angular/using-renderer2/). – ConnorsFan Jan 29 '19 at 03:31
  • right yes this is similar to another article I was reading, and I was wondering if that was the recommended way or something else was the recommended way instead of manipulating the DOM Directly – Smokey Dawson Jan 29 '19 at 03:33
  • In my opinion, the preferred way is with template reference variables, when possible. The `Renderer2` would be used in directives or for the host element. – ConnorsFan Jan 29 '19 at 03:38