130

How can I add a class to the body tag without making the body as the app selector and using host binding?

I tried the using the Renderer but it changes the whole body

Angular 2.x bind class on body tag

I'm working on a big angular2 app and changing the root selector will impact a lot of code, I will have to change a lot of code

My use case is this:

When I open a modal component (created dynamically) I want the document scrollbar to hide

Sunil Garg
  • 14,608
  • 25
  • 132
  • 189
Rachid O
  • 13,013
  • 15
  • 66
  • 92
  • 1
    Actually if you work with js within html page what is the problem with using `document.body.className|classList`? – yurzui Apr 21 '17 at 12:13
  • haha if only it was that simple :) but it's a bad practice to access dom directly – Rachid O Apr 21 '17 at 12:14
  • You can write a big wrapper that will be executed several second and at the end added `class` to `body`. If you are not going to use server rendering or web worker what you're afraid of? – yurzui Apr 21 '17 at 12:17
  • so there's no better solution than this ? – Rachid O Apr 21 '17 at 12:25
  • 7
    I can't understand these abusive people who downvote and close questions for no valid reason – Rachid O Apr 21 '17 at 12:42
  • I didn't downvote – yurzui Apr 21 '17 at 12:45
  • Just a question, Angular is designed as a component architecture. Why do you need to change the body and not the top component app that everything is inside it ? – elpddev Apr 21 '17 at 13:17
  • I'm working on a big angular2 app and changing the root selector will impact a lot of code, >I will have to change a lot of code – Rachid O Apr 21 '17 at 15:16
  • Why change the body tag? Background color? Top level 'modernizer style' css classes? – Simon_Weaver Jun 03 '18 at 00:28
  • There are [3 great examples to skin this cat](http://diguphere.com/how-to-add-or-remove-class-to-body-tag-in-angular/) listed here. (helped me a lot) – Frank N Feb 20 '20 at 14:34

2 Answers2

282

I would love to comment. But due to missing reputation I write an answer. Well I know two possibilities to solve this issue.

  1. Inject the Global Document. Well it might not be the best practice as I don't know if nativescript etc supports that. But it is at least better than use pure JS.
constructor(@Inject(DOCUMENT) private document: Document) {}

ngOnInit(){
   this.document.body.classList.add('test');
}

Well and perhaps even better. You could inject the renderer or renderer 2 (on NG4) and add the class with the renderer.

export class myModalComponent implements OnDestroy {

  constructor(private renderer: Renderer) {
    this.renderer.setElementClass(document.body, 'modal-open', true);
  }

  ngOnDestroy(): void {
    this.renderer.setElementClass(document.body, 'modal-open', false);
  }
}

EDIT FOR ANGULAR4:

import { Component, OnDestroy, Renderer2 } from '@angular/core';

export class myModalComponent implements OnDestroy {

  constructor(private renderer: Renderer2) {
    this.renderer.addClass(document.body, 'modal-open');
  }

  ngOnDestroy(): void {
    this.renderer.removeClass(document.body, 'modal-open');
  }
}
Tsvetan Ganev
  • 8,246
  • 4
  • 26
  • 43
DaniS
  • 3,429
  • 1
  • 12
  • 18
  • 3
    thanks for the reply, I think using the rendrer is the best solution – Rachid O Apr 24 '17 at 09:44
  • 6
    In case anyone wondering where to get DOCUMENT, that's: `import { DOCUMENT } from '@angular/platform-browser' ` – Neph May 16 '17 at 22:07
  • 15
    The Renderer solution is much better. In Angular 4, Renderer has been deprecated and replaced with Renderer2. The code would have to change to: `this.renderer.addClass(document.body, 'modal-open');` and `this.renderer.removeClass(document.body, 'modal-open');` – GreyBeardedGeek Jul 27 '17 at 14:51
  • 3
    Also, `@Inject(DOCUMENT)` is no longer needed in the constructor – GreyBeardedGeek Jul 27 '17 at 14:57
  • If you're using ng4 with renderer2, the methods have been renamed to a more explicit `addClass` and `removeClass`. Documentation here: https://angular.io/api/core/Renderer2 – yjimk Oct 04 '17 at 21:58
  • This won't work server-side as there's no `document` there. – mgol Nov 30 '17 at 15:16
  • 4
    As an update to @Neph : importing DOCUMENT from platform-browser is deprecated. Use @angular/common instead. – Pieter De Bie Jun 20 '18 at 07:35
  • 2
    you have to `import { Component, OnInit,Renderer2 } from '@angular/core';` – suhailvs Jun 29 '18 at 05:23
  • minor addition: do the addClass stuff in the ngOnit. Better testable AND, i remove the modal on close. So the oninit is called each time anyway. And the constructor might be invoked in different situations (I inject the modal component through a service, that will cause the add class to fire early) – Remco Vlierman Feb 22 '19 at 12:46
  • How am I supposed to instantiate this class? I am expecting to create it like `dialog = new Dialog();`, so it complains about the two missing parameters. – Marcos Lima Jul 16 '19 at 12:51
  • thanks, @PieterDeBie very important note for those who choose to use the Document method – Code with Benji Aug 20 '22 at 06:39
76

I think the best way to do it is a combination of both approaches by DaniS above: Using the renderer to actually set / remove the class, but also using the document injector, so it is not strongly dependant on the window.document but that can be replaced dynamically (e.g. when rendering server-side). So the whole code would look like this:

import { DOCUMENT } from '@angular/common';
import { Component, Inject, OnDestroy, OnInit, Renderer2 } from '@angular/core';

@Component({ /* ... */ })
export class MyModalComponent implements OnInit, OnDestroy {
    constructor (
        @Inject(DOCUMENT) private document: Document,
        private renderer: Renderer2,
    ) { }

    ngOnInit(): void {
        this.renderer.addClass(this.document.body, 'embedded-body');
    }

    ngOnDestroy(): void {
        this.renderer.removeClass(this.document.body, 'embedded-body');
    }
}
DHainzl
  • 1,043
  • 9
  • 10