Angular Change Detection in UI - How to succeed it?

210 Views Asked by At

I am having 2 components that do not have any parent-child relationship.

One component is a simple login.component.html:

<form #loginForm="ngForm" (ngSubmit)="onSubmit()">
  <div class="login-container">
    <mat-form-field appearance="fill">
      <mat-label>Enter your username</mat-label>
      <input
        matInput
        name="username"
        type="text"
        [(ngModel)]="username"
        #usernameCtrl="ngModel"
        required
      />
    </mat-form-field>
    <mat-form-field appearance="fill">
      <mat-label>Enter your password</mat-label>
      <input
        matInput
        name="password"
        [type]="hide ? 'password' : 'text'"
        [(ngModel)]="password"
        #passwordCtrl="ngModel"
        required
      />
      <button
        mat-icon-button
        matSuffix
        (click)="hide = !hide"
        [attr.aria-label]="'Hide password'"
        [attr.aria-pressed]="hide"
      >
        <mat-icon>{{ hide ? "visibility_off" : "visibility" }}</mat-icon>
      </button>
    </mat-form-field>
  </div>
  <div>
    <button
      mat-button
      color="primary"
      [disabled]="!loginForm.form.valid"
      type="submit"
    >
      Login
    </button>
  </div>
</form>

and the other is a menu-bar.component.html:

<div>
    <h1>Hello world!</h1>
    <h2>I am visible in world all of the time!</h2>
    <div *ngIf="loginSuccessfull"> <menu-app> </menu-app> </div>
</div>

When the user logs in successfully, I want to show my menu-app.component. What I did is that I defined a boolean variable that set it true when user logs in.

In login.component.ts:

onSubmit() {
    if (this.username === 'a' && this.password === 'a') {
      console.log('login successful');
      this.loginService.updateLoginSuccessfull(true);
    }

In menu-bar.component.ts:

ngOnInit() {
    this.loginService.loginSuccessfull.subscribe(loginSuccessfull => this.loginSuccessfull = loginSuccessfull);
  }

  ngAfterContentChecked() {
    this.loginService.loginSuccessfull.subscribe(loginSuccessfull => this.loginSuccessfull = loginSuccessfull);
  }

As you can understand, both components are calling a service which is defined login.service.ts:

export class LoginService {

  private onLogin = false;
  private onLoginSuccessfull = new BehaviorSubject(this.onLogin);
  loginSuccessfull = this.onLoginSuccessfull.asObservable();

  constructor() { }

  updateLoginSuccessfull(loginSuccessfull: boolean) {
    this.onLoginSuccessfull.next(loginSuccessfull);
  }

}

What am I missing? Is there any other way to do this change detection dynamically? Thanks! The expected behaviour is to show the <menu-app></menu-app> only when the user logs in. If the user does not log in, he should not see the component. The way I implemented does not show the component even though the use logs in successfully.

1

There are 1 best solutions below

0
Chris Danna On

The components look mostly fine. Are you using OnPush change detection for the menu-bar component? You are also creating 2 subscriptions in the menu-bar component in ngOnInit and ngAfterContentChecked. This will update the loginSuccessful field twice each time a value is emitted from the service. Neither of these are needed if you use the async pipe. The async pipe automatically subscribes (and unsubscribes) to the Observable, will trigger change detection when using OnPush, and reduces the code needed in the component.

menu-bar.component.ts

@Component({
  selector: 'menu-bar',
  templateUrl: './menu-bar.component.html',
  styleUrls: ['./menu-bar.component.css'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class MenuBarComponent {
  loggedIn$ = this.service.loginSuccessful;

  constructor(private service: LoginService) { }
}
<div>
  <h1>Hello world!</h1>
  <h2>I am visible in world all of the time!</h2>
  <div *ngIf="loggedIn$ | async"> <menu-app> </menu-app> </div>
</div>

https://stackblitz.com/edit/angular-ivy-pzss5d