Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular LifecyleHooks and when is @ViewChild valid?

I am a bit perplexed. I thought I basically understood the lifecycle of angular components, but THEN I ran into an interesting case, where the @Input of a component was undefined.

In this case, I have TWO instances of my application. The given component: LogoutWarning is shown when a logout is pending and the user can then refresh the login token and continue. That works fine.

The instance comes up when the first application renews the login token, and hides showing the LogoutWarning component. All is good in this first application.

The second application is also showing the LogoutWarning component since it detected the login token was going to become expired and shows a LogoutWarning modal, It then notices that the login token was been renewed and calls LogoutWarning.cancel() to hide the modal. In this case, this.logoutWarningModal.hide() explodes because the this.logoutWarningModal is UNDEFINED, even though the lifecyle ngAfterViewInit() shows that this.logoutWarningModal is defined.

I am an very surprised at this point. If I simply check that this.logoutWarningModal is undefined and not call this.logoutWarningModal.hide() all works as expected. But why is this.logoutWarningModal undefined here???

The code for the LoutWarning component is simple:

@Component({
  selector: 'logoutWarning',
  template: `
    <div bsModal #logoutWarning="bs-modal" [config]="{ show: true }" class="modal fade" tabindex="-1" role="dialog"
     aria-labelledby="new application version avaliable" aria-hidden="true" (onHidden)="hidden()">
      <div class="modal-dialog modal-sm">
        <div class="modal-content">
          <div class="modal-header">
            <div class="app_header">
              <div class="app_icon"><img src="assets/images/tracker_hi_res_512.gif"></div>
              <div class="app_name">Tracker</div>
            </div>
          </div>
          <div class="modal-body center-message">
            <span class="bold">You will be logged out in </span>
            <span><strong>{{minutes | async}}</strong> minutes!</span>
          </div>
          <div class="modal-footer">
            <button class="btn btn-primary" (click)="ok()">Stay Logged In</button>
          </div>
        </div>
      </div>
</div>
`
})
export class LogoutWarning implements OnInit {
  @ViewChild('logoutWarning') public logoutWarningModal: ModalDirective;
  @Input() minutes: string;

  constructor(private userCondition: UserCondition, private broadcaster: BroadCaster, private versionS: Version, private auth: AuthService) {
  }

  public ok(): void {
    this.auth.renewLogin(this.userCondition, this.userCondition.minutesDurationUntilLogout.getValue());
    this.logoutWarningModal.hide();
   }

  public cancel() {
    if (this.userCondition.bShowLogoutWarning) {
        this.logoutWarningModal.hide();
    }
  }

  public hidden() {
    // reset the state of the dialog so that it can be reshown.
    this.userCondition.bShowLogoutWarning = false;
    this.logoutWarningModal.show();
  }


  public ngOnInit() {
    console.log('ngOnInit()');
    console.log('this:', this, this.logoutWarningModal);
  }

}

and the component is shown when userCondtion.bShowLogoutWarning is true and shown in my main application. The relevant section of html is:

<logoutWarning *ngIf="userCondition.bShowLogoutWarning" [minutes]="userCondition.minutesUntilLogout"></logoutWarning>

Both applications log:

 ngOnInit()    logoutWarning.component.ts?f8b4:55 
 this: LogoutWarning {userCondition: UserCondition, broadcaster: BroadCaster, versionS: Version, auth: AuthService, logoutWarningModal: ModalDirective, …} 
 ModalDirective {onShow: EventEmitter, onShown: EventEmitter, onHide: EventEmitter, onHidden: EventEmitter, isAnimated: true, …} 
    userCondition.service.ts?0111:164 

Then after the first application renews the login token, the second application shows:

 this: LogoutWarning {userCondition: UserCondition, broadcaster: BroadCaster, versionS: Version, auth: AuthService} undefined

where this.logoutWarningModal is UNDEFINED!

Thanks for your insight!

like image 945
JoelParke Avatar asked Sep 29 '17 18:09

JoelParke


1 Answers

@ViewChild would return undefined till it's initialised and it's the case in OnInit(). Check it in AfterViewInit() instead:

ngAfterViewInit() {
   console.log('ngAfterViewInit()');
   console.log('this:', this, this.logoutWarningModal);
    // this will returns the object
}
like image 58
Vega Avatar answered Nov 20 '22 11:11

Vega