【问题标题】:Angular2 View Not Changing After Data Is Updated数据更新后Angular2视图没有改变
【发布时间】:2015-10-20 19:37:23
【问题描述】:

我正在尝试在 websocket 事件返回更新数据后更新我的视图。

我在我的应用程序中注入了一个服务,并在该服务上调用 getData() 方法。此方法向我的 NodeJS 服务器发出 socket.io 事件,该服务器又执行外部 api 调用并解析一些数据。然后,NodeJS 服务器会使用我在服务中侦听的新数据发出一个成功事件。返回成功事件后,我会更新视图中引用的服务的属性。

但是,无论我尝试什么,一旦属性更新,我都无法显示数据。

我已经搜索了几天,我发现所有的博客文章说这种变化应该是无缝的,或者我需要以某种方式合并 zone.js,或者使用表单尝试相同的逻辑(但是我试图无需用户交互即可执行此操作)。似乎没有什么对我有用,我有点沮丧。

例如:

假设我收到一个字符串数组,我想用它创建一个未排序的列表。

app.ts

import {Component, View, bootstrap, NgFor} from 'angular2/angular2';
import {MyService} from 'js/services/MyService';

// Annotation section
@Component({
    selector: 'my-app',
    viewInjector: [MyService]
})
@View({
    templateUrl: 'templates/my-app.tpl.html',
    directives: [NgFor]
})

class MyComponent {
    mySvc:MyService;

    constructor(mySvc:MyService) {
        this.mySvc = mySvc;
        this.mySvc.getData();
    }
}   

bootstrap(MyComponent, [MyService]);

MyService.ts

let socket = io();
export class MyService {
    someList:Array<string>;

    constructor() {
        this.initListeners();
    }

    getData() {
        socket.emit('myevent', {value: 'someValue'});
    }

    initListeners() {
        socket.on('success', (data) => {
            self.someList = data;
        });
    }
 }

我的应用程序.tpl.html

<div>
    <h2>My List</h2>
    <ul>
        <li *ng-for="#item of mySvc.myList">Item: {{item}}</li>
    </ul>
</div>

有趣的是,我发现如果我在我的组件中加入一个超时来更新我在从成功回调更新 someList 属性后在视图上设置的一些任意属性,那么两个属性值都会同时正确更新.

例如:

新的 app.ts

    import {Component, View, bootstrap, NgFor} from 'angular2/angular2';
    import {MyService} from 'js/services/MyService';

    // Annotation section
    @Component({
        selector: 'my-app',
        viewInjector: [MyService]
    })
    @View({
        templateUrl: 'templates/my-app.tpl.html',
        directives: [NgFor]
    })

    class MyComponent {
        mySvc:MyService;
        num:Number;

        constructor(mySvc:MyService) {
            this.mySvc = mySvc;
            this.mySvc.getData();
            setTimeout(() => this.updateNum(), 5000);
        }

        updateNum() {
            this.num = 123456;
        }
    }   

    bootstrap(MyComponent, [MyService]);

新的 my-app.tpl.html

<div>
    <h2>My List {{num}}</h2>
    <ul>
        <li *ng-for="#item of mySvc.myList">Item: {{item}}</li>
    </ul>
</div>

那么我应该如何让 angular2 识别在“成功”事件之后数据已更改而不更新其他属性?

使用 NgFor 指令有什么遗漏吗?

【问题讨论】:

  • 使用onInitonCheck。因为据我了解,您正在尝试动态打印列表,因此您应该使用 onInit。
  • 如何使用 onInit() 或 onCheck()?我不清楚文档
  • 我做了一个 bar bone git repo,你可以克隆它,用 socket.io github.com/jgainfort/angular2MyApp 设置一个基本的 nodejs/express 服务器。自述文件包括简单的设置过程。
  • 好吧,经过多次尝试,我得出的结论是,我不知道为什么会发生这种情况,哈哈。按照您的解决方案并避免更改随机属性,您可以将ChangeDetectorRef 传递给构造函数,并在其中写入:setTimeout(() =&gt; {this.cd.requestCheck();}, 500);(其中this.cdcd: ChangeDetectorRef,而 500 是一个足够低的数字以获得服务器和客户端发送和接收他们的事件)。我真的想要一个更好的答案,但现在我认为这可能会对你有所帮助。您可以在他们的 git 或 angular 的邮件列表中询问以获得更好的答案。
  • 谢谢@ericmartinez,使用 setTimeout 的解决方法确实显示了更新的数据。我同意你的观点,这并不理想,我会采纳你的建议并将这个问题转移到有角度的问题上。我敢打赌,这与将套接字事件设置在区域之外的情况有关,因此角度永远不会捕获属性更新。

标签: websocket angular


【解决方案1】:

所以我终于找到了我喜欢的解决方案。按照这篇文章How to update view after change in angular2 after google event listener fired 中的答案,我在 zone.run() 中更新了 myList,现在我的数据在我的视图中按预期更新了。

MyService.ts

/// <reference path="../../../typings/tsd.d.ts" />

// Import
import {NgZone} from 'angular2/angular2';
import {SocketService} from 'js/services/SocketService';

export class MyService {
    zone:NgZone;
    myList:Array<string> = [];
    socketSvc:SocketService;

    constructor() {
        this.zone = new NgZone({enableLongStackTrace: false});
        this.socketSvc = new SocketService();
        this.initListeners();
    }

    getData() {
        this.socketSvc.emit('event');
    }

    initListeners() {
        this.socketSvc.socket.on('success', (data) => {
            this.zone.run(() => {
                this.myList = data;
                console.log('Updated List: ', this.myList);
            });
        });
    }
 }

【讨论】:

  • 亲爱的上帝,我在这个问题上坐了 2 天,zone.run() 就像一个魅力,谢谢!
  • 我的 Angular 2 应用程序在没有区域的情况下运行良好,直到我在 ipad 上对其进行了测试。添加区域后,它也可以在 ipad 上运行。我真的不知道为什么。
  • 你就是男人!我也花了一整天的时间尝试做同样的事情!
【解决方案2】:

只需将您的 socket.io 初始化移至服务构造函数即可。
看看这个例子:

import {Injectable} from 'angular2/core';  
@Injectable()
export class SocketService {
    socket:SocketIOClient.Socket;

    constructor(){
        this.socket = io.connect("localhost:8000");
    }
    public getSocket():SocketIOClient.Socket{
        return this.socket;
    }
}

现在,每当您将此服务注入组件并使用套接字时,您的视图都会自动更新。但是,如果您像以前那样将其保留在全局范围内,则必须与某些东西进行交互才能强制更新视图。
以下是使用此服务的示例组件:

export class PostsComponent {
    socket: SocketIOClient.Socket;
    posts: Array<Post> = [];

    constructor(private _socketService:SocketService){
        this.socket.on('new-post', data => {
            this.posts.push(new Post(data.id, data.text));
        });  
}  

【讨论】:

  • 对于正在使用的人:this.socket.on('new-post',function(data){ this.posts.push(new Post(data.id,data.text)); }) 将不起作用。不过,您的解决方案有效!
  • @John 您在此处评论中的回调函数未绑定到与外部上下文相同的this。这可能是那里的问题。
【解决方案3】:

如果您有兴趣,我可以建议将ngrx/storeOnPush 更改检测一起使用。我遇到了类似的问题,在 Angular 之外发生了一些事情(无论这意味着什么),而我的观点并没有反映这种变化。

将 Redux 模式与事件调度程序和一个保存我的数据的单一状态结合使用OnPush 更改检测为我解决了这个问题。我不知道为什么或如何解决这个问题。暨格兰诺萨利斯。

详情请参阅this comment

【讨论】:

  • 使用 ngrx/store 非常好,但对这个错误没有帮助。当从 websocket 返回数据时,我正在使用它并且有这种行为。 this.zone.run(() =&gt; {...}) 解决方案效果很好!
【解决方案4】:

我遇到了同样的问题,问题是:

我正在使用 “angular2”:“2.0.0-beta.1” 好像有bug,因为更新到 “angular2”:“2.0.0-beta.15” 它工作正常。

希望对你有帮助,我是通过痛苦的方式学会的

【讨论】:

    【解决方案5】:

    更新

    我链接的plunker 提供了一个基本示例,但让我感到沮丧的是,我无法展示一个完整的“工作示例”。因此,我创建了一个 github repo,您可以将其下拉以查看我在下面谈到的各个部分的完整工作示例。


    所以在您的实际问题中存在两个问题。您在“MyService.ts”文件中的原始代码中有错字

    self.someList = data;//should be this.someList, self is the browser window object
    

    另一个问题是 Angular2 无法按照您期望的方式识别 change detection。如果已将其设置为“this”,我仍然认为它不会更新您的组件视图。

    在您的回答中,它确实有效,但是您以错误的方式解决了这个问题。您应该在服务中实现Observable

    当您将这两个功能结合在一起时,您可以以一种相当直接的方式实现帆。我创建了一个示例plunker,但请记住,它实际上并没有连接到 Sails 服务器,因为 plunker 需要 https,而且我不会为我的本地机器购买 ssl 证书。该代码确实反映了您应该如何在 Angular2 中实现与帆的通信。

    基本思路可以在src/io.service.ts文件中找到

    constructor() {
      this._ioMessage$ = <Subject<{}>>new Subject();
      //self is the window object in the browser, the 'io' object is actually on global scope
      self.io.sails.connect('https://localhost:1337');//This fails as no sails server is listening and plunker requires https
      this.listenForIOSubmission();
    }
    
    get ioMessage$(){
      return this._ioMessage$.asObservable();
    }
    
    private listenForIOSubmission():void{
      if(self.io.socket){//since the connect method failed in the constructor the socket object doesn't exist
        //if there is a need to call emit or any other steps to prep Sails on node.js, do it here.
        self.io.socket.on('success', (data) => {//guessing 'success' would be the eventIdentity
          //note - you data object coming back from node.js, won't look like what I am using in this example, you should adjust your code to reflect that.
          this._ioMessage$.next(data);//now IO is setup to submit data to the subscribbables of the observer
        });
      }
    }
    

    【讨论】:

      【解决方案6】:

      执行此操作的一种非常简单的方法是在区域中运行您要更新的任何变量。

      zone.run(()=>{
          this.variable = this.variable;
      });
      

      似乎没有那么容易,但是如果在区域中运行,只需将其分配给自身就会更新它。我不知道这是否仍然是 angular2 的问题,因为我运行的是旧版本。

      【讨论】:

      • 你可以导入NgZone,然后在构造函数中依赖注入来获取zone对象。从那里你可以像上面的答案一样调用 run 。谢谢它对我很有用。
      • 这在我的应用程序中由于某种原因不起作用。 stackoverflow.com/questions/40419533/…
      猜你喜欢
      • 2016-12-18
      • 1970-01-01
      • 2016-04-02
      • 2023-03-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-08-08
      • 1970-01-01
      相关资源
      最近更新 更多