Justin Davis
Justin Davis

Reputation: 305

Update DOM when Angular 2 component property changes

I just recently moved from Angular 1 to Angular 4, and I'm having a bit of a tough time understanding why the DOM isn't updating when a component property is updated. I've searched and read countless posts, and can't find anything that seems to answer this.

I have an app that has a component for displaying error messages, called MessageComponent:

import { Component, OnInit } from '@angular/core';                                                                   
                                                                                                                         
 @Component({                                                                                                               
   selector: 'message',                                                                                                     
   templateUrl: './message.component.html',                                                                                 
   styleUrls: ['./message.component.css']                                                                                   
 })                                                                                                                         
                                                                                                                         
export class MessageComponent implements OnInit {                                                                          
                                                                                                                         
  messages: Array<string>;                                                                                                 
                                                                                                                         
  constructor() {                                                                                                          
                                                                                                                         
  }                                                                                                                        
                                                                                                                         
  ngOnInit() {                                                                                                             
    this.messages = ['My messages'];                                                                                       
  }                                                                                                                        
                                                                                                                         
  /* Takes an array of messages */                                                                                         
                                                                                                                         
  showErrors(errors) {                                                                                                     
    this.messages = errors;                                                                                                
  }
}

The template is straightforward:

<div class="message">                                                                                                      
  <p>Messages go here</p>                                                                                                  
  <div class="error">                                                                                                      
     <ul>                                                                                                                 
       <li *ngFor="let message of messages">{{message}}</li>                                                              
     </ul>                                                                                                                
  </div>                                                                                                                   
</div>

And I'm calling the showErrors method from another component:

  import { Component, OnInit } from '@angular/core';                                                                         

  import { MessageComponent } from  '../message/message.component';                                                          
                                                                                                                             
  @Component({                                                                                                               
    selector: 'app-signup',                                                                                                  
    templateUrl: './signup.component.html',                                                                                  
    styleUrls: ['./signup.component.css']                                                                                    
  })                                                                                                                         
  export class SignupComponent implements OnInit {                                                                           
                                                                                                                             
    email: string;                                                                                                           
    password: string;                                                                                                        
    cardNumber: string;                                                                                                      
    expiryMonth: string;                                                                                                     
    expiryYear: string;                                                                                                      
    cvc: string;                                                                                                             
    plan: string;                                                                                                            
                                                                                                                             
    constructor(private activatedRoute: ActivatedRoute, private auth: Auth, private message: MessageComponent) { }           
                                                                                                                             
    signupUser() {                                                                                                           
      // do stuff here, then call back with status                                                                                                                                                                                   
      }, (status: number, response: any) => {                                                                                
        if (status == 200) {                                                                                                 
          // yay, do success stuff                                                                                                
        } else {                                                                                                             
          console.log('Error', response.error.message);                                                                      
          this.message.showErrors([response.error.message]);                                                                 
        }                                                                                                                    
      });                                                                                                                    
    } 

The signupUser() method is called from this form, which is signup.component.html, the template for the SignupComponent:

    <message></message>                                                                                                      
    <h1>Signup</h1>                                                                                                          
    <form (submit)="signupUser()">                                                                                           
      <div class="row">                                                                                                      
        <div class="small-12 columns">                                                                                       
          <label>Email address                                                                                               
              <input type="text" [(ngModel)]="email" name="email">                                                           
          </label>                                                                                                           
        </div>                                                                                                               
      </div>                                                                                                                 
      <div class="row">                                                                                                      
        <div class="small-12 columns">                                                                                       
          <label>Password                                                                                                    
            <input type="password" [(ngModel)]="password" name="password">                                                   
          </label>                                                                                                           
        </div>                                                                                                               
      </div>                                                                                                                 
      <div class="row">                                                                                                      
        <div class="small-12 columns">                                                                                       
          <label>Card Number                                                                                                 
              <input type="text" [(ngModel)]="cardNumber" name="card-number" data-stripe="number">                           
          </label>                                                                                                           
        </div>                                                                                                               
      </div>                                                                                                                 
      <div class="row">                                                                                                      
        <div class="small-6 columns">                                                                                        
          <label>Expiration Date (MM/YY)                                                                                     
            <span><input type="text" size="2" [(ngModel)]="expiryMonth" name="expiry-month" placeholder="MM"><input type="text" size="2" [(ngModel)]="expiryYear" name="expiry-year" placeholder="YY"></span>                                                      
          </label>                                                                                                           
        </div>                                                                                                               
        <div class="small-6 columns">                                                                                        
          <label>CVC                                                                                                        
            <input type="text" [(ngModel)]="cvc" name="cvc">                                                                
          </label>                                                                                                          
        </div>                                                                                                              
      </div>                                                                                                                
      <input type="submit" value="Sign Up">                                                                                 
    </form>  

Hopefully that's enough to get the gist. What I can observe is that this.messages inside the MessagesComponent DOES indeed change to the correct value when called, however, the DOM doesn't update.

What am I missing here? I feel like I have some fundamental misunderstanding about how Angular detects the changes to the component property and propagates that to the DOM, but I have no clue what it is I'm missing.

Thanks for any help!

Upvotes: 1

Views: 15649

Answers (2)

Abel Valdez
Abel Valdez

Reputation: 2408

I had this issue today and I resolved like this way:

import { ChangeDetectorRef } from '@angular/core';

... it is going to notify changes every one second

constructor(private ref: ChangeDetectorRef){

     setInterval(() => {
       this.ref.detectChanges()
     }, 1000);

 }

... if you whant just notify ones, you need to use this line after u need it

 this.ref.detectChanges()

Upvotes: 4

Picci
Picci

Reputation: 17762

I would rearrange the code as follows. The whole idea is that MessageComponent has an Input property, to be used to receive the messages it has to show, and that SignupComponent passes the error messages to the MessageComponent using template binding sintax.

MessageComponent

import { Component, OnInit, Input } from '@angular/core';                                                                   

 @Component({                                                                                                               
   selector: 'message',                                                                                                     
   templateUrl: './message.component.html',                                                                                 
   styleUrls: ['./message.component.css']                                                                                   
 })                                                                                                                         

export class MessageComponent implements OnInit {                                                                          

  @Input() messages: Array<string>; // Input property                                                                                                

  constructor() {                                                                                                          
  }                                                                                                                        

  ngOnInit() {                                                                                                                                                                                                    
  }  
}

SignupComponent

import { Component, OnInit } from '@angular/core';                                                                         

  @Component({                                                                                                               
    selector: 'app-signup',                                                                                                  
    templateUrl: './signup.component.html',                                                                                  
    styleUrls: ['./signup.component.css']                                                                                    
  })                                                                                                                         
  export class SignupComponent implements OnInit {                                                                           

    email: string;                                                                                                           
    password: string;                                                                                                        
    cardNumber: string;                                                                                                      
    expiryMonth: string;                                                                                                     
    expiryYear: string;                                                                                                      
    cvc: string;                                                                                                             
    plan: string; 

    messages: Array<string>;  // property used to pass the messages to the MessageComponent                                                                                                         

    constructor(private activatedRoute: ActivatedRoute, private auth: Auth) { }           

    signupUser() {                                                                                                           
      // do stuff here, then call back with status                                                                                                                                                                                   
      }, (status: number, response: any) => {                                                                                
        if (status == 200) {                                                                                                 
          // yay, do success stuff                                                                                                
        } else {                                                                                                             
          console.log('Error', response.error.message);                                                                      
          this.messages = [response.error.message];                                                                 
        }                                                                                                                    
      });                                                                                                                    
    } 

SignupComponent template

<!-- TEMPLATE BINDING -->
<message [messages]="messages"></message>                                                                                                      
<h1>Signup</h1>                                                                                                          
<form (submit)="signupUser()">   
..... the rest of your code
</form>

Upvotes: 0

Related Questions