Search code examples
angularfirebasefirebase-authenticationangularfire2

Cannot read property 'uid' of null


I've got my AuthService, where I subscribe to authState.

@Injectable()
export class AuthService {

    private user: User = null;

    constructor(private afAuth: AngularFireAuth) {
       const sub$ = this.afAuth.authState.subscribe(auth => {
       this.user = auth;
    });

   get userId(): string {
       return this.user.uid;
   }
}

Now in other component I want to take objects belongs to current logged user so I created a service:

@Injectable()
export class SomeService{

    constructor(private readonly afs: AngularFirestore,
                private auth: AuthService) {
    }

    ...

     getByCurrentUser(): Observable<any> {
         return this.afs.collection<any>('my-path',
            ref => ref
            .where('userId', '==', this.auth.userId)).valueChanges();
     }
}

And in component I subscribe to this method:

...

ngOnInit() {
   this.getData();
}

private getData(): void {
   this.testService.getByCurrentUser().subscribe(
     res => console.log(res),
     error => console.log(error)
   );
}

Problem: When I redirect between pages it's works fine, but after refresh page getData() method is called before authState callback assign current auth and in effect userId() method return null.

How to prevent it?


Solution

  • You can use an auth guard or a resolver, something like this:

    This guard will prevent a route from being loaded unless the user is authenticated.

    export class AdminAuthGuard implements CanActivate {
    
      constructor(private auth: AngularFireAuth) {}
    
      canActivate(): Observable<boolean> | Promise<boolean> | boolean {
        return this.auth.authState.pipe(map((auth) => {
          if (!auth) {
            // Do stuff if user is not logged in
            return false;
          }
          return true;
        }),
        take(1));
      }
    }
    

    Use it in your routing module:

    {
      path: 'yourPath',
      component: YourComponent,
      canActivate: [AdminAuthGuard],
    }
    

    Or this resolver will set the current user id before a route is loaded:

    export class UserIdResolver implements Resolve<boolean> {
      constructor(
        private auth: AngularFireAuth,
        private authService: AuthService,
      ) {}
    
      resolve(): Observable<boolean> {
        return this.auth.user.pipe(map((user) => {
          if (user) {
            this.authService.setCurrentUser(user.uid); // set the current user
            return true;
          }
          this.authService.setCurrentUser(null);
          return false;
        }), take(1));
      }
    } 
    

    Use it in your routing module:

    {
      path: 'yourPath',
      component: YourComponent,
      resolve: [UserIdResolver],
      runGuardsAndResolvers: 'always',
    }