David McGhee
David McGhee

Reputation: 11

Why does my Ngrx signalStore update its state inside a service but fail to reflect those changes in a component?

I'm storing firebase auth details that have been retrieved in a service in signal store, primarily at this stage just to use in an auth guard.

But while the authenticated user seems to be correctly returned and updated in the store from the service, the store does not give me the user value in my auth guard or components.

"@angular/core": "^18.2.0",
"@angular/fire": "^18.0.1",
"@ngrx/signals": "^18.1.1",

Here's how I define my signalStore:

import { User } from '@angular/fire/auth';
import { patchState, signalStore, withMethods, withState } from '@ngrx/signals';

type AuthState = {
  user: User | null;
};

const initialState: AuthState = {
  user: null,
};

export const AuthStore = signalStore(
  { providedIn: 'root' },
  withState(initialState),
  withMethods((store) => ({
    updateUser(user: User | null): void {
      patchState(store, () => ({ user }));
    },
  }))
);

Nice and simple.

I have a firebase service which does things like signing in and listening to the auth status:

import { inject, Injectable } from '@angular/core';
import {
  getAuth,
  onAuthStateChanged,
  signInWithEmailAndPassword,
} from '@angular/fire/auth';
import { AuthStore } from '../components/auth/auth-store';

@Injectable({ providedIn: 'root' })
export class FirebaseService {
  authStore = inject(AuthStore);

  auth = getAuth();

  logIn(email: string, password: string) {
    signInWithEmailAndPassword(this.auth, email, password)
      .then((userCredential) => {
        const user = userCredential.user;
        this.authStore.updateUser(user);
      })
      .catch((error) => {
        console.log(error);
      });
  }

  constructor() {
    this.authStatusListener();
  }

  authStatusListener() {
    onAuthStateChanged(this.auth, (user) => {
      console.log('authStateChanged');
      if (user) {
        console.log(user); // logs the user data
        this.authStore.updateUser(user);
        console.log('User is logged in');
        console.log(this.authStore.user()); // logs the user data
      } else {
        this.authStore.updateUser(user);
        console.log('User is logged out');
      }
    });
  }
}

And this seems to work fine. The logs I've commented there log out my test user when authed without an issue.

However, when I go to use this in my auth guard:

import { inject } from '@angular/core';
import {
  ActivatedRouteSnapshot,
  CanActivateFn,
  Router,
  RouterStateSnapshot,
} from '@angular/router';
import { AuthStore } from './components/auth/auth-store';

export const AuthGuard: CanActivateFn = (
  next: ActivatedRouteSnapshot,
  state: RouterStateSnapshot
) => {
  const router = inject(Router);
  const authStore = inject(AuthStore);
  if (!authStore.user()) {
    console.log(authStore.user()); // null every time!
    router.navigate(['login']);
    return false;
  }
  if (authStore.user()) {
    console.log('user is authed: ', authStore.user());
    return true;
  }

  console.log('fallthrough');
  return false;
};

user() is always null.

even if I just shove user() in the login template, it's null:

import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { Router } from '@angular/router';
import { FirebaseService } from '../../service/firebase.service';
import { CommonModule } from '@angular/common';
import { AuthStore } from './auth-store';

@Component({
  selector: 'app-auth',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: ` <div class="">
    <div class="">
      <h1 class="">Log In</h1>
      <div>
        <label>Email</label>
        <input
          [(ngModel)]="email"
          class=""
          type="email"
          placeholder="Your email"
        />
      </div>
      <div>
        <label>Password</label>
        <input
          [(ngModel)]="password"
          class=""
          type="password"
          placeholder="Password"
        />
      </div>
      <div>
        <button
          (click)="onSubmit()"
          type="submit"
          class=""
          [disabled]="loading"
        >
          {{ loading ? 'Loading' : 'Log In' }}
        </button>
      </div>
      <div>{{ store.user() ?? 'null' }}</div> // this here is always null
    </div>
  </div>`,
  imports: [CommonModule, FormsModule],
  providers: [AuthStore],
})
export class AuthComponent {
  loading = false;
  public store = inject(AuthStore);
  public email: string | null = null;
  public password: string | null = null;

  firebaseService = inject(FirebaseService);

  constructor(private readonly router: Router) {}

  async onSubmit(): Promise<void> {
    if (this.email && this.password) {
      this.firebaseService.logIn(this.email, this.password);
    }
  }
}

And this is after I've seen logs in the console telling me I have an authed user and printing the user out.

So... stuck. Is it a scope thing? a DI thing? an async thing? help!

Upvotes: 1

Views: 207

Answers (1)

Dimitri
Dimitri

Reputation: 1

I´m thinking this may be your problem:

patchState(store, () => ({ user }));

Should be:

patchState(store, { user });

Upvotes: 0

Related Questions