Are you frustrated because objects in your directives are null?

Well I might have the answer.

And in this guide, I'll tell you what's going on.

Get out of the Constructor

It's probably because you're using the constructor to handle some initialization work.

And you're forgiven for doing that. I've noticed that a lot of the online tutorials about directives put work in the constructor.

But that ain't always gonna cut it.

Let's take a look at an example.

@Directive({
  selector: '[displayOutcomeStatus]'
})
export class DisplayOutcomeStatusDirective extends MultipleClassesBaseDirective implements OnInit {

  @Input('displayOutcomeStatus') activity: Activity;

  constructor(private activityService: ActivityService) {
    super();
    this._elementClass.push('badge');
    this.addExtraClass();
  }

  ngOnInit() {

  }

  private addExtraClass() {
    console.log("Activity is ", this.activity);
    if (this.activity) {
      let extraClass: string = null;
      let outcome: ActivityOutcome = this.activity.outcome;
      let status: string = this.activity.status;

      if (outcome && outcome.sentiment) {
        let sentiment = outcome.sentiment;

        if (sentiment == 'POSITIVE') extraClass = 'badge-success';
        else if (sentiment == 'NEGATIVE') extraClass = 'badge-error';
        else extraClass = 'badge-info';
      } else {
        if (status == 'COMPLETED') extraClass = 'badge-success';
        else if (this.activityService.isOverdue(this.activity)) extraClass = 'badge-error';
        else if (status == 'ON_HOLD') extraClass = 'badge-warning';
      }

      if (extraClass) this._elementClass.push(extraClass);
    }
  }
} 

As you can see, I'm doing "the work" in the constructor there.

And the directive is used in the HTML as follows:

<div *ngIf="row.outcome" [displayOutcomeStatus]="row">
   {{row.outcome.name}}
 </div>

So the displayOutcomeStatus input should set to the value of the row object at the component level.

Well if I run that code, I'll see that logging from the first line of the addExtraClass() method gives me this bad news:

Activity is undefined

But why? I've defined it in the component. I've bound it via @Input.

Why is it undefined?

Answer: because the view isn't initialized yet.

That's why you should avoid referencing component-level objects in the constructor.

Instead, move that same code to the ngOnInit() method.

  constructor(private activityService: ActivityService) {
    super();
  }

  ngOnInit() {
    this._elementClass.push('badge');
    this.addExtraClass();
  }

Now if I run that code again, I'll see that the Activity object is, in fact, defined.

A Couple of Takeaways

So let's draw some conclusions from this guide.

First, yes, you can use ngOnInit() with directives. You might think that directives are some kind of "special" class that can't implement OnInit.

But they can.

Next, stick with ngOnInit() for any initialization related to component objects. In fact, it's probably a great idea to avoid doing much of anything (except injecting dependencies) in the constructor.

Finally, familiarize yourself with the component lifecycle hooks and when they happen. That will help you avoid these kinds of challenges in the future.

Wrapping It Up

Hopefully this fixed your problem.

If not, then your object is undefined for another reason. Use some code tracing to find out what's going on.

Have fun!

Photo by Poppy Thomas Hill from Pexels