1

I'm implementing an Angular application based upon the google drive API.

My configuration is the following :

  • "@angular/core": "~13.0.0",
  • "@angular/forms": "~13.0.0",
  • "@angular/material": "^13.0.1",
  • "@types/gapi": "^0.0.41",
  • "rxjs": "~7.4.0"

In short, there is a classical architecture with a service in charge of retrieving information from google API and a component in charge of displaying information.

Here are the code for the two elements :

First the service

import {Injectable} from "@angular/core";
import { Observable, Subject, BehaviorSubject } from 'rxjs';

declare const gapi: any;

@Injectable()
export class UserService {

    public auth2: any;

    public isSignIn: boolean;

    private isSignInChange: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
    public isSignInObs: Observable<boolean> = this.isSignInChange.asObservable();

    constructor() {
        //Init various variables
        this.isSignIn = false;
        this.isSignInChange.next(false);
        //Load the google api for future OAUTH load
        gapi.load('client:auth2', () => {this.googleInitClient(); });
    }

    private googleInitClient() {
        gapi.client.init({
            ...
        }).then( (res: any) => {
            // Listen for sign-in state changes.
            gapi.auth2.getAuthInstance().isSignedIn.listen( (res: boolean) => {this.updateSigninStatus(res)} );
            // Handle the initial sign-in state.
            this.updateSigninStatus(gapi.auth2.getAuthInstance().isSignedIn.get());
        }, (error: any) => {
            console.warn(error);
        });
    }

    public updateSigninStatus(isSignedIn: boolean) {
       this.isSignIn = isSignedIn;
        this.isSignInChange.next(isSignedIn);
    }

    public signIn() {
        gapi.auth2.getAuthInstance().signIn();
    }
}

The component script

import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { UserService } from '../_services/user.service';

@Component({
  selector: 'app-my',
  templateUrl: './my.component.html',
  styleUrls: ['./my.component.scss']
})
export class MyComponent implements OnInit {

  constructor() {
   }

  ngOnInit(): void {
  }

  public signIn() {
    this.userService.signIn();
  }

  public signOut() {
    this.userService.signOut();
  }
}

The component template

<button *ngIf="userService.isSignInObs | async; else elseBlock" mat-button [matMenuTriggerFor]="my"><mat-icon>avatar</mat-icon></button>
<mat-menu #my="matMenu" yPosition="above" [overlapTrigger]="false">
  <button mat-menu-item disabled><mat-icon>account_circle</mat-icon> {{currentUser?.nom}}</button>
  <button mat-menu-item disabled><mat-icon>email</mat-icon> {{currentUser?.mail}}</button>
  <button mat-menu-item routerLink="/configuration"><mat-icon>settings</mat-icon> Configuration</button>
  <button mat-menu-item (click)="signOut()"><mat-icon>logout</mat-icon> Déconnexion</button>
</mat-menu>
<ng-template #elseBlock><a mat-button (click)="signIn()" title="Se connecter"><mat-icon>login</mat-icon></a></ng-template>

The problem

I have a common problem (many threads on the subject) but I can't get it to work properly. In fact, as soon as I finish loading the google drive API (this.isSignInChange.next(isSignedIn);), I should have the condition in my ngIf (userService.isSignInObs | async) changed to trueand my template should update. But whatever the implementing solution (I've try a lot of solution), the template does'tn change until I click some where in the view.

The only solution that I manage is to use ChangeDetectorRef but :

Can you help me to find what's wrong with my code !? Yhanks in advance

1
  • To complete my question, I've finally succeed to fix the problem with <mat-expansion-panel>. That means I can use ChangeDetectorRef but I don't like the idea... and appreciate any further help ! Commented Nov 22, 2021 at 18:37

1 Answer 1

1

I don't know if it is a copy error, or it's the problem, but you've missed inject the service in your 'component script' constructor:

constructor(
   public userService: UserService 
) {}

Once you inject the service, you could try something like this (I guess you really wanted to show the mat-menu only if the user is signned, so I put it inside the ng-container. If it's not, you can put the whole 'mat-menu block' below, at the end of the code (under the code of 'ng-template #elseBlock')):

<ng-container *ngIf="userService.isSignInObs | async as isSignIn; else elseBlock">
   <button mat-button [matMenuTriggerFor]="my"><mat-icon>avatar</mat-icon></button>

<mat-menu #my="matMenu" yPosition="above" [overlapTrigger]="false">
      <button mat-menu-item disabled><mat-icon>account_circle</mat-icon>{{currentUser?.nom}}</button>
      <button mat-menu-item disabled><mat-icon>email</mat-icon>{{currentUser?.mail}}</button>
     <button mat-menu-item routerLink="/configuration"><mat-icon>settings</mat-icon> Configuration</button>
     <button mat-menu-item (click)="signOut()"><mat-icon>logout</mat-icon> Déconnexion</button>
</mat-menu>

</ng-container>
<ng-template #elseBlock><a mat-button (click)="signIn()" title="Se connecter"><mat-icon>login</mat-icon></a></ng-template>

UPDATE: If it doesn't work, could you just try to change this line in your 'UserService':

private isSignInChange: BehaviorSubject<boolean|null> = new BehaviorSubject<boolean>(null);

I'm afraid the problem is about the 'falsy' value of isSignInObs (not just 'false', but null, undefined,...). Perhaps reversing the logic?, something like this:

<ng-container *ngIf="!userService.isSignInObs | async as isSignIn; else elseBlock">
   <a mat-button (click)="signIn()" title="Se connecter"><mat-icon>login</mat-icon></a>
</ng-container>

<ng-template #elseBlock>

    <button mat-button [matMenuTriggerFor]="my"><mat-icon>avatar</mat-icon></button>

    <mat-menu #my="matMenu" yPosition="above" [overlapTrigger]="false">
       <button mat-menu-item disabled><mat-icon>account_circle</mat-icon>{{currentUser?.nom}}</button>
       <button mat-menu-item disabled><mat-icon>email</mat-icon>{{currentUser?.mail}}</button>
       <button mat-menu-item routerLink="/configuration"><mat-icon>settings</mat-icon> Configuration</button>
       <button mat-menu-item (click)="signOut()"><mat-icon>logout</mat-icon> Déconnexion</button>
    </mat-menu>

</ng-template>


Sign up to request clarification or add additional context in comments.

7 Comments

userService cannot be private if you want to use it in the template
Good point!, I'll edit in my answer, thxs!
Thanks, but the user Service was already injected. I try to add the as isSignIn but the problem persists.
Please, read my update. I hope it works.
No it's not changing anything.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.