【问题标题】:Update DOM when Angular 2 component property changesAngular 2 组件属性更改时更新 DOM
【发布时间】:2017-05-21 19:18:00
【问题描述】:

最近刚从 Angular 1 迁移到 Angular 4,并且有点难以理解为什么 DOM 在组件属性更新时没有更新。我已经搜索并阅读了无数帖子,但找不到任何似乎可以回答这个问题的东西。

我有一个应用程序,它有一个用于显示错误消息的组件,称为 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;                                                                                                
  }
}

模板很简单:

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

我正在从另一个组件调用showErrors 方法:

  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]);                                                                 
        }                                                                                                                    
      });                                                                                                                    
    } 

从这个表单调用signupUser()方法,也就是signup.component.html,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>  

希望这足以了解要点。我能够观察到的是,MessagesComponent 中的this.messages 在调用时确实会更改为正确的值,但是 DOM 不会更新。

我在这里缺少什么?我觉得我对 Angular 如何检测组件属性的更改并将其传播到 DOM 有一些基本的误解,但我不知道我错过了什么。

感谢您的帮助!

【问题讨论】:

  • 通常是这样。我们需要知道更新模型的调用来自哪里。搜索手动调用变更检测
  • 感谢@GünterZöchbauer - 更新模型的调用在 SignupComponent 中调用,就在末尾附近:this.message.showErrors(...)。这就是你要问的吗?另外,我尝试注入ApplicationRef 并调用ref.tick(),但似乎没有什么不同(就尝试手动调用更改检测而言)。
  • 是的,但是从哪里调用signupUserNgZone.run 或 'ChangeDetectorRef.detectChanges` 会修复它,但如果能修复根本原因会更好
  • 啊,明白了@GünterZöchbauer - 我刚刚将该代码添加到帖子中。简而言之,signupUser() 是从 signup.component.html 调用的,SignupComponent 的模板。
  • 抱歉,不知道。

标签: angular


【解决方案1】:

我今天遇到了这个问题,我是这样解决的:

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

...它将每隔一秒通知一次更改

constructor(private ref: ChangeDetectorRef){

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

 }

...如果你只想通知那些,你需要在你需要它之​​后使用这条线

 this.ref.detectChanges()

【讨论】:

  • 这并不能真正解决问题。当任何 Input 属性发生变化时,Angular 应该会自动触发变化检测。这里的问题是:为什么它没有发生?也许您更改了某些受影响组件上的ChangeDetectionStrategy
  • 它发生在一个 ng2 智能表上,我用不同的解决方案解决了,我知道这不是一个好的解决方案,即使我会删除它
【解决方案2】:

我将重新排列代码如下。整个想法是 MessageComponent 有一个 Input 属性,用于接收它必须显示的消息,并且 SignupComponent 使用模板绑定 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() {                                                                                                                                                                                                    
  }  
}

注册组件

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 BINDING -->
<message [messages]="messages"></message>                                                                                                      
<h1>Signup</h1>                                                                                                          
<form (submit)="signupUser()">   
..... the rest of your code
</form>

【讨论】:

  • 这对我来说完全有意义,但似乎也没有更新 DOM。
  • 我唯一能建议的就是看看你在response.error.message 收到了什么样的对象。该代码假定您收到一个字符串数组。如果您收到一个简单的字符串(而不是字符串数组),您可能什么都看不到。
  • 事实证明,如果我将它包装在 ngzone.run() 中,这确实有效。显然,这似乎是调用的异步性质的问题(我在此调用中使用 Stripe API,并且 response.error.message 是该调用的返回值)。当我把它包裹起来时,你的解决方案就完美了。我还是想把它封装得更好一点,让它更容易在其他地方实现,但这肯定是一个可行的解决方案!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-18
  • 2016-05-21
  • 2011-06-01
  • 2016-07-27
  • 2016-12-20
相关资源
最近更新 更多