Search code examples
angulartypescriptngxs

How to fix 'Cannot set property userInfo$ of [object Object] which has only a getter' error in Angular


I am developing a webapp with the use of NGXS, and want to understand why I am getting this specific error. I am fetching data from an API, and want to display it on the column. Within the console log, I am able to see the proper data, but I am not able to display the data.

This is for a webapp, which is being developed by using Angular and NGXS. I did try avoiding to subscribe, and just retrieve the data asynchronously within the HTML, but then it wasn't able to find the properties defined in the model. I got the errors

Property 'name' does not exist on type 'UserInfo[]'.
Property 'email' does not exist on type 'UserInfo[]'.
@Select(UserLoginSate.getInfo) userInfo$: Observable<UserInfo[]>;
  info = {};

  constructor(private store: Store) {
  }

  ngOnInit() {
    this.store.dispatch(new GetUserInfo()).subscribe(result => {
      this.userInfo$ = result;
    });

The model is defined with two properties:

export interface UserInfo{
    name: string;
    email: string;
}

I expect it to display the user info (i.e. name and email), but I am getting the error Cannot set property userInfo$ of [object Object] which has only a getter. However, the proper data can be found in the console log.

EDIT:

Here is the state:

export class UserLoginStateModel{
    info: UserInfo[];
}

@State<UserLoginStateModel>({
    name: 'info',
    defaults: {
        userInfo: []
    }
})

export class UserLoginState {

    constructor(private userService: UserService) {}

    @Selector()
    static getInfo(state: UserLoginStateModel) {
        return state.userInfo;
    }

    @Action(GetUserInfo)
    getUserInfo({getState, setState}: StateContext<UserLoginStateModel>) {
        return this.userService.fetchUserInfo().pipe(tap((result) => {
            const state = getState();
            setState({
                ...state,
                userInfo: result,
            });
        }));
    }
}

This is the part of layout, which I expect will show the data:

<nb-layout-column>
      <div>
          name: {{ userInfo$.name }}
          <br/>
          email: {{ userInfo$.email }}
        </div>
  </nb-layout-column>

Solution

  • Subscribe is not necesery:

    ngOnInit() {
        this.store.dispatch(new GetUserInfo())
    }
    

    It could be better if You set up userInfo as an null so if user is not logged in there will be null.

    @State<UserLoginStateModel>({
        name: 'info',
        defaults: {
           userInfo: null
        }
    })
    

    When You change your @state change @Select(UserLoginSate.getInfo) userInfo$: Observable<UserInfo[]>; to @Select(UserLoginSate.getInfo) userInfo$: Observable<UserInfo>;.

    Template change to:

    <nb-layout-column>
        <div *ngIf="userInfo$ | async as userInfo">
            <p>name: {{ userInfo.name }}</p>
            <p>email: {{ userInfo.email }}</p>
        </div>
    </nb-layout-column>
    

    You cannot display Observables and Promises with no *ngIf statement. Thay are asynchronus.