Avoid evaluating input properties on Angular directives after init

Issue

I have a very simple Angular attribute directive that looks something like this:

import { Directive, Input, OnInit } from "@angular/core";

@Directive({
  selector: "[foo]",
})
export class FooDirective implements OnInit {
  @Input() public foo: string;

  public ngOnInit(): void {
    // Do something here once
  }
}

It will be used like this:

<div [foo]="bar"></div>

bar might be a simple string, but could also be a string getter that is relatively expensive to run.
I would therefore like to read the value once on initialization, but ignore it afterwards.
I know that the ngOnInit is only called once, but change detection will cause the bar property to be called repeatedly (depending on how the containing component works).

Using @Attributes would have been nice, since they only get evaluated once.
But it doesn’t seem like @Attribute works for directives (with the same name?):

import { Directive, OnInit, Attribute } from "@angular/core";

@Directive({
  selector: "[foo]",
})
export class FooDirective implements OnInit {
  public constructor(
    @Attribute("foo")
    private readonly foo: string
  ) {}

  public ngOnInit(): void {
    // Do something with this.foo here
  }
}

How do I avoid that the input property is evaluated more than once?
I would prefer to unsubscribe from any changes, so to speak.

Directives don’t seem to support ChangeDetectionStrategy.OnPush, but something similar could be nice.

Solution

I guess this is not a problem to solve on the directive definition side. Every time that change detection runs on the component that host that div the expensive calculation on the string getter takes place.
So I would not use that expensive string getter in the template, instead I would work around to assign that result to a variable in my class and use that variable on the template. You could do the expensive calculation on the ngOnInit or other lifecycle hook that fits better your conditions, in the component that host that div.

Just for clarification, this is the div that I’m talking about:

  <div [foo]="bar"></div>

To quickly sumarize, my suggestion is use the lifecycle hooks in the components that host this div to get the value, pass to a variable, and use that variable on the template instead of the expensive string getter.

Answered By – Arthur Pérez

Answer Checked By – Terry (AngularFixing Volunteer)

Leave a Reply

Your email address will not be published.