I'm creating a Facebook service that calls the Facebook javascript api and am wondering how to best implement change detection when my values are updated.
I have a UserService
which has a currentUser
property that is a BehaviorSubject:
currentUser: Subject<User> = new BehaviorSubject<User>(new User(null));
And when I want to update the user in response to the facebook javascript sdk telling me the user has logged in or logged out, I update that and need to call tick()
on an ApplicationRef
:
updateUser(user: User) {
console.log('UserService.updateUser:', user);
this.currentUser.next(user);
this.appRef.tick(); // UI does not update without this
}
constructor(facebook: Facebook) {
this.facebook.facebookEvents.filter(x => x != null
&& x.eventName == 'auth.authResponseChange')
.subscribe((event) => {
this.updateUser(new User(event.data));
}
}
In my component I store the 'currentUser' from the user service in the constructor and bind to the value property:
<h2>Logged into Facebook as {{currentUser.value.name}}</h2>
<p>Is this you? <img src="{{currentUser.value.profilePicUrl}}"></p>
Am I doing something wrong? Is there a better way than having to call ApplicationRef.tick() after a change triggered from an external library?
I tried using NgZone and that doesn't work, using a different event that returns posts in a feed as the service pages through them:
constructor(userService: UserService, private ref: ApplicationRef, private zone: NgZone)
...
this.postsSubject.subscribe((post) => {
this.zone.runOutsideAngular(() => { // doesn't do anything
this.posts.push(post);
console.log('postsSubject POST, count is ', this.posts.length);
ref.tick(); // required to update bindings
});
}
The console shows the count incrementing, but the html binding {{posts.length}}
is only updated if I add the ref.tick()
call...
I think I saw somewhere that you can make available 'inputs' to any component from the top-level app component which might be the way to go for the logged in user, but not other calls like getting posts in a feed...
We can trigger change detection manually by using detectChanges() , ApplicationRef. tick() , and markForCheck() that we mentioned earlier on AsyncPipe . detectChanges() on ChangeDetectorRef which will run change detection on this view and its children.
Change detection works by detecting common browser events like mouse clicks, HTTP requests, and other types of events, and deciding if the view of each component needs to be updated or not.
ngOnChanges triggers following the modification of @Input bound class members. Data bound by the @Input() decorator come from an external source. When the external source alters that data in a detectable manner, it passes through the @Input property again.
ngOnChanges gets called before ngOnInit and whenever a component's bound input is changed FROM THE PARENT COMPONENT. Remember that ngOnChanges is specific to bound inputs on the component. This means if you don't have any @Input properties on a child, ngOnChanges will never get called.
Am I doing something wrong? Is there a better way than having to call
ApplicationRef.tick()
after a change triggered from an external library?
It depends. If you're calling the Facebook APIs outside of Angular (i.e., registering asynchronous event handlers outside Angular), then you'll need to manually run change detection as part of handling any events. You can either
NgZone
and then call run()
on that object, which will execute the passed (callback) function inside the Angular zone and then automatically call ApplicationRef().tick()
, which will run change detection for the entire app, orApplicationRef
and call tick()
yourself, orChangeDetectorRef
and call detectChanges()
yourself -- this will only run change detection on the one component and its childrenNote, if you inject NgZone
, you don't want to use runOutsideAngular()
. That will execute the passed (callback) function outside the Angular zone, and it will not call ApplicationRef().tick()
, so change detection will not execute.
If you call the Facebook APIs inside Angular, you might be able to avoid all of the above, because then Angular (via its use of Zone.js) should monkey-patch the asynchronous event calls. Then, when your events fire, ApplicationRef.tick()
will automatically be called. See https://stackoverflow.com/a/34593821/215945 for more discussion on this approach.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With