NgRx Store Observable usage in ngModel


#1

Hi, This might be a very dummy question. I’m very new to NGRX/store.

I have

<ion-select [(ngModel)]="myobject.name">
...
</ion-select>

but this doesn’t recognize any changes to myobject.
although if I do this in the template, it shows the value correctly.

{{ (myobject | async)?.name }}

So i am trying to use async pipe inside the [(ngModel)] tag like below

<ion-select [(ngModel)]="( myobject | async)?.name">
...
</ion-select>

but this gives me an error that can’t use the pipe inside ngModel. What should be the right way to use Observable with ngModel?

Thanks!


#2

The ngrx/store approach almost competes with ngmodel. ngrx/store encourages smart containers and dumb components. That’s what I do, and I’ve adopted onPush change detection as well, to leverage that approach. So I don’t use ngmodel at all for properties in storage. I do use ngmodel with ion-selects to, for example, allow the user to control sorts – like a select that clicks between “New to Old” and “Old to New”. But that sorting direction is stored in a sorting provider, not in the Store.

If you explain in English what you want to achieve, we can probably tell you how to do it. I can’t tell from your code what you are trying to make happen. Is myobject in your local Store? If so, you can’t bind it directly with ngModel. You interact with the object indirectly, using Store Actions and Effects.


#3

I can’t paste the whole code, but basically i had this object in my component

settings = {
    language: 'en'
}

and in my template, i had

<ion-select
        [(ngModel)]="settings.language"
        (ionChange)="settingsChanged($event, 'language')"
        interface="popover">
        <ion-option value="en">English</ion-option>
        <ion-option value="ko">한국어</ion-option>
      </ion-select>

those are the old ways without using ngrx/store.

I can’t post my whole code here, but i’m trying to achieve the same thing but using ngrx/store.
below is the reducer.

import { Settings } from '../_models/settings.model';

interface Action {
  type: string,
  payload: any
}

export const initialSettings: Settings = {
  language: 'en',
  theme: 'light'
}

export const settingsReducer = (state: Settings = initialSettings, action: Action) => {
  let newState;
  switch(action.type) {
    case 'GET_SETTINGS':
      newState = Object.assign({}, state);
      return newState;
    case 'UPDATE_SETTINGS':
      newState = Object.assign({}, state, action.payload);
      return newState;
    default:
      return state;
  }
}

and in my component i have the code like this

import { Component, ViewChild } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
import { Slides } from 'ionic-angular';
import {
  IonicPage,
  NavController,
  NavParams,
  ModalController
} from 'ionic-angular';
import { DownloadPage } from '../download/download';
import { Settings } from '../../_models/settings.model';

interface AppState {
  settings: Settings
}

interface Theme {
  name: string,
  label: string
}

@IonicPage()
@Component({
  selector: 'page-options',
  templateUrl: 'options.html',
})
export class OptionsPage {
  @ViewChild(Slides) slides: Slides;

  settings: Observable<Settings>;
  themes: Theme[] = [
    { name: 'light', label: 'THEME_LIGHT' },
    { name: 'dark', label: 'THEME_DARK' },
    { name: 'rose', label: 'THEME_ROSE' }
  ]
  
  constructor(
    public navCtrl: NavController,
    public modalCtrl: ModalController,
    public navParams: NavParams,
    private store: Store<AppState>
  ) {
    this.settings = this.store.select('settings');
  }

  ionViewDidLoad() {
    console.log('ionViewDidLoad OptionsPage');
  }

  openDownload() {
    let modal = this.modalCtrl.create(DownloadPage);
    modal.present();
  }

  themeChanged() {
    let currentIndex = this.slides.getActiveIndex();

    this.settingsChanged(this.themes[currentIndex].name, 'theme'); // TODO: ???? why not working???
  }

  settingsChanged(value: string, field: string) {
    let payload: any = {};
    payload[field] = value;
    this.store.dispatch({
      type: 'UPDATE_SETTINGS',
      payload: payload
    });
  }

}

and in the template i have

<ion-item>
      <ion-label>
        {{ 'LANGUAGE' | translate }}
      </ion-label>
      <!-- ngModel won't work. async pipe not working with ngModel -->
      <ion-select
        [(ngModel)]="settings.language"
        (ionChange)="settingsChanged($event, 'language')"
        interface="popover">
        <ion-option value="en">English</ion-option>
        <ion-option value="ko">한국어</ion-option>
      </ion-select>
    </ion-item>

but since settings is now an Observable, if i try to display the actual value using async pipe, it works but i am not sure how i can use [(ngModel)] to bind the data with the object.


#4

Hi

I would be surprised to see it work at all, as streams are not intended to be manipulated through ngModel, only operators. And changing data does not work upstream…

Regards
Tom


#5

Yeah. then, my question is how can i default <ion-select> to en (english)?


#6

By instantiating this.settings.language to en?


#7

#8

well, i know that. here i’m trying to use ngrx/store instead of doing it in traditional way.

this.settings = this.store.select('settings');

i’m doing this which does have language value in it. it’s just that this is observable, i can’t use the async pipe to get the value. that is the problem.


#9

Yes
And my point is that you cant use ngmodel on a stream.
Tom


#10

At least not in two way binding mannet


#11

Got it. thanks.

this.subscription = this.store.select('settings').subscribe(settings => {
      this.settings = settings;
    });

i did this and now it works.