Thjen
Thjen

Reputation: 527

Understanding scope in Angular

I'm pretty new to angular, so I hope this question isn't too basic. I'm trying to run a function in angular, but I seem to have an issue with the scope of my variables. This is what it looks like:

export class Skulder14q1Component {

  button1 = document.getElementById('button1');
  button2 = document.getElementById('button2');
  button3 = document.getElementById('button3');
  button4 = document.getElementById('button4');
  showallbutton = document.getElementById('showallbutton');

  onShowAllOptions() {
    this.button1.classList.toggle('hide');
    this.button2.classList.toggle('hide');
    this.button3.classList.toggle('hide');
    this.button4.classList.toggle('hide');
    this.showallbutton.classList.add('hide');
  }

Now the function is run by the press of a button, but when pressing the button, the console log returns this:

> Skulder14q1Component.html:12 ERROR TypeError: Cannot read property 'classList' of null
    at Skulder14q1Component.onShowAllOptions (skulder14q1.component.ts:28)
    at Object.eval [as handleEvent] (Skulder14q1Component.html:15)
    at handleEvent (core.js:10251)
    at callWithDebugContext (core.js:11344)
    at Object.debugHandleEvent [as handleEvent] (core.js:11047)
    at dispatchEvent (core.js:7710)
    at core.js:8154
    at HTMLButtonElement.<anonymous> (platform-browser.js:988)
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:421)
    at Object.onInvokeTask (core.js:3811)

Changing to this code instead works:

onShowAllOptions() {
  const button1 = document.getElementById('button1');
  const button2 = document.getElementById('button2');
  const button3 = document.getElementById('button3');
  const button4 = document.getElementById('button4');
  const showallbutton = document.getElementById('showallbutton');
  button1.classList.toggle('hide');
  button2.classList.toggle('hide');
  button3.classList.toggle('hide');
  button4.classList.toggle('hide');
  showallbutton.classList.add('hide');
}

I feel like I'm missing something basic, and I hope you can help me out. Thanks!

Upvotes: 1

Views: 63

Answers (1)

Marian
Marian

Reputation: 4079

First of all, I would recommend against managing the DOM too much through standard browser methods like document.getElementById, etc. Using the Angular abstraction is recommended, you can read more here.

This out of the way, we can get to the why.

When the Skulder14q1Component is initialized, the DOM (the HTML elements / the template) for it has not been created yet. So when you try to find the buttons, they are not there yet, so getElementById returns null.

Components in Angular go through a life-cycle, and you can hook into this life-cycle by adding certain methods to your class. you can read more about those in the docs on lifecycle hooks.

In your case, you need the AfterViewInit hook. To implement it, we need to do two things:

  1. implement the interface AfterViewInit

    class Skulder14q1Component implements AfterViewInit {
    ...
    }
    
  2. Add the method

    class Skulder14q1Component implements AfterViewInit {
        button1: HTMLElement;
        button2: HTMLElement;
        button3: HTMLElement;
        button4: HTMLElement;
        showAllButton: HTMLElement;
    
        ngAfterViewInit() {
            this.button1 = document.getElementById('button1');
            this.button2 = document.getElementById('button2');
            this.button3 = document.getElementById('button3');
            this.button4 = document.getElementById('button4');
            this.showallbutton = document.getElementById('showallbutton');
        }
    
        // when this is called later, the buttons will have been set up
        onShowAllOptions() {
            this.button1.classList.toggle('hide');
            this.button2.classList.toggle('hide');
            this.button3.classList.toggle('hide');
            this.button4.classList.toggle('hide');
            this.showallbutton.classList.add('hide');
        }
    }
    

The code in the method will run after Angular has prepared the template for you.


Edit: In my experience, ViewChild is not used that often, since you want the elements inside your templates (especially if they are other components) to be responsible for their own behaviour, and not control everything from an omnipotent class.

Here is a StackBlitz I made for your use-case precisely.

Upvotes: 1

Related Questions