0

I'm having the issue where I'm getting the following error when trying to access a html element by ID. I'm attempting to access the classList when the user clicks a button to apply a different style class to the element. The class list and element in general are always null, unless I switch the viewEncapsulation back to the default emulated setting.

Error Message:

TypeError: Cannot read property 'classList' of null

Problem: I'm attempting to have this specific component not inherit a 3rd party SCSS file that I've imported into the project via the angular.json file. When I use default ViewEncapsulation.Emulated, the component is inheriting the global SCSS styles which is conflicting with some of the components styling.

The file structure is normal, I'm putting the SCSS and HTML in the corresponding files, then importing them into the component.

I feel like this should be a common theme to see with larger projects. If there's a different way to go about switching the styling of elements on events please let me know.

Component .ts:

import { Component, ViewEncapsulation, OnInit } from '@angular/core';

@Component({
  encapsulation: ViewEncapsulation.Native,
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
  title = 'scssTesting';

  constructor() { }

  step: string = 'step1';

  step1: any;
  step2: any;
  step3: any;


  ngOnInit() {

  }

  next() {

    this.step1 = document.getElementById('step1');
    this.step2 = document.getElementById('step2');
    this.step3 = document.getElementById('step3');


    if (this.step === 'step1') {
      this.step = 'step2';
      this.step1.classList.remove("is-active");
      this.step1.classList.add("is-complete");
      this.step2.classList.add("is-active");

    } else if (this.step === 'step2') {
        ....
  }
}

Component .scss

.progress {
  position: relative;
  display: flex;
  .........
}

Component .html

<div class="container">
    <div class="progress">
        <div class="progress-track"></div>
        <div id="step1" class="progress-step">
            Step One
        </div>
        <div id="step2" class="progress-step">
            Step Two
        </div>
        <div id="step3" class="progress-step">
            Step Three
        </div>
    </div>
    <button (click)="next()">Next Step</button>
</div>
4
  • have you tried using ElementRef? Commented May 16, 2019 at 20:56
  • Don't use document. There are many ways to access view elements. Please read the documentation for ViewChild and ViewChildren. Commented May 16, 2019 at 20:59
  • I think it is cause of this too @cgTag, test it and tell us please Commented May 16, 2019 at 21:00
  • 1
    First of all, try using @ViewChild() to get an ElementRef like @Barbu Barbu and @cgTag suggested. Second of all, if you're already using document, just know that it's unsafe to use document directly in Angular. You should use dependency injection and @Inject(DOCUMENT) (import Inject from '@angular/core' and DOCUMENT from '@angular/common'). Commented May 16, 2019 at 21:09

2 Answers 2

1

The easiest way I could think of to access a HTML element is using Angulars ElementRef. You may read more about that here. Please use this with caution! Below an example on how to utilize it in your case:

In your app.component.html

<div #myelement>This is Pink</div>

In your app.component.ts

import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements AfterViewInit {
  @ViewChild('myelement') el: ElementRef;

  ngAfterViewInit() {
    this.el.nativeElement.classList.add("myclass");
  }
}

In your app.component.css

.myclass {
    color: pink;
    font-weight: bold;
}

EDIT:

For pure styling, as already mentioned by @SnorreDan, it would be better to use Angulars [ngClass] directive. You may have a look at how do this with the following example:

In your app.component.html

<div [ngClass]="myColorClass">This may change color!</div>

<button (click)="makePink()">Pink</button>
<button (click)="makeGreen()">Green</button>

In your app.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  myColorClass: string

  makePink() {
    this.myColorClass = 'pink';
  }

  makeGreen() {
    this.myColorClass = 'green';
  }
}

In your app.component.css

.pink {
    color: pink;
    font-weight: bold;
}

.green {
    color: green;
    font-weight: bold;
}

I hope that helps!

Sign up to request clarification or add additional context in comments.

2 Comments

Awesome! This ended up working, but, I ended up implementing the above solution with ngClass due to the potential dangers that come with Element Ref.
For pure styling that is the better solution anyways. My approach was more focused on accessing the HTML element. I am glad you solved your problem.
1

I think you are asking two different questions:

1. How do you dynamically add and remove classes in Angular:

In Angular you should try to avoid using document directly. A good way of adding a class based on a condition is with the ngClass directive, you can read more about it here: https://angular.io/guide/template-syntax#ngclass

Another way of adding class dynamically is by grabbing the element from the template using ViewChild and then setting a class on the element by using the angular Renderer (you find more info if you google it)

2. How to have my component not inherit the global CSS from the Angular.json file

I can not think of a solution for this. Using the encapsulation strategies like ViewEncapsulation.None and ViewEncapsulation.Emulated is not going to solve this as they only influence the classes in this component not spreading to other components. It does not influence global classes coming in to this component.

1 Comment

Your assumption is correct, this can be split into two questions. I'll take a peek at the documentation for the first solution you provided. As for the second question in your answer, when I use viewEncapsulation.Native, why do I not inherit any outside or global scss(css) styling? What is the main purpose of using Native vs the default Emulated setting?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.