*ngIf does not react to boolean change
Asked Answered
C

5

8

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;
  }
}
Condominium answered 24/9, 2017 at 10:0 Comment(17)
It works for me. can you provide more of the code?Adamski
Open your browser's debug console (usually F12 key in most browser's). You probably have a template error somewhere else (unrelated to this) which can cause the template code to stop working.Barriebarrientos
Check css for opacity:0; display:none; visibility:hidden...Schramke
try *ngIf="wrongCredentialsInserted == true", it should workCentrifugal
@AlejandroCamba Why do you think *ngIf="wrongCredentialsInserted == true" should work, while *ngIf="wrongCredentialsInserted" and *ngIf="wrongCredentialsInserted === true" do not?Agential
=== checks for value and type, == checks value. in your ngif you are comparing the value that holds your wrongCredentialsInserted property to the value "true"Centrifugal
@Barriebarrientos Nah, the console doesn't show any errors.Condominium
@Agential He copied the accepted answer of some other thread. I already found that one before.Condominium
Could you set up demo to show the behaviour? Meantime, I would suggest to remove all class="" that you have in that section of html and testSchramke
Here is a start: stackblitz.com/edit/…Schramke
how is onSubmit triggered?Preserve
@Schramke I tried my best with Plunker (only used that tool to see working examples until now): plnkr.co/edit/PlCUAZNyduc19VAKvm7bCondominium
@AngularInDepth.com By the button in a UI. Don't worry, the code does actually get executed and the variable changes its state. It's just not propagated to the UI. It somehow does not get updates. As soon as other validations or stuff on the component kick in, the updates really happens (as I just noticed).Condominium
put a breakpoint inside this method github.com/angular/angular/blob/…. does it hit it after the function onSubmit is executed?Preserve
I fixed the plunker, it works: plnkr.co/edit/9jiXxKY8gT3OTarYtbOd?p=preview. See also the stackblitz demo (it my above comment). You should look closer to your formSchramke
@Schramke Thank you very much for your help! The plunker is fixed, but my real app still doesn't work. It seems like there is some update-triggering problem. There is no event fired or something that changes the view directly when the boolean changes. To clear up with confusion, I added the full files. Maybe you can take a look and see what's bad there? I also have the code on github, but I didn't commit the recent change with those events. github.com/codepleb/hpCondominium
I found out, that the problem is from the formsmodule. If I put the part outside of the <form> tags, it works without problems. Inside it seems to get overriden or something.Condominium
S
4

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.

Stickseed answered 24/9, 2017 at 11:36 Comment(1)
Thank you very much, but none of those points seems to be valid in my case. I also checked the code by using breakpoints (I did console.log() before to check if the variable really changes). No errors, and I didn't mess with the change detection. I'm not sure how exactly I should check the CSS for "hidden", etc, but my CSS is basically non-existing and all other *ngIf on the same component work (although they are checked over form validations and not single attributes).Condominium
W
2

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.

Writhe answered 28/1, 2020 at 10:35 Comment(1)
It can be simplified as: yourVariable = booleanValue === "true"Diarmuid
B
2

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();
    }
    
Blocking answered 28/5, 2020 at 2:7 Comment(0)
C
0

The problem in my case was, that the *ngIf appeared within a <form></form> tag. If I placed it outside, everything worked. I know that angular uses the form tags to inject their form-module, but I didn't expect such a behaviour. My validation-error therefore only popped up, when the form itself had other validation errors.

Condominium answered 24/9, 2017 at 20:50 Comment(1)
There's no reason for ngIf not to work inside a form tag.Surgery
M
0

Somewhere Validations are triggering that bool variable to that state. (Like 'get' function in angular reactive form) and please use console.log() to evaluate the variable state

Multifoliate answered 29/11, 2018 at 11:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.