当我将函数用于 return 值并导致浏览器崩溃时,为什么 *ngFor 会产生无限循环?

Why *ngFor make infinite loop when I use function to return value and crashing browser?

我不明白为什么 *ngFor 在我对 return 值使用函数时会产生无限循环。

我用Angular8个

聊天视图HTML模板

<div class="row conpeek_message_chat_row" *ngFor="let chatMessage of chatMessages | async ">

   <div class="conpeek_chat_message_inner [innerHTML]="getChatMessageText(chatMessage)"></div>

</div>

聊天视图组件

private chatMessages:Observable<Array<any>>;

ngAfterViewInit() {
    this.chatMessages = this.chatMessagesService.getMessages();
}

getChatMessageText(chatMessage) {
    if (chatMessage.type == 'text' || chatMessage.type == 'info') {
      console.log('TYPE TEXT');
      return chatMessage.value;
    }
    if (chatMessage.type == 'image') {
      let img_obj = `<div class="conpeek_msg_img_outer"><img src="${chatMessage.value}"></div>`;
      return img_obj;
    }
    if (chatMessage.type == 'file') {
      let file_obj = `<a>${chatMessage.filename}</a>`;
      return file_obj;
    }
  }

聊天消息服务

private chatMessages = [];
private chatMessagesStream = new BehaviorSubject(this.chatMessages);

constructor(private dialogInfoService: DialogInfoService) {
    this.dialogInfoService.getDialogMembers().subscribe(members => {
      this.dialogMembers = members;
    });
}

getMessages(): Observable<any> {
   return this.chatMessagesStream.asObservable();
}

addMessage(chatMessage) {
    if (chatMessage.type != 'info') {
      for (let member of this.dialogMembers) {
        if (member.member_uuid == chatMessage.sender_dialog_uuid) {
          chatMessage.sender_name = member.member_presentation_name;
          chatMessage.avatar_id = member.avatar_id;

          if (member.member_uuid == this.dialogInfoService.getOwnDialogUUID()) {
            chatMessage.sender_type = 'contact';
            chatMessage.status = 'sending';
          } else {
            chatMessage.sender_type = 'consultant';
          }
        }
      }
    }

 this.chatMessages.push(chatMessage);
 this.chatMessagesStream.next(this.chatMessages);
}


初始化此组件并收到第一条消息后,我在控制台

中得到消息的无限循环console.log('TYPE TEXT');

当我使用它请求图像消息类型来下载 blob 时,我的 chrome 浏览器崩溃了。

为什么会发生,如何解决?


已编辑 我在此处找到了 "infinite" 循环的线索。我用最少的附加代码重新创建了该应用程序,并且几乎一切正常。但是当我将 WebSocket 添加到我的应用程序时,当这个 webscoket 做出一些动作(打开连接、发送消息、关闭连接等)时,我的循环进行迭代。无限循环只是一个乒乓消息,我的 websocket 与服务器一起做,以快速检测连接问题。

我在这里展示问题,在 WS 执行每个操作后检查控制台日志,或者通过单击按钮发送 ws 消息。

我阅读了一些相关内容,每个更改检测事件(如按钮单击、http 请求或 websocket 操作)都会导致此循环迭代。

https://stackblitz.com/edit/angular-xpb5hw

您要更改 getChatMessageText(chatMessage)chatMessages 的值吗?如果是这样,我就是这个问题的根源

尝试将此添加到您的组件中:

chatMessageSubscription: Subscription;

    constructor(private chatMessageService: ChatMessagesService) {}

ngOnInit() {
    this.chatMessageSubscription = this.chatMessageService.chatMessagesStream .subscribe((messages: data) => {
            this.chatMessages = data;
        }
}
  1. 您还没有关闭 class 报价
<div class="conpeek_chat_message_inner [innerHTML]="getChatMessageText(chatMessage)"></div>
  1. 您的 chatMessagesService return 是可观察的,因此您需要订阅它
 // this.chatMessages = this.chatMessagesService.getMessages();
    this.chatMessagesService.getMessages().subscribe(messages=>{
      this.chatMessages=messages
    })
  1. 因为您有一个带有默认值的行为主题,您可以在初始化视图之前安全地订阅以避免内容检查错误。将 ngAfterViewInit() 替换为 ngOnInit()

我不确定你是如何测试 addMessage 函数的,但有可能是不小心在 observable 本身内,这会导致无限循环。

另外一点,一旦您从服务接收到数据,这将是填充消息文本的好时机,而不是依赖从 html 模板调用的函数。

这是一个最小的工作复制:https://stackblitz.com/edit/angular-xnrupp

我通过将 *ngFor 部分代码放入新组件并添加到此组件装饰器 changeDetection: ChangeDetectionStrategy.OnPush 选项来解决问题。 在 init 上具有停止检测更改的功能 (detach())。现在仅当来自服务的新消息到来时循环迭代(我在 subcribe fn 中添加了 detachChanges,如下所示)。

我需要隔离此组件,因为在 Angular 中,每个按钮单击、请求或 WebSocket 消息都会触发整个应用程序中的检测更改,现在我控制它。

ChatMessagesViewComponent

@Component({
  selector: 'chat-messages-view',
  template: `... *ngFor html here only ...`,
  styleUrls: ['./chat-messages-view.component.css'],
  changeDetection: ChangeDetectionStrategy.OnPush
})

constructor(private chatMessagesService: ChatMessagesService, private cd: ChangeDetectorRef) {
}

ngOnInit() {
  this.chatMessages = this.chatMessagesService.getMessages();
  this.cd.detach();
}

ngAfterViewInit() {
  this.chatMessagesService.getMessages().subscribe(messages => {
    this.cd.detectChanges();
    this.scrollMessagesToBottom(true);
  });
  this.scrollMessagesToBottom(false);
}