7

I am writing a directive which has 2 @Input() variables and takes value from who ever using that directive like components.

Everything is fine. The only problem is that when there is a Observable.subscribe in the constructor, then @Input values are available in constructor and without Observable.subscribe the @Input() variable values are undefined

I know the better way to get @Input() variable of a directive is to access them inside a life cycle hook like ngOnInit or ngOnChange but my question is: why is this available in some cases and not available in other cases in a directive.

<div authorizedme
     [permission]="'manager'"
     [auth]="department"
     class="col-2 data-object-link">your salary is $90,000,0000

directive

If inside the directive's constructor below the subscribe code is there, then permission and auth is available, and if it's commented out then both of the @Input() variables are undefined.

this.userService.getUser().subscribe((user) => {
  this.user = user;
  if (this.authorized()) {
    this.elementRef.nativeElement.style.display = 'block';
  }
});

Below is the entire directive code

@Directive({
  selector: '[authorizedme]'
})
export class AuthorizedDirective implements OnInit {

  @Input() permission: string;
  @Input() auth: string;
  private user: any;

  constructor(private elementRef: ElementRef, private currentUserService: userService) {
    this.elementRef.nativeElement.style.display = 'none';
    this.currentUser = this.userService.userAuthorizations;

    /*this.currentUserService.getUser().subscribe((user) => {
      this.user = user;
      if (this.authorized()) {
        this.elementRef.nativeElement.style.display = 'block';
      }
    });*/

  }

  public authorized() {
   return this.user || authorize;
  }
}
Chuck
  • 248
  • 5
  • 13
Aniruddha Das
  • 20,520
  • 23
  • 96
  • 132

2 Answers2

7

Don't try to access @Input()s in the constructor, do it in the ngOnInit life-cycle hook. The constructor has almost nothing to do with the Angular application life-cycle.

From another question:

Implement this interface to execute custom initialization logic after your directive's data-bound properties have been initialized. ngOnInit is called right after the directive's data-bound properties have been checked for the first time, and before any of its children have been checked. It is invoked only once when the directive is instantiated.

Furthermore, the constructor is not an Angular feature, it's a TypeScript feature. To quote directly from Günter in his comment to the above-linked answer:

Contstructors are not related to Angular2, they are a TypeScript feature. Lifecycle hooks are called by Angular after some initialization took place or when some event happend to allow the component act on certain situations and to give it the chance to do some tasks at proper times.

It's to be expected, then, that your data-bound properties are not yet available in the constructor.

msanford
  • 11,803
  • 11
  • 66
  • 93
  • 1
    I use constructors in my very large application heavily because I construct the entire components tree before change detection. So constructor is very important for Angular. Read [Here is how to get ViewContainerRef before @ViewChild query is evaluated](https://hackernoon.com/here-is-how-to-get-viewcontainerref-before-viewchild-query-is-evaluated-f649e51315fb) for more details. – Max Koretskyi Jul 19 '17 at 19:47
  • Interesting @Maximus! Thanks. :) – msanford Jul 19 '17 at 19:55
  • @Maximus Oh, I went down the rabbit hole, don't worry. I love HackerNoon; it carries lots of 'street cred' with me. Keep it up! – msanford Jul 21 '17 at 15:46
  • 1
    I'm actually posting now in my own publication https://blog.angularindepth.com/. Do follow) – Max Koretskyi Jul 21 '17 at 15:47
5

This is a classic case of lifecycle hooks and asynchronous processing!

Taking it step by step:

  1. Angular instantiates the directive
  2. Angular processing things
  3. While processing it calls LifeCycle hooks to let you know what is happening.

Putting it all together what this means is Angular instantiates your directive AuthorizedDirective with its constructor function. In the context of that function permission, auth, and currentUser are all undefined because you have not set values to them yet. You then subscribe to changes that will happen in the service which is basically registering a function to occur AFTER we process observables.

As it happens observables, are not processed until a tick of the zone they are in for Angular.

The reason permission / auth are set in ngOnInit is because after Angular has instantiated your object, it parses it to see if you have any input or output values to use. If you do, it looks for the corresponding things that were set on the element and sets them before calling ngOnInit which is all before a zone tick happens.

So this is why you have the value in the subscribe and in ngOnInit but you don't have the value in the constructor itself.

KeniSteward
  • 179
  • 9
  • 2
    _which is all before a zone tick happens._ - actually this is part of change detection, it doesn't happen **before** the `tick`, it's **during** the tick. All lifecycle hooks are part of change detection. Read [Everything you need to know about change detection in Angular](https://hackernoon.com/everything-you-need-to-know-about-change-detection-in-angular-8006c51d206f) for more details – Max Koretskyi Jul 19 '17 at 19:50