*ngIf does not react to boolean change

codepleb picture codepleb · Sep 24, 2017 · Viewed 7.2k times · Source

Here are some pieces of code. This same pattern (afaik) works for the hero tutorial.

login.component.html:

<div class="four wide column middle aligned" *ngIf="wrongCredentialsInserted">
     <div class="ui error message">Invalid credentials
     </div>
 </div>

login.component.ts:

wrongCredentialsInserted: boolean = false;

//...      

onSubmit(login: LoginDto): void {
        console.log(`User '${login.username}' attempts to login...`);
        if (this.authService.login(login.username, login.password)) {
          this.location.back();
        } else {
          this.wrongCredentialsInserted = true; //This line gets executed!
        }
      }

The message doesn't get displayed, even though I set wrongCredentialsInserted to true. It gets set to true, I already validated that. I also tried things like *ngIf="wrongCredentialsInserted === true", because I read that somewhere else, but it didn't work. I read that this could be related to "one way dataflow, starting with Angular 2", but I know that we were able to do such things in A2+ projects in our company. AFAIK one way databinding only refers to component-component communication.

Any kind of help is highly appreciated.

EDIT: Since there seems to be a bit of confusion with the things I did, I post the whole files here.

login.component.ts

import {AbstractControl, FormBuilder, FormControl, FormGroup, Validators} from '@angular/forms';
import {routerTransition} from '../../router.transition';
import {Component} from '@angular/core';
import {AuthService} from '../auth.service';
import {LoginDto} from './login-dto';
import {Location} from '@angular/common';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css'],
  animations: [routerTransition()]
})
export class LoginComponent {

  private readonly USERNAME: string = 'username';
  private readonly PASSWORD: string = 'password';

  myForm: FormGroup;
  username: AbstractControl;
  password: AbstractControl;

  message: string;
  wrongCredentialsInserted = false;

  constructor(public fb: FormBuilder,
              public authService: AuthService,
              public location: Location) {
    this.message = '';

    this.myForm = fb.group({
      username: ['', Validators.required],
      password: ['', Validators.required]
    });

    this.username = this.myForm.controls[this.USERNAME];
    this.password = this.myForm.controls[this.PASSWORD];
  }

  onSubmit(login: LoginDto): void {
    console.log(`User '${login.username}' attempts to login...`);
    if (this.authService.login(login.username, login.password)) {
      this.location.back();
    } else {
      this.wrongCredentialsInserted = true;
    }
  }

  login(username: string, password: string): boolean {
    this.message = '';
    if (!this.authService.login(username, password)) {
      this.message = 'Incorrect credentials.';
      setTimeout(
        function () {
          this.message = '';
        }.bind(this), 2500);
    }
    return false;
  }

  logout(): boolean {
    this.authService.logout();
    return false;
  }

}

login.component.html

<div class="ui raised segment">
  <form [formGroup]="myForm"
        (ngSubmit)="onSubmit(myForm.value)"
        class="ui form"
        [class.error]="!myForm.valid">

    <div class="field"
         [class.error]="!username.valid && username.touched">
      <label for="username">Username:</label>
      <input type="text"
             id="username"
             placeholder="Username"
             [formControl]="username">
      <div *ngIf="username.hasError('required') && username.touched"
           class="ui error message">
        Username is required
      </div>
    </div>

    <div class="field"
         [class.error]="!password.valid && username.touched">
      <label for="password">Password:</label>
      <input type="text"
             id="password"
             placeholder="Password"
             [formControl]="password">
      <div *ngIf="password.hasError('required') && password.touched"
           class="ui error message">Password is required
      </div>
    </div>

    <div class="ui grid">

      <div class="two wide column middle aligned">
        <button type="submit"
        class="ui button"
        [class.disabled]="!myForm.valid">Submit
        </button>
      </div>

      <div class="fourteen wide column middle aligned" *ngIf="wrongCredentialsInserted">
        <div
          class="ui error message">Invalid credentials
          </div>
        </div>

      </div>

    </form>
  </div>

login.component.css: Empty

auth.service.ts:

@Injectable()
export class AuthService {

  constructor(private http: Http) {
  }

  login(user: string, password: string): boolean {
    if (user === 'user' && password === 'password') {
      localStorage.setItem('username', user);
      return true;
    }

    return false;
  }

  logout(): any {
    localStorage.removeItem('username');
  }

  getUser(): any {
    return localStorage.getItem('username');
  }

  isLoggedIn(): boolean {
    console.log(`Is user logged in? ' + ${this.getUser() !== null}`);
    return this.getUser() !== null;
  }
}

Answer

Lazar Ljubenović picture Lazar Ljubenović · Sep 24, 2017

There are a couple of possible reasons for *ngIf not reacting to change in the model.

Change detection is not running

You're using OnPush strategy on your component and changing the component state without manually triggering a CD cycle. Either turn back on automatic CD or trigger the CD manually by injecting the ChangeDetectorRef and using the method which suits your needs.

Styles are misleading you

It's possible that ngIf binding is working correctly and that the template is properly created and destroyed, but there are styles which visually obscure this, such as display: none, visibility: hidden, etc. Make sure to inspect the page by opening your browser's dev tools.

There was an uncaught error

Easy to miss if you do not have your console open while developing. There might've been an error which has broken your code and Angular cannot recover from it; thus preventing any further CD cycles to run and update the DOM. Be sure to have an open console to check for errors.

You're not even changing the model

Angular is a framework where you declaratively specify how you want the DOM to be created based on the model. You do this by writing templates. If your model does not change, the DOM won't either. Make sure that the correct piece of code is running. A quick way to do this is by placing a console.log near the code that changes the variable. You can also place a breakpoint in your browser's dev tools, or use a utility extension for Chrome such as Augury to inspect the model independently from the way it's rendered based on the template.