Observable.interval 的问题 - 模板中的数据未更新,ngFor 的长度不正确,启动时获取轮询数据

Problems with Observable.interval - data is not updating in template, ngFor is incorrect length, get polling data on startup

在我不断努力自学 Angular2 的过程中(下次我有假期我要去度假!)我遇到了一个问题......我有一个从虚假 REST 服务中提取用户数据的服务我已经创造出来了。在我的服务中,我希望轮询数据(在这种情况下,我模拟在线的朋友和不在线的朋友)。这是带有轮询代码的服务:

@Injectable()
export class UserDataService {

    public loginInUser: Subject<UserStatus> = new BehaviorSubject<UserStatus>(null);
    public currentUser: UserStatus;
    public onlineContacts: Subject<UserContact> = new BehaviorSubject<UserContact>(null);
    public userContacts: Subject<UserContact> = new BehaviorSubject<UserContact>(null);

    constructor(public http:Http) {
        this.loadUserStatus(); // this is lower in the Component... I have not included it here
        this.pollContacts();
    }

    private pollContacts() : any {

        var headers = new Headers();
        headers.append('Content-Type', 'application/json');

        return Observable.interval(30000)
            .switchMap(() => this.http.get('/restservice/contacts', {headers: headers}))
            //.startWith() - Would this allow me to kick off the service when the Component is loaded / called?
            .subscribe((data: any) => {
                data = data.json();
                this.onlineContacts.next(data.onlineContacts);
                this.userContacts.next(data.allContacts);
            });

    }

这是我调用服务的组件:

@Component({
    selector: 'contacts-list',
    directives: [...ROUTER_DIRECTIVES],
    template: require('./contact-list.html')
})

export class ContactsList {

    public onlineContacts: any;
    public userContacts: any;

    constructor (public userDataService: UserDataService) {
        this.userDataService.onlineContacts.subscribe(
            (onlineContacts) => {
                this.onlineContacts = onlineContacts;
            });

        this.userDataService.userContacts.subscribe(
            (userContacts) => {
                this.userContacts = userContacts;
            });
    }
}

这是我的组件的 HTML 视图/模板...

<h3>Online Contacts</h3>
<div class="online-contact" *ngIf="onlineContacts?.length > 0" *ngFor="#con of onlineContacts" [routerLink]="['ForeignProfile', {id: con.persId}]">
    <img [src]="con.imgUrl" class="img-circle online-contact-img">
    {{ con.name }}
</div>

<h3>Contacts</h3>
<div class="online-contact" *ngIf="userContacts?.length > 0" *ngFor="#con of userContacts" [routerLink]="['ForeignProfile', {id: con.persId}]">
    <img [src]="con.imgUrl" class="img-circle offline-contact-img">
    {{ con.name }}
</div>

现在我被问题所困扰...

  1. 首先,我需要在加载组件/视图时启动 pollContacts() 方法,而不是等待间隔过去。我尝试添加 .startWith 方法(请参阅评论)但我不确定要传递给该方法的参数?

  2. 轮询有效并且正确的信息从服务传递到组件...但是模板没有更新?我是否需要将 async 管道添加到模板中(我确实尝试过但没有成功)?

  3. 出于某种原因,两个 ngFor 指令迭代(或循环)比对象数组的实际长度多一个。例如,当对象数组仅包含一项时,*ngFor="#con of onlineContacts" 有两行(循环两次),当 userContacts 的长度为 19 时,*ngFor="#con of userContacts" 有 20 行(循环 20 次)!

我知道我已经并且一直在问很多关于这个问题的问题,但是我没有人可以就这个问题谈谈所以请容忍我的问题。提前致谢。

我认为您需要明确通知 Angular 有关更改:

@Component({
    selector: 'contacts-list',
    directives: [...ROUTER_DIRECTIVES],
    template: require('./contact-list.html'),
    changeDetection: ChangeDetectionStrategy.OnPush // <== added (optional)
})

export class ContactsList {

    public onlineContacts: any;
    public userContacts: any;

    constructor (public userDataService: UserDataService,
        private cd: ChangeDetectorRef // <== added
    ) {
        this.userDataService.onlineContacts.subscribe(
            (onlineContacts) => {
                this.onlineContacts = onlineContacts;
                this.cd.markForCheck(); // <== added
            });

        this.userDataService.userContacts.subscribe(
            (userContacts) => {
                this.userContacts = userContacts;
                this.cd.markForCheck(); // <== added
            });
    }
}

另见 http://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html

关于你的第一个问题,是的,你可以使用 startWith 运算符,但它必须在 switchMap 运算符之前使用:

return Observable.interval(3000)
        .startWith('')
        .switchMap(() => this.http.get('./app/restservice/contacts', {headers: headers}))
        .subscribe((data: any) => {
        });

关于 async,由您决定。要么直接使用 subscribe 方法,要么让管道在后台使用它。

@Component({
  template: `
    <h3>Contacts</h3>
    <div class="online-contact" *ngFor="#con of userContacts | async">
      <img [src]="con.imgUrl" class="img-circle offline-contact-img">
      {{ con.name }}
    </div>
  `
})
export class ContactsList {
  public onlineContacts: any;
  public userContacts: any;

  constructor (public userDataService: UserDataService) {
    this.userContacts = this.userDataService.userContacts;
  }
}

关于最后一点,我可以显示来自您的 BehaviorSubject 的数据。也许您的数据级别存在问题。你收到过类似的东西吗:

{
  "allContacts": [
    {
      "name": "Sparky",
      "imgUrl": "https://restlet.com/static/app/img/sparky_right.png"
    }
  ],
  "onlineContacts": [
    {
      "name": "Sparky",
      "imgUrl": "https://restlet.com/static/app/img/sparky_right.png"
    }
  ]
}

这是一个有效的插件:https://plnkr.co/edit/vYQPePG4KDoktPoGaxYu?p=preview