Niek Jonkman
Niek Jonkman

Reputation: 1056

Service side Obsevable data does not update initially in component Angular

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:

enter image description here

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:

enter image description here

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

Answers (2)

Niek Jonkman
Niek Jonkman

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

Qellson
Qellson

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

Related Questions