Ionic2 load ion-toggle or ion-select at will from promise?

I am trying to make a basic settings page.
For this I need to store data and retrieve it from a promise.

Now the problem is if I update the ion-toggle from a promise, the value doesn’t get updated on the view. I am not sure if this is a bug, or how it is supposed to work…

code example below:
HTML:

<ion-toggle [(ngModel)]="a" ionChange="changeSetting()"></ion-toggle>

TS:

    ngOninit() {
        this._settingsService.getSetting('foo').then(
            value => { this.a = value }
        )
    }

So if I initalize the ngModel to a value immediately, it will work, but if instead the value of the model gets updated from a promise then the setting won’t change (will start unchecked)… help

I’m not sure what you’re wanting to happen. Until the promise resolves, there’s no way for anybody to know what a should be.

@rapropos that’s fine. But when the promise resolves the page does not refresh. I.e. the toggle stays off although a is set to true by the promise.

From the 2-way data-bind I would expect the page to dynamically refresh?

Could you provide more information, e.g. the output of ionic info and error messages (if any)?

Can you show the complete code for the path that generates the promise from getSetting()? In particular, what I’m interested in is who is constructing the promise, and specifically if it’s zone-aware.

Thanks for helping out guys!

Here’s my settings.service.ts

import { Injectable } from '@angular/core';
import { Storage, SqlStorage } from 'ionic-angular'

import { SETTINGS_CONSTANTS } from './settings-constants.data';

var SETTINGS_STORAGE_OPTIONS = {
    "name": "foo",
}

@Injectable()
export class SettingsService {
    public storage: Storage;

    constructor() {
        this.storage = new Storage(SqlStorage, SETTINGS_STORAGE_OPTIONS)
        for (var key in SETTINGS_CONSTANTS) {
            this.initSetting(key);
        }
    }

    ngOnInit() {
        this.storage = new Storage(SqlStorage, SETTINGS_STORAGE_OPTIONS)

        for (var key in SETTINGS_CONSTANTS) {
            this.initSetting(key);
        }
    }

    initSetting(key) {
        this.storage.get(key).then((value) => {
            if (!value) {
                value = SETTINGS_CONSTANTS[key];
                this.setSetting(key, value);
            }
        });
    }

    getSetting(key) {
        var getSettingPromise = this.storage.get(key);
        return Promise.resolve(getSettingPromise);
    }

    setSetting(key, value) {
        this.storage.set(key, value);
    }
}

And here is how I am retreiving it:

import {Page, NavController, NavParams} from 'ionic-angular';

// Local services
import { SettingsService } from '../../model/settings/settings.service.ts';

// Local classes
import { RawQuestion } from '../../model/question/raw-question';
import { Question, QuestionBuilder } from '../../model/question/question';
import { Answer } from '../../model/question/answer';

// Page template
@Page({
    templateUrl: 'build/pages/settings/settings.html'
})

export class SettingsPage {
    xxx: number;
    yyy: number;
    zzz: boolean;

    settingsMapping = {
        "xxx": "xxxSetting",
        "yyy": "yyySetting",
        "zzz": "zzzSetting"
    }

    constructor(
        private nav: NavController,
        navParams: NavParams,
        private _questionService: QuestionService,
        private _settingsService: SettingsService
    ) {

    }

    ngOnInit() {
        for (var key in this.settingsMapping) {
            var field = key;
            var settingName = this.settingsMapping[field];
            this.initializeSetting(field, settingName);
        }
    }

    // Asynch promise resolver
    initializeSetting(field, settingName) {
        this._settingsService.getSetting(settingName).then(
            value => {
                this[field] = value; // ***** THE INIT THAT IS NOT WORKING ***** //
                console.log(value);
                console.log(field);
            }
        );
    }

    changeSetting(field) {
        var settingName = this.settingsMapping[field];
        this._settingsService.setSetting(settingName, this[field]);
        console.log(settingName, this[field]);
    }
}

settings.html

<ion-navbar *navbar>
  <ion-title>Settings</ion-title>
</ion-navbar>

<ion-content>
  <ion-list>
    <ion-item>
      <ion-label>XXX</ion-label>
      <ion-select [(ngModel)]="xxx" (change)="changeSetting('xxx')">
        <ion-option value=25>25</ion-option>
        <ion-option value=50>50</ion-option>
        <ion-option value=75>75</ion-option>
        <ion-option value=100>100</ion-option>
      </ion-select>
    </ion-item>

    <ion-item>
      <ion-label>YYY</ion-label>
      <ion-select [(ngModel)]="yyy" (change)="changeSetting('yyy')">
        <ion-option value=15>15</ion-option>
        <ion-option value=30>30</ion-option>
        <ion-option value=60>60</ion-option>
        <ion-option value=90>90</ion-option>
      </ion-select>
    </ion-item>

    <ion-item>
      <ion-label>ZZZ</ion-label>
      <ion-toggle [(ngModel)]="zzz" (change)="changeSetting('zzz')">
      </ion-toggle>
    </ion-item>
  </ion-list>

I don’t see any reason to create another promise in getSetting via Promise.resolve, but I think your main problem is that you’ve got a race condition whereby you can’t know that all the initSetting calls have completed by the time getSetting is called. There are a bunch of ways to deal with this, but the simplest to me would seem to be to ditch the entire initSetting path entirely and move its backstopping logic of using defaults set in SETTINGS_CONSTANTSinto getSetting itself, in the case that nothing is in storage for that key.

Ah interesting. Im not too familiar with promises. Thought the Promise.resolve was mandatory. I removed that but the problem still persists.

As for the race condition.

  1. This is not the first page that loads and settingsService is initialized before.
  2. At this point settings were inited in a pervious run of the app, and stored in DB, so initSetting is really doing nothing.

I think there might be a problem with my setup.
This are my tabs from where I goto the settings:

TS:

import {Alert, Page, NavController, NavParams} from 'ionic-angular';

// Import Local Pages
import {SettingsPage} from '../settings/settings';

// Import Local Services
import {SettingsService} from '../../model/settings/settings.service.ts';

@Page({
    templateUrl: 'build/pages/app-tabs/app-tabs.html',
})

export class AppTabsPage {
    tabSettings = SettingsPage;

    constructor(
        private nav: NavController,
        navParams: NavParams,
        private _settingsService: SettingsService
    ) {

    }
}

and HTML:

<ion-tabs>
    <ion-tab tabIcon="settings" tabTitle="Settings" [root]="tabSettings" ></ion-tab>
</ion-tabs>

Also my app:

import 'es6-shim';
import {App, IonicApp, Platform, MenuController} from 'ionic-angular';
import {StatusBar} from 'ionic-native';

// Import Local Root Page
import {AppTabsPage} from './pages/app-tabs/app-tabs';

// Import Local Services
import {SettingsService} from './model/settings/settings.service.ts';

@App({
    templateUrl: 'build/app.html',
    providers: [
        SettingsService,
    ],
    config: {} // http://ionicframework.com/docs/v2/api/config/Config/
})

class MyApp {
    appTabsPage = AppTabsPage;

    constructor(
        private app: IonicApp,
        private platform: Platform,
        private menu: MenuController
    ) {
        this.initializeApp();
    }

    initializeApp() {
        this.platform.ready().then(() => {
            // Okay, so the platform is ready and our plugins are available.
            // Here you can do any higher level native things you might need.
            StatusBar.styleDefault();
        });
    }
}

and HTML:

<ion-nav id="nav" [root]="appTabsPage" swipe-back-enabled="false"></ion-nav>

NOTE:
When I double click on the tab, the tab refreshes with the correct settings, but this does not happen dynamically :confused:

This is my ionic info output btw:

(node:15173) fs: re-evaluating native module sources is not supported. If you are using the graceful-fs module, please update it to a more recent version.

Your system information:

Cordova CLI: You have been opted out of telemetry. To change this, run: cordova telemetry on.
6.2.0

Ionic Framework Version: 2.0.0-beta.7
Ionic CLI Version: 2.0.0-beta.25
Ionic App Lib Version: 2.0.0-beta.15
ios-deploy version: 1.8.6
ios-sim version: 5.0.8
OS: Mac OS X Yosemite
Node Version: v6.2.0
Xcode version: Xcode 7.2 Build version 7C68


******************************************************
 Dependency warning - for the CLI to run correctly,
 it is highly recommended to install/upgrade the following:

 Please install your Cordova CLI to version  >=4.2.0 `npm install -g cordova`

******************************************************

I believe that your problem might be caused by the following line in your app.ts:

import 'es6-shim';

You need to remove this - check out the guide for updating to beta.6.

I would also recommend you to use Node v4/v5 but not v6 because it’s not supported yet:

@iignatov You were right. Works now.

Will keep that link for reference!

Thanks!