I am currently developing a basic login application using Angular, NgRx Store, and Firebase. I have implemented a login action that is supposed to log in to Firebase and store the credentials in the store. However, it seems like there is an issue in my implementation. After dispatching the action, the firebase request is successful, the credentials are returned, but the store does not update as expected. Instead, I encounter the following error in the console:
ERROR TypeError: Cannot freeze
at Function.freeze (<anonymous>)
at freeze (ngrx-store.js:843)
at ngrx-store.js:857
at Array.forEach (<anonymous>)
at freeze (ngrx-store.js:845)
at ngrx-store.js:857
at Array.forEach (<anonymous>)
at freeze (ngrx-store.js:845)
at ngrx-store.js:857
at Array.forEach (<anonymous>)
I am aware that the state is immutable and this error might be related to that, but I am struggling to identify the exact problem. Below is the code snippet I am working with:
State:
export interface CoreState {
readonly credentials: UserCredential
}
export const initialCoreState: CoreState = {
credentials: {} as UserCredential
}
Actions:
const signIn = createAction(
`${actionPrefix} Sign In`,
props<{ email: string, password: string }>()
);
const signInSuccess = createAction(
`${actionPrefix} Sign In Success`,
props<{ readonly userCredential: UserCredential }>()
);
const signInFailure = createAction(
`${actionPrefix} Sign In Failure`,
props<{ readonly error: string }>()
);
export const CoreActions = {
signIn,
signInSuccess,
signInFailure
}
Effects:
@Injectable()
export class CoreEffects {
signIn$ = createEffect(() =>
this.actions$.pipe(
ofType(CoreActions.signIn),
switchMap(({ email, password }) =>
this.authenticationService.signInWithEmailAndPassword(email, password)
.pipe(
map((userCredential: UserCredential) => {
return CoreActions.signInSuccess({ userCredential })
}
),
catchError(error =>
of(CoreActions.signInFailure({ error: error.statusText }))
)
)
),
)
);
constructor(
private readonly actions$: Actions,
private readonly authenticationService: AuthenticationService
) {}
}
Reducer:
export const coreReducer = createReducer<CoreState>(
initialCoreState,
on(CoreActions.signInSuccess, (state, { userCredential }) => {
console.log('Inside Reducer');
return {
...state,
credentials: userCredential
}
})
)
Upon further investigation, I discovered that by using Object.freeze(userCredentials)
before passing it from effects to signInSucess
, and then applying Object.freeze(userCredentials)
again before assigning it in the reducer, it resolves the issue. However, this workaround feels unconventional to me. If anyone can provide insights or point out where I may be going wrong, I would greatly appreciate it. Thank you!
Edit: Added AuthenticationService and App Component
AuthenticationService
@Injectable({
providedIn: 'root'
})
export class AuthenticationService {
constructor(private readonly angularFireAuth: AngularFireAuth) { }
signOut(): Observable<void> {
return from(this.angularFireAuth.signOut());
}
createUserWithEmailAndPassword(email: string, password: string): Observable<UserCredential> {
return from(this.angularFireAuth.createUserWithEmailAndPassword(email, password));
}
signInWithEmailAndPassword(email: string, password: string): Observable<UserCredential> {
return from(this.angularFireAuth.signInWithEmailAndPassword(email, password));
}
}
App Component
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'firebase-auth';
constructor(private readonly store: Store) {}
onClick(): void {
const email = '<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="3a4e5f494e7a4e5f494e14595557">[email protected]</a>';
const password = '123456';
this.store.dispatch(CoreActions.signIn({ email, password }))
}
}