12
votes

I'm attempting to encapsulate my ngrx state in a shared service class to abstract the implementation details away from my components.

Example service class that is registered in my app.module.ts providers

@Injectable()
export class PatientService {

  state: Observable<PatientState>;

  constructor(
    private store: Store<AppState>,
  ) {
    this.state = store.select<PatientState>('patients');
  }

}

I have verified my actions, reducer and effects are working as expected, however, when I subscribe to the service state in a component, it returns undefined.

Example component subscription using the shared service:

@Component({
  ...
})
export class DashboardComponent implements OnInit {

  constructor(
    private patientService: PatientService,
  ) {}

  ngOnInit(): void {
    // dispatches action to load patient from API
    this.patientService.loadPatient();

    this.patientService.state.subscribe(patientState => {
        console.log('patientState', patientState);
        // Does not work. Logs undefined.
    });
  }

}

If I subscribe directly to the store, it works as expected.

Example:

@Component({
  ...
})
export class DashboardComponent implements OnInit {

  constructor(
    private patientActions: PatientActions,
    private store: Store<AppState>,
  ) {}

  ngOnInit(): void {
    this.store.dispatch(this.patientActions.loadPatient());

    this.store.select<PatientState>('patients').subscribe(patientState => {
        console.log('patientState', patientState);
        // Works as expected.
    });
  }

}

What am I doing wrong?

3
I had the similar issue: when a component subscribes to state it gets state === undefined always. It was very confused for me, but finally I've found corresponding reducer is not implemented magic code: default: return state;. Hope it'll help to somebodyMergasov

3 Answers

5
votes

I solved this by following Mergasov's advice and set a default case condtion:

I had the similar issue: when a component subscribes to state it gets state === undefined always. It was very confused for me, but finally I've found corresponding reducer is not implemented magic code: default: return state;

Here's how that looks in the context of a larger reducer.ts:

  export function reducer(state: EntityState= initialEntityState, action:  actions.EntityAction) {
    switch (action.type) {
      case actions.CREATE_ENTITY_SUCCESS:
      case actions.UPDATE_ENTITY_SUCCESS: {
        const EntityDetails = action.payload;
        const entities = {
          ...state.entities,
          [Entitydetails.Id]: EntityDetails,
        };
        return {
          ...state,
          error: null,
          entities,
        };
      }
      default : {
        return state;
      }
    }
 }

Previously, my code did not have a default condition, and was returning undefined due to that fact. adding the default condition to the reducer resolved the issue.

0
votes

I have implemented a similar usecase. Your attempt is good, and I got it working this way:

@Injectable()
export class PatientService {
  // Define Observable
  patientState$: Observable<PatientState>;
  constructor(private store: Store<AppState>) {
    // Get data from the store
    this.patientState$ = store.select<PatientState>('patients');
  }

  getState(): PatientState {
    // subscribe to it so i don't have to deal with observables in components
    let patientState: PatientState = null;
    this.patientState$.subscribe(ps => patientState = ps);
    return patientState;
  }
}

Now you are able to call this method from any component you want like that:

@Component({
  ...
})
export class DashboardComponent implements OnInit {
  patientState = new PatientState;
  constructor(
    private patientService: PatientService,
  ) {}

  ngOnInit(): void {
    // Simply get the Object from the store without dealing with observables
    this.patientState = this.patientService.getState();
  }

}

I use the $ at the end of observables so I know every time I touch a variable if it is a Observable or not, this way I don't get confused.

0
votes

I think you're missing a this reference,

this.state = store.select<PatientState>('patients');

should be

this.state = this.store.select<PatientState>('patients');