Reputation: 1056
I am fairly new to Angular and I'm trying to display async data from my service into my component. When I do this, the data seems to update, but the component only shows the new data after I click a button. It seems to me the DOM is not updated when data is changed in my service, and only updates when I tell it to, in this example on the click of a button.
My app.component.ts :
import { Component } from '@angular/core';
import { AuthserviceService } from './services/authservice.service';
import { Subscription } from 'rxjs';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
displayName: string = 'no displayName available';
subscription: Subscription;
constructor(public authService: AuthserviceService){}
ngOnInit(){
this.subscription = this.authService.getDisplayName().subscribe(displayName => {this.displayName = displayName})
}
ngOnDestroy(){
this.subscription.unsubscribe();
}
login(){
this.authService.login();
}
logout(){
this.authService.logout();
}
methodThatDoesNothing(){
}
}
My app.component.html :
<button (click)="logout()">Logout</button>
<button (click)="login()">Login</button>
<button (click)="methodThatDoesNothing()">button that does nothing</button>
<p>{{ displayName }}</p>
<router-outlet></router-outlet>
My authservice.service.ts :
import { Injectable } from '@angular/core';
import { AngularFireAuth } from '@angular/fire/auth';
import * as firebase from 'firebase/app';
import { Observable, Subject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class AuthserviceService {
private displayName = new Subject<any>();
constructor(public afAuth: AngularFireAuth) { }
login() {
this.afAuth.auth.signInWithPopup(new firebase.auth.GoogleAuthProvider()).then(data => {
this.displayName.next(data.user.displayName);
});
}
logout() {
this.afAuth.auth.signOut();
}
getDisplayName(): Observable<any> {
return this.displayName.asObservable();
}
}
I am trying to authenticate by using Google (popup shows up, I login, data is provided by Google (including displayName) ). I then want to display my display name, which is saved in the service, into my component. Before logging in, this is what I see:
When I click login, I can login, but the displayname is not updated. Only when I click the button "button that does nothing" (or any other button), the display name is updated:
It seems to me the DOM is changed on the click of a button, but I don't really know what is actually happening behind the scenes ( I coulnd't find the answer, mainly because I did not know what to look for).
How would I correctly display the displayName, after logging in?
Thanks in advance.
EDIT:
This piece of code is the problem I figured, I don't know why yet:
login() {
this._displayName.next('this data will be shown on the template immediately');
this.afAuth.auth.signInWithPopup(new firebase.auth.GoogleAuthProvider()).then(data => {
this.displayName.next(data.user.displayName); //This data here does not
});
}
The first "displayname.next" changes the displayName and immediately changes the value on csreen.
The second one, in the popup method, does not show immediately, and requires me to click a button (which forces a refresh???) to show the displayname.
Upvotes: 0
Views: 275
Reputation: 1056
I have "fixed" the problem by forcing it to detect a change. I first tried ChangeDetectorRef, but that is only a solution when used inside of the component, while using a service, I used the solution mentioned in this topic (which uses ApplicationRef):
Trigger update of component view from service - No Provider for ChangeDetectorRef
Upvotes: 0
Reputation: 552
I would recommend that you use BehaviorSubject instead of Subject. A BehaviorSubject holds one value. When it is subscribed it emits the value immediately. A Subject doesn't hold a value.
private displayName: BehaviorSubject<string> = new BehaviorSubject('no displayName available');
create a get function
get _displayName(){
return this.displayName
}
subscribe it in the template with async pipe
<p>{{ authService._displayName | async }}</p>
when you want to pass a new value to it just use
_displayName.next('new value')
or if outside of service
authService._displayName.next('new value')
async pipe will handle subing and unsubscribing for you.
So you can remove the subscription on OnInit. Ive made a stackblitz example that comes close to yours to showcase this
https://stackblitz.com/edit/angular-effvqv
The way you did it was a bit off becouse
Observable → Subject → BehaviorSubject
so you make an Observable out of an Observable.
Hope this helped.
Upvotes: 1