Search code examples
angulartypescriptobservableangularfireangular-ng-if

ERROR TypeError: You provided 'null' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable


I searched everywhere for an answer but I couldn't get out this issue. I found that many people are having this issues with Observables in Angular but usually it was just a syntax error. I tried many things (I will list them below) but I'm confused. I don't know why it does like this.

ERROR:

ERROR TypeError: You provided 'null' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.

That's not a functionality error. In fact, it works perfectly with my expectations, but it throws an error to the console.

EXPECTATION:
It's just an ngIf directive that manages the view of a menu content. If the user is logged, it should view certain menu-items (for ex. 'My profile', etc...), if not, it should only see 'Login' and 'Signup' buttons. It actually works this kind of system, but I receive a console error.

WHAT I TRIED:
Googling around I tried many things. The one listed below are only few of them, but at some point I was just trying without knowing really how to solve the problem.

In the first try (which I will not list here because it was pretty long), I tried to switchMap the angular fire auth state to get the logged status, then I setted this.appUser$ to this.userService.get(user.uid), to return user data from the database. This will be the final result, but after getting the error I simplified by a lot the code to find out what can be the issue with that, hoping that it was easier. In fact, I didn't solved anything in 3 days of tries.

  1. navbar.component.html

<mat-menu #userMenu="matMenu">
  <ng-template matMenuContent >
    <div class="menuContent" *ngIf="(appUser$ | async) as user; else notLoggedMenuitems">
      <div mat-menu-item>
        <div class="row no-gutters ml-2 mr-2">
          <div class="col-auto"><img [src]="user.photoURL" class="user-profile-img" alt="Profile image"></div>
          <div class="col"><p class="mat-body-strong mb-0 ml-3" style="line-height: 350%;"><b>{{ user.displayName }}</b></p></div>
        </div>
      </div>
      <mat-divider class="mt-2"></mat-divider>
      <div mat-menu-item>...</div>
      <div mat-menu-item>...</div>
      <mat-divider></mat-divider>
      <div mat-menu-item (click)="logout()">Logout</div>
    </div>

    <ng-template #notLoggedMenuitems>
      <div mat-menu-item routerLink="/auth/login">Login</div>
      <div mat-menu-item routerLink="/auth/signup">Signup</div>
    </ng-template>
  </ng-template>
</mat-menu>
  1. navbar.component.ts

export class NavbarComponent implements OnInit {

  appUser$: Observable<firebase.User>;

  constructor(private auth: AngularFireAuth) {
    this.appUser$ = this.auth.user;
  }

  ngOnInit(): any {
  }

  logout(): void {
    this.auth.signOut();
  }

}
  1. navbar.component.html
    I changed the ngIf directive to the one below, removing the else block. I tried even with other operators like " ! " and so on, but nothing changed...

*ngIf="(appUser$ | async)"
  1. navbar.component.ts

export class NavbarComponent implements OnInit {

  appUser$: Observable<firebase.User> = this.auth.user;

  constructor(private auth: AngularFireAuth) {}

  ngOnInit(): any {
  }

  logout(): void {
    this.auth.signOut();
  }

}

CONCLUSION:
How can I do this? I don't know why typescript throws this error.

UPDATE 1:
Updated navbar.component.html

<mat-menu #userMenu="matMenu">
  <ng-template matMenuContent >
    <div class="menuContent" *ngIf="(appUser$ | async) as user; else notLoggedMenuitems">
      <div mat-menu-item>
        <div class="row no-gutters ml-2 mr-2">
          <div class="col-auto"><img [src]="user.details.imgUrl" class="user-profile-img" alt="Profile image"></div>
          <div class="col"><p class="mat-body-strong mb-0 ml-3" style="line-height: 350%;"><b>{{ user.name }}</b></p></div>
        </div>
      </div>
      <mat-divider class="mt-2"></mat-divider>
      <ng-container *ngIf="user.roles.admin">
        <div mat-menu-item><mat-icon>local_bar</mat-icon>Manage Drinks</div>
          <div mat-menu-item><mat-icon>note_add</mat-icon>Add Drinks</div>
          <mat-divider></mat-divider>
      </ng-container>
      <div mat-menu-item><mat-icon>settings</mat-icon>My Profile</div>
      <div mat-menu-item><mat-icon>wine_bar</mat-icon>My Drinks</div>
      <div mat-menu-item><mat-icon>favorite</mat-icon>Wishlist</div>
      <mat-divider></mat-divider>
      <div mat-menu-item (click)="logout()">Logout</div>
    </div>

    <ng-template #notLoggedMenuitems>
      <div mat-menu-item routerLink="/auth/login">Login</div>
      <div mat-menu-item routerLink="/auth/signup">Signup</div>
    </ng-template>
  </ng-template>
</mat-menu>

Updated navbar.component.ts

import { Component, OnInit } from '@angular/core';
import { AngularFireAuth } from '@angular/fire/auth';
import { Observable } from 'rxjs';

import { AuthenticationService } from './../../../auth/authentication/authentication.service';
import { User } from './../../../auth/authentication/models/user.model';



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

  appUser$: Observable<User>;

  constructor(private authService: AuthenticationService, private auth: AngularFireAuth) {
  }

  ngOnInit(): any {
    this.appUser$ = this.authService.user$;
    this.appUser$.subscribe(res => console.log('in app user: ', res));
  }

  logout(): void {
    this.auth.signOut();
  }

}

Updated authentication.service.ts

private initUser(): void {
  this.user$ = this.afAuth.authState.pipe(
    switchMap(user => {
      if (user) {
        return this.db.doc$<User>(`users/${user.uid}`);
      } else {
        return undefined;
      }
    })
  );
}

UPDATE 2:
Updated authentication.service.ts

private initUser(): void {
  this.user$ = this.afAuth.authState.pipe(
    switchMap(user => {
      if (user) {
        return this.db.doc$<User>(`users/${user.uid}`);
      } else {
        return new Observable<User>();
      }
    })
  );
}

Now I don't see the error anymore. But the user image doesn't shows well... I don't know if that's a good method to do things though.


Solution

  • I think you almost got it.
    What is not correct is the way you handle your user$ in your service. You should always return an observable in a switchMap operator.
    To handle the case where there's no user login, you can use of(). It's an observable that emits the value passed in param and complete right after that.
    Regarding the database your using, are you using firestore or the realtime database ? To request your data, in both cases, you need to end your call with valueChanges to return an observable.
    I asume you are using firestore since you called the doc method:

    private initUser(): void {
      this.user$ = this.afAuth.authState.pipe(
        switchMap(user => {
          if (user) {
            // return an observable<User>
            return this.db.doc<User>(`users/${user.uid}`).valueChanges();
          } else {
            return of(null);
          }
        })
      );
    }
    

    Try this, it should work.