Reputation: 15291
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...
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?
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)?
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
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 BehaviorSubject
s. 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
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