Mike Sav
Mike Sav

Reputation: 15291

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

in my constant struggle to teach myself Angular2 (next time I have vacation days I'm going on vacation!) and I have a problem... I have a servive that pulls user data from a phoney REST service I have created. In my service I wish to poll data (in this case I mock friends who are online and friends who aren't online). Here is the service with the polling code:

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

    }

Here is my component that calls the service:

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

And here is the HTML view / template for my component...

<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>

Now I am beset with problems...

  1. firstly I need to kickoff the pollContacts() method when the Component / view is loaded rather than wait for the interval to pass. I tried adding the .startWith method (see the comment) but I was unsure of the argument to pass to the method?

  2. The polling works and the correct information is passed from the service to the Component... however the template is not updating? Do I need to add the async pipe to the template (I did try this and it didn't work)?

  3. For some reason the two ngFor directives iterate (or loop) one more than the actual length of the object arrays. For example the *ngFor="#con of onlineContacts" has two rows (loops two times) when the object array contains only one item and the *ngFor="#con of userContacts" has 20 rows (loops 20 times) when there are the length of userContacts is 19!

I realise I am and have been asking a lot of questions on this subject but I have no one to talk to on this subject so please tolerate my questions. Thanks in advance.

Upvotes: 4

Views: 1227

Answers (2)

Thierry Templier
Thierry Templier

Reputation: 202138

Regarding your first question, yes, you can use the startWith operator but it must be used before the switchMap one:

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

Regarding the async, it's up to you. Either you use directly the subscribe method either you let the pipe use it under the hood.

@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;
  }
}

Regarding the last point, I have no problem to display data from your BehaviorSubjects. Perhaps there is a problem at the level of your data. Do you receive something like that:

{
  "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"
    }
  ]
}

Here is a working plunkr: https://plnkr.co/edit/vYQPePG4KDoktPoGaxYu?p=preview.

Upvotes: 1

G&#252;nter Z&#246;chbauer
G&#252;nter Z&#246;chbauer

Reputation: 657078

I think you need to explicitly notify Angular about changes:

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

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

Upvotes: 2

Related Questions