Global variables on ionic 2

Well… if using webpack you could declare them from the webpack.config.js but then you would be tied to webpack until you declare those variables in your code, also it’s not a recommended way of doing it since those variables definition would be in the config of the build process and not in your source code, which makes way harder to understand the code since you can’t tell where the hell those variables come from unless you’re the one that made the variables, tough is a good step for polyfills and libraries that need global scope like Chart.js.

In Ionic 2, we have ES6 modules, and having global variables isn’t really recommended. In fact, it’s big anti-pattern with modules

@richardshergold is pointing you in the right direction. Having a dedicated data-store class instead is what you should be doing. This keeps things nice and separated and you can reason about where things are coming from.

1 Like

Hi am really getting an headache on trying to understand how global variable works in ionic 2 … or how we achieve the same thing.

I read on stackoverflow that some suggest different ways becuz Angular 2 syntax is somehow changing recently, I also read, some mention BehaviorSubject… but I dun understand how it works… or does it work in ionic 2.

I come to realise there’s nothing like $rootScope anymore in ionic2, but I still cant manage to understand how.

  1. Say, I was trying to use ngSwitch to cater an app with mutliple language.
  2. I register a service to detect User Language on default (while allowing them to change it in a dropdown menu).
  3. in ionic 1 I just do the detection at the beginning and store it to a variable in the rootScope, users may change and alter the variable.
  4. ngSwitch will watch the variable and switch language content (for some reason my users need to be able to switch the language anytime quickly).

so… how do I do this in ionic2? declaring variable in the app.ts doesnt work… I tried… :disappointed_relieved:

Hi am really getting an headache on trying to understand how global variable works in ionic 2 … or how we achieve the same thing.

I read on stackoverflow that some suggest different ways becuz Angular 2 syntax is somehow changing recently, I also read, some mention BehaviorSubject… but I dun understand how it works… or does it work in ionic 2.

I come to realise there’s nothing like $rootScope anymore in ionic2, but I still cant manage to understand how.

  1. Say, I was trying to use ngSwitch to cater an app with mutliple language.
  2. I register a service to detect User Language on default (while allowing them to change it in a dropdown menu).
  3. in ionic 1 I just do the detection at the beginning and store it to a variable in the rootScope, users may change and alter the variable.
  4. ngSwitch will watch the variable and switch language content (for some reason my users need to be able to switch the language anytime quickly).

so… how do I do this in ionic2? declaring variable in the app.ts doesnt work… I tried… :disappointed_relieved:

Look at ng2-translate. There is an Ionic sample in the README.

thanks @rapropos I will try to make sense of that.

It looks promising on creating some mutli-language app.
However I am using multiple JSON to load some sort of complicated ancient text content to the view…
It is hard to explain, but it seems a bit different from just switching between language.:sob:
I really need NgSwitch to get working to solve the problem in the simplest manner. :thinking:

So I really really really… want to know how to get it working, to store and change a variable and bind it with content.

@richardshergold covered that upthread with links to exactly where to look in the conference app.

How about storing your globals in a singleton class? As in:

  • make a module for your globals, have it contain one class that has all the “globals” you need.
  • use the class as a singleton by decorating it with @Injectable and including it in app.ts and in its ionicBootstrap() call
  • include the class anywhere you need a global

Done!

Thanks @doron
I was sort of trying to achieve it in a similar way by setting up a “globals” class/service with Injectable.

Then, I face a problem, while the variables are being updated, the changes were not known real-time by other component. I ended up using an “emit” function to get it done by triggering an event.

Not sure if this is the proper way of doing it, but it worked.

Yes, @ultradryan, that’s exactly what I was suggesting. I also used this (injectable provider class) solution and had the same problem - that some variables were not being detected as changed.

That turned out to be related to zone.js and angular2 change detection.

One (probably terrible) hack that solved things and always worked for me was to set the variable inside a setTimeout() (or similar) call because that triggers the angular2 change detection. So I did:

In the provider, injectable class that provides a variable’s value to another:

instead of this:

this.memberVariable = value;

i had this:

setTimeout(this.memberVariable = value);

and the change-detection issue went away.

I think this is a hack and at some point I’ll look more into improving on it because I dislike hacks, but it works.

I used this method for access to host variable via provider e.g.

in app.ts top

declare var host : ‘http://xxx’ ;

in provider class :
this.http.get(host+’/api/r/lst’)

But : TypeScript error: path Error TS2304: Cannot find name ‘host’.

@mhartington @richardshergold can you help us (newbies) create ‘best practice’ once and for all to manage global variables in ionic 2? (I know I’m bumping the thread but it’s worth it).

I checked the Conference App as suggested by @richardshergold and looked up the “favorites” variable (not favourites you made a typo :wink: ). And there seem to be a user-data provider in the user-data.ts file.

So I did my own provider:

global-vars.ts:

import { Injectable } from '@angular/core';
import { Events } from 'ionic-angular';
import { Storage } from '@ionic/storage';

  @Injectable()
  export class GlobalVars {

  	_vars: Array<{name: string, value: any}>;

  	constructor(
  		public events: Events,
  		public storage: Storage,
	) {
  		console.log('Hello GlobalVars Provider');
  	}

  	varExist(varName: string): boolean {
   		return (this._vars.indexOf(varName) > -1);
  	};

  	addGlobalVar(varName: string, value: any): void {
  		this._vars.push({
		  name: varName,
		  value: value
		});
  	};

  	removeGlobalVar(varName: string): void {
  		let index = this._vars.indexOf(varName);
  		if (index > -1) {
  			this._vars.splice(index, 1);
  		}
  	};

  }

and in a component,
weightlevel.ts:

import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { ComparePage } from '../compare/compare';
import { ViewChild } from '@angular/core';
import { Slides } from 'ionic-angular';
import { LocalStorageService } from 'angular-2-local-storage';
import { GlobalVars } from '../../providers/global-vars';

@Component({
    selector: 'page-weightlevel',
    templateUrl: 'weightlevel.html'
})
export class WeightlevelPage {

    name: string;

    constructor(
        public navCtrl: NavController,
        public navParams: NavParams,
        private localStorageService: LocalStorageService,
        public globalVars: GlobalVars,
    ) {
        console.log("Executing Weightlevel.ts");
        this.name = 'Max';
        this.globalVars.addGlobalVar("isWebIntegration", false);
    }

    @ViewChild(Slides) slides: Slides;

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

    goToSlide() {
        this.slides.slideTo(2, 500);
    }
}

Can you help me finish this code? It’s not working for now.

Thanks

I think this approach is overly generic, and loses you lots of important compile-time checking, IDE assistance, and self-documentation. If you have a global isWebIntegration boolean, say so. Explicitly put isWebIntegration: boolean in your provider (or expose something that provides a Promise<boolean> if you wish it to be seamlessly integrated with backing storage).

1 Like

I am not sure I get what you say. Can you code it ?

Obviously there are many different options depending on design decisions, but here’s one:

@Injectable()
export class AppContext {
  ready: Promise<any>;
  private _isWebIntegration: boolean;

  constructor(private _storage: Storage) {
    this.ready = Promise.all([
      this._storage.get('isWebIntegration').then((wip) => this._isWebIntegration = wip),
      // &c &c for other similar stuff
    ]);
  }

  // don't call any of these until this.ready resolves

  isWebIntegration(): boolean {
    return this._isWebIntegration;
  }

  setWebIntegration(wip: boolean): Promise<any> {
    this._isWebIntegration = wip;
    return this._storage.set('isWebIntegration', wip);
  }
}
1 Like

Thanks @rapropos , I am trying your code right now, although I don’t understand fully.
So here’s my global-vars.ts with your code:

import { Injectable } from '@angular/core';
import { Storage } from '@ionic/storage';


@Injectable()
export class GlobalVars {
  ready: Promise<any>;
  private _isWebIntegration: boolean;

  constructor(private _storage: Storage) {
    this.ready = Promise.all([
      this._storage.get('isWebIntegration').then((wip) => this._isWebIntegration = wip),
      // &c &c for other similar stuff
    ]);
  }

  // don't call any of these until this.ready resolves

  isWebIntegration(): boolean {
    return this._isWebIntegration;
  }

  setWebIntegration(wip: boolean): Promise<any> {
    this._isWebIntegration = wip;
    return this._storage.set('isWebIntegration', wip);
  }
}

but when I add the dependency injection in weightlevel.ts, I get an error that was not there before. I added the import line, the @Component({...providers: [GlobalVars] the constructor(... public globalVars: GlobalVars and as follows:

import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { ComparePage } from '../compare/compare';
import { ViewChild } from '@angular/core';
import { Slides } from 'ionic-angular';
import { LocalStorageService } from 'angular-2-local-storage';
import { GlobalVars } from '../../providers/global-vars';


@Component({
    selector: 'page-weightlevel'
    , templateUrl: 'weightlevel.html'
    , providers: [GlobalVars]
})
export class WeightlevelPage {

    name: string;
    firstNavParam: boolean;
    data:any = {};
 

    constructor(
        public navCtrl: NavController,
        public navParams: NavParams,
        private storageService: LocalStorageService
        , public globalVars: GlobalVars
    ) {
        console.log("Executing Weightlevel.ts");
        this.name = 'Max';
        // this.globalVars.addGlobalVar("isWebIntegration", false);

        if (storageService.get('firstNavParam') === 'no') {
            this.firstNavParam = false;
        } else {
            this.firstNavParam = true;
        }
        this.data.shareoptions = ['Facebook', 'Twitter', 'Email'];
        this.data.techniqueText = "Intermediate+";
        this.data.frequScaleWording = ['Once a year or  +','Once a month or  +', 'Once a week or  +', 'Once a day or  +'];    
        this.data.fitnessWording = ['Couch potatoe', 'Healthy', 'Sporty', 'Top athlete'];
        this.data.levelName = ['Total noob', 
        'Noob', 
        'Beginner', 
        'Intermediate-', 
        'Intermediate', 
        'Intermediate+', 
        'Good', 
        'Good+', 
        'Very good', 
        'Excellent', 
        'Pro surfer!'];
        this.data.slideIndex = 0;
        this.data.slideTitle = ['Gender',
        'Date of birth',
        'Weight',
        'Height',
        'Technique',
        'Frequency',
        'Fitness',
        'Wetsuit'];
        this.data.slideTitleTranslationIDs = [];
        for (var i = 0; i < this.data.slideTitle.length; i++) {
            this.data.slideTitleTranslationIDs[i] = 'general.' + this.data.slideTitle[i].toLowerCase().replace(/\s/g, '');
        }
    }

    @ViewChild(Slides) slides: Slides;

    ionViewDidLoad() {
        console.log('ionViewDidLoad WeightlevelPage');
    }
  
    goToSlide() {
        this.slides.slideTo(2, 500);
    }
}

and I now get this error:

Runtime Error
Error in ./MyApp class MyApp - caused by: No provider for Storage!
Stack
Error: DI Error

Another question:
Do you think this or this (putting global variables in a ts file) is a good solution? It looks simpler for sure.

Don’t do this. It will completely defeat the entire purpose, because a new instance will be spun up for every component. Put it only in the app module.

I assumed you were using ionic-storage (which you probably should be). If you insist on sticking with that other storage library, use it in place of Storage.

This would all probably go a lot more smoothly for you if you can find the time to sit down and go through the Hall of Heroes and the Ionic conference app.

Hi @rapropos. Ok thanks for your help.
I am gonna do what you suggest and go through both tutorials.

Thanks !

1 Like

Hi @rapropos,

thanks for making me aware of Ionic Storage, it’s seem nice, but it’s a real pain to rewrite all my calls to Storage in a Promise scope! I am not sure I’m ready to take the step…
But I am giving it a try for this GlobalVars component. So I have your code:

import { Injectable } from '@angular/core';
import { Storage } from '@ionic/storage';

@Injectable()
export class GlobalVars {
	ready: Promise<any>;
	private _isWebIntegration: boolean;

	constructor(
		private _storage: Storage
	) {
		this.ready = Promise.all([
			this._storage.get('isWebIntegration').then((wip) => this._isWebIntegration = wip)
			// &c &c for other similar stuff
			]);
	}

	// don't call any of these until this.ready resolves

	isWebIntegration(): boolean {
		return this._isWebIntegration;
	}

	setWebIntegration(wip: boolean): Promise<any> {
		this._isWebIntegration = wip;
		return this._storage.set('isWebIntegration', wip);
	}
}

I am calling it from my component weightlevel.ts:

import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { ComparePage } from '../compare/compare';
import { ViewChild } from '@angular/core';
import { Slides } from 'ionic-angular';
import { LocalStorageService } from 'angular-2-local-storage';
import { GlobalVars } from '../../providers/global-vars';

@Component({
    selector: 'page-weightlevel'
    , templateUrl: 'weightlevel.html'
})
export class WeightlevelPage {

    @ViewChild(Slides) slides: Slides;

    name: number;
    firstNavParam: boolean;
    data: any = {};
    // data: Object = {};

    
    // data.techniqueText: string;
    // data.frequScaleWording: string[];
    // data.fitnessWording: string[];
    // data.levelName: string[];

    constructor(
        public navCtrl: NavController
        , public navParams: NavParams
        , private storageService: LocalStorageService
        // , private MathService: MathService
        , public globalVars: GlobalVars
    ) {
        
        console.log("Executing Weightlevel.ts");
        this.globalVars.setWebIntegration(true).then( (shit) => { console.log( "isWebIntegration is set to = ", this.globalVars.isWebIntegration())} );

But when I set the boolean value, I have run the app twice to see the right value in the console.log (see the screenshot, the value is true in IndexedDB, and false in the console):

Why does it need to run twice to get the actual IndexedDB value ?

Is there some sort of race condition setting up the database?