Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

*ngIf does not react to boolean change

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;
  }
}
like image 605
codepleb Avatar asked Sep 24 '17 10:09

codepleb


People also ask

What is the use of * in ngIf?

The *ngIf directive is most commonly used to conditionally show an inline template, as seen in the following example. The default else template is blank.

What is * in * ngIf?

The * syntax means that ngIf is a structural directive, meaning that it affects the structure of the page.

What is * in * ngIf in angular?

The ngIf Directive in Angular10 is used to remove or recreate a portion of HTML element based on an expression. If the expression inside it is false then the element is removed and if it is true then the element is added to the DOM.

Is Hidden faster than ngIf?

Both are the same in terms of performance, becouse both are angular common atributes (*ngIf === [ngIf]) and they work equal at angular rendering. Save this answer.


3 Answers

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.

like image 194
Lazar Ljubenović Avatar answered Sep 30 '22 18:09

Lazar Ljubenović


I had similar issue earlier with *ngIf not reating to change in value, but it was because my boolean was being sent as string from backend eg: "true" or "false" and the solution that I found worth was

yourVariable = booleanValue === "true" ? true : false

Hope this Helps.

like image 43
Neeraj Kamat Avatar answered Sep 30 '22 16:09

Neeraj Kamat


In my case this issue happened because the change was happening outside of Angular's zone. Running the code inside change detection cycles fix the issue. Few options you have in this case are

  • Wrap the third party function inside a zone.run (Inject NgZone to the controller)

    myFunctionRunByAThirdPartyCode(){
      this.ngZone.run(this.myFunction);
    }
    
  • Wrap the function inside a setTimeout

    myFunctionRunByAThirdPartyCode(){
      setTimeout(this.myFunction,0);
    }
    
  • Call detectChanges() manually (Inject ChangeDetectorRef to the controller)

    myFunctionRunByAThirdPartyCode(){
     this.changeDetectorRef.detectChanges();
    }
    
like image 42
sree Avatar answered Sep 30 '22 16:09

sree