0

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

  • Am I doing something wrong in my code?
  • Am I forgetting something?

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.

Niek Jonkman
  • 1,014
  • 2
  • 13
  • 31

2 Answers2

1

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.

Qellson
  • 532
  • 2
  • 7
  • Thanks for providing me a better way of doing this, unfortunately this way also gives me the same problem. 'new value' is only displayed after I click any of the buttons. I assume that the data is supposed to change as soon as the data in the service is updated. I think the problem may be somewhere else. – Niek Jonkman Mar 13 '19 at 11:06
  • This way it your login method could change the displayName. When you get the data just next the new displayName. It would be awesome if you could provide a stackblitz example of your code – Qellson Mar 13 '19 at 11:21
  • I've figured out what piece of code (where it happends) is causing the issue, I do not yet know why that is. I can provide a stackblitz some time later. – Niek Jonkman Mar 13 '19 at 11:34
  • Nice! Looking forward to it :) – Qellson Mar 13 '19 at 11:42
  • Did you forget to change the variable displayName in the "then" block to _displayName? It works fine when you change it. Althou i didnt get success response so i added a catch to the then block and the display rerendered. `this.afAuth.auth.signInWithPopup(new firebase.auth.GoogleAuthProvider()).then(data => { this._displayName.next(data.user.displayName); //This data here does not }).catch(err => this._displayName.next('some string'));` It should work for you if you get a success.Just change the variable name in the then block. – Qellson Mar 13 '19 at 12:58
  • When I use the catch block, just like you, it works. But in the then block, it doesn't work, just as before. I'll postpone my problem for now as it does not require immediate attention. Thanks for your help though. – Niek Jonkman Mar 13 '19 at 13:12
  • Np :) Just try to check does the code even enter the then block.If its something wrong it wouldnt work in the catch block aswell. – Qellson Mar 13 '19 at 13:18
0

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

Niek Jonkman
  • 1,014
  • 2
  • 13
  • 31