Angular ngrx migration error "Property 'select' does not exist on type 'Observable<State>'"

2.1k views Asked by At

I am attempting to migrate an existing application from ngrx v2 to v4, and am having a bit of trouble with a scenario I do not see covered in the migration document. The migration document has you remove @ngrx/core, but I am not sure what to do with my reducers that import from @ngrx/core/add/operator/select and select from an Observable. I am getting the error "Property 'select' does not exist on type 'Observable'" from all of the reducers.

/actions/action-with-payload.ts -- Workaround for the migration

import { Action } from '@ngrx/store';

export class ActionWithPayload implements Action {
    type: string;
    payload?: any;
}

/actions/users-list.ts

export class UsersListActions {
    static LOAD_USERS_LIST = '[UserManagement/Users List] Load Users List';
    loadUsersList(): ActionWithPayload {
        return {
            type: UsersListActions.LOAD_USERS_LIST
        };
    }

    static LOAD_USERS_LIST_SUCCESS = '[UserManagement/Users List] Load Users List Success';
    loadUsersListSuccess(users: User[]): ActionWithPayload {
        return {
            type: UsersListActions.LOAD_USERS_LIST_SUCCESS,
            payload: users
        };
    }
}

/reducers/users-list.ts

export interface UsersListState {
  loaded: boolean;
  loading: boolean;
  entities: User[];
}

const initialState: UsersListState = {
  loaded: false,
  loading: false,
  entities: [],
} 

export default function (state = initialState, action: ActionWithPayload): UsersListState {
  switch (action.type) {
    case UsersListActions.LOAD_USERS_LIST:
      return Object.assign({}, state, {
        loading: true
      });
    case UsersListActions.LOAD_USERS_LIST_SUCCESS:
      return Object.assign({}, state, {
        loaded: true,
        loading: false,
        entities: action.payload
      });
    default:
      return state;
  }
};

export function getLoaded() {
  return (state$: Observable<UsersListState>) => state$
    .select(s => s.loaded);
}

export function getLoading() {
  return (state$: Observable<UsersListState>) => state$
    .select(s => s.loading);
}

export function getUsers() {
  return (state$: Observable<UsersListState>) => state$
    .select(s => s.entities);
}

/reducers/index.ts

import usersListReducer, * as fromUsers from './users-list';

export interface UserManagementState {
    usersList: fromUsers.UsersListState,
};

export { usersListReducer }

export function getUsersListState() {
    return (state$: Observable<UserManagementState>) => state$
        .select(s => s.usersList);
}

export function getUsers() {
    return compose(fromUsers.getUsers(), getUsersListState());
}

export function getUsersLoaded() {
  return compose(fromUsers.getLoaded(), getUsersListState());
}

export function getUsersLoading() {
    return compose(fromUsers.getLoading(), getUsersListState());
}

/pages/user-list.page.ts

export class UserListPage {
    private users$: Observable<User[]>;
    private usersLoading$: Observable<boolean>;

    constructor(
        private store: Store<UserManagementState>,
    ) {
        this.users$ = store.let(getUsers());
        this.usersLoading$ = store.let(getUsersLoading());
    }
}

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';    
import { usersListReducer } from '/reducers';    
import { AppComponent }  from './app.component';

@NgModule({
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    AppRoutingModule,
    SharedModule.forRoot(),
    StoreModule.provideStore({
        usersList: usersListReducer,
        ...
    })
  ],
  declarations: [AppComponent],
  bootstrap: [AppComponent],
  providers: [ 
    ...
  ]
})
export class AppModule { }
2

There are 2 answers

0
Aravind On

Create an interface which contains all the states as its property as below,

export interface CompanyAppStore {
    userState: UsersListState;
    employeeState: EmployesListState;
}

Create a reducer factory which is a collection of all the reducers as below,

export const reducers: ActionReducerMap<CompanyAppStore> = {
    userState: userStateReducer.reducer,
    employeeState: employeeStateReducer.reducer
};

Access the reducers by importing them as below,

import * as userStateReducer from './user.state.reducer';
import * as employeeStateReducer from './employee.state.reducer';

Your component can be injected with the app interface which was created in the step 1 as

constructor(private appStore: Store<CompanyAppStore>){ }

Now you can select a particular state using

this.appStore
      .select(states => states.userState)
      .subscribe(..)
1
Vamshi On

I am not sure why you need to import select. I never did that even in version 2. I will try to show how the code should look like for a single entity.

import {
  Action,
  ActionReducerMap,
  createFeatureSelector,
  createSelector 
} from '@ngrx/store';

export function userReducer(state = initialState, action: ActionWithPayload): UsersListState {
  switch (action.type) {
    case UsersListActions.LOAD_USERS_LIST:
      return Object.assign({}, state, {
        loading: true
      });
    case UsersListActions.LOAD_USERS_LIST_SUCCESS:
      return Object.assign({}, state, {
        loaded: true,
        loading: false,
        entities: action.payload
      });
    default:
      return state;
  }
};

export const getLoadedState = (state: UsersListState) => state.loaded; 
export const getLoadingState = (state: UsersListState) => state.loading;
export const getEntitiesState = (state: UsersListState) => state.entities;

export userStateSelector = createFeatureSelector<UsersListState>('users');
export const loadedState = createSelector(userStateSelector, getLoadedState);
export const loadingState = createSelector(userStateSelector, getLoadingState);
export const userState = createSelector(userStateSelector, getEntitiesState);

module

imports: StoreModule.forFeature('users', userReducer);

In component use select like this :

public users$: Observable<User[]>;
constructor(private userStore: Store<UsersListState>){
    this.users$ = this.userStore.select(userState);
}

There might be lot of compilation errors, but this is just a pseudocode to explain you how things work.

You can check https://github.com/ngrx/platform/tree/master/example-app for detailed example. But personally I struggled a lot to understand how it works. I will probably write a blog this weekend on this if you are interested.