Search code examples
angulartypescriptngrx

NGRX Select: How is the selection working with an inner object?


i am new to NGRX and try to get running a simple NGRX example. I have an interface called LoginState, which is the Object in my Store. The LoginState contains a user object of type IUser. If i select on this object in my component, i don't get returned the IUser, if the user object changes (my effects seem to work fine, because if i just do a subscribe on the store, i get returned all state changes).

Here my configuration:

app.module.ts

imports: [
    ...
    StoreModule.forRoot({loginState: loginReducer})
]

login.actions.ts

export const userLoginRequest = createAction('[Login] user login request');
export const userLoginFacebookSuccess = createAction('[Login] user login facebook success', props<{socialUser: SocialUser}>());
export const userLoginSuccess = createAction('[Login] user login success', props<{user: IUser}>());

login.reducer.ts

export interface LoginState {
    request: boolean;
    socialUser?: SocialUser;
    user?: IUser;
}

export const initialState: LoginState = {
    request: false
};

const reducer = createReducer(initialState,
    on(userLoginRequest, (status, action) => ({
        ...status,
        request: true
     })),
    on(userLoginFacebookSuccess, (status, action) => ({
        ...status,
        socialUser: action.socialUser
    })),
    on(userLoginSuccess, (status, action) => ({
        ...status,
        user: action.user
    }))
);

export function loginReducer(sta: LoginState, action: Action): LoginState {
    return reducer(sta, action);
}

login.effects.ts

import { userLoginRequest, userLoginSuccess, userLoginFacebookSuccess } from './login.action';

@Injectable()
export class LoginEffects {

loginFacebook$ = createEffect(() => this.actions$.pipe(
    ofType(userLoginRequest),
    switchMap(() =>
        this.authService.loginFacebook()
            .pipe(
                 map((socialUser) => {
                     this.tokenService.saveToken(socialUser.authToken);
                     return userLoginFacebookSuccess({socialUser});
                 })
            )
        )
     )
 );

loadUser$ = createEffect(() => this.actions$.pipe(
    ofType(userLoginFacebookSuccess),
    switchMap((act) =>
        this.userService.getUser(act.socialUser)
            .pipe(
                map((usr) => userLoginSuccess({user: usr}))
            )
        )
    )
);


constructor(
  private actions$: Actions,
  private authService: AuthWrapperService,
  private tokenService: TokenStorageService,
  private userService: UserService
) {}

}

Login.component.ts

export class LoginComponent implements OnInit {

localUser: IUser;

constructor(
          private router: Router,
          private store: Store<LoginState>
){}

signInWithFB(): void {
    this.store.dispatch(userLoginRequest());
}

ngOnInit() {
    this.store.select(selectUser).subscribe( (user) => {
    console.log(JSON.stringify(user, undefined, 2));
}
);
}
}

const loginState = (state: LoginState) => state;

export const selectUser = createSelector(
  loginState,
  (state: LoginState) => state.user
);

Thanks a lot for your help!

Cheers,

Tho


Solution

  • the code you have is fine, you need to keep in mind that ngrx selects data instantly, even you don't have it.

    Therefore when this.store.select(selectUser) is executed very first time before the use has been loaded it will return undefined, it's expected.

    When userLoginSuccess has been processed we have a user in the store and the selector will emit the second value that is desired user.

    You can filter emits in a stream if you expect always to get a user. Also please note that your state is under loginState, therefore you need to use createFeatureSelector.

    
    const loginStateFeature = createFeatureSelector<LoginState>('loginState');
    
    export const selectUser = createSelector(
      loginStateFeature,
      (state: LoginState) => state.user
    );
    
    
    ngOnInit() {
      this.store.select(selectUser).pipe(
        filter(user => !!user), // skipping falsy emits.
      ).subscribe((user) => {
        console.log(JSON.stringify(user, undefined, 2));
      });
    }