Trouble understanding how observers work


#1

Hello,

I recently started using Ember and, I thought I started getting a graps on the damn thing and now I am so confused…

Context: I am currently trying to implement multi language on my site, I am implementing intl.

I created a menu to select my locale:

{{view "select" 
      classNames="ui-language-selector"
      content=locales 
      optionValuePath="content.id" 
      optionLabelPath="content.text" 
      value=currentLocale
    }}

And my application controller:

export default Ember.Controller.extend({

  intl: Ember.inject.service('intl'),

  init() {
    this.set('currentLocale', this.get('intl.locale')[0]);
  },

  currentLocale: null,

  currentLocaleHasChanged: Ember.observer('currentLocale', function() {
    console.log('change');
    this.get('intl').setLocale(this.get('currentLocale'));
  }),

  locales: Ember.computed('intl.locales', function() {
    let locales = [];
    for(let loc of this.get('intl.locales')) {
      locales.push({
        id: loc,
        text: this.get('intl').t('language.' +loc)
      });
    }
    return locales;
  })

});

This works well, when I choose a new locale in my dropdown, the content is translated.

Now I want to do several other things when the locale (intl.locale) changes, hence, I wanted to add observer in that property:

Update my document’s title (with ember-cli-document-title)

title: Ember.observer('intl.locale', function() {
    let path = this.controller.get('currentPath');
    return this.get('intl').t(path+'.title') +' - '+ this.get('intl').t('site.title');
  })

Does not work.

Another component doing stuff whilst changing locale:

export default Ember.Component.extend({

  intl: Ember.inject.service('intl'),

  localeHasChanged: Ember.oberver('intl.locale', function() {
    //Do something
  })

});

Does not work.

Am I missing something about observers? Services?

Thanks


#2

You’ve misunderstood a little about how observers work, they aren’t the same as computed properties.

A computed property returns a value, an observer does something when the property it observes is changed but does not return a value so you need to use set if you want to change a value. That is why you first observer code snippet doesn’t do anything. The second one has a typo in it - “oberver” should be “observer” :slight_smile:

On a side note, keep persisting with Ember, there are hurdles and a bit of a learning curve early on but it is worth persevering.


#3

Hello, thanks for taking some time.

Yep, understood, I tried so many things to try to have it work that the code is not pretty anymore.

I tried to use Ember.computed for the title and it indeed should be Ember.observer in the second case, but even this way, it does not work, I really am confused… Can we observe properties from a service? Should I create some sort of alias in my controller instead?

I will try more experiments later this evening, I am so confused right now…


#4

This is slightly deceiving, and this should have been documented. In ember-intl you’re not actually setting intl.locale. Since we need more control over the set behavior, you invoke setLocale to change the locale. This was done intentionally to sanitize the input and future-proof the API to allow for eventual async operations to settle before triggering the locale change.

If you want to be notified when setLocale was invoked you can do the following:

export default Ember.Component.extend({
  intl: Ember.inject.service('intl'),
  init() {
    this._super(...arguments);
    this.get('intl').on('localeChanged', this, 'handleLocaleChanged');
  },
  willDestroy() {
    this._super(...arguments);
    this.get('intl').off('localeChanged', this, 'handleLocaleChanged');
  },
  handleLocaleChanged() {
    // ... things you want to do when the locale changes
  }
});

Update: I published a fix in 2.9.5 to break the computed property’s cache that backs intl.locale, which will cause your observer to work as you had expected them to. It’s best I support that scenario to avoid confusion until I make localeChanged a public API.


#5

Hello!

Awesome thanks! Should I understand that I can update the addon and it should work with observers now?

I will try when I get back to my place.

Edit: I tried and does not work :confused:, however, I made it work with the event. Thanks!


#6

I noticed something weird, after switching language 3 times, the language won’t change anymore the 4th time, anyone ever experiences something like that??


#7

Have never seen this, if you can reproduce it with a fresh example app feel free to push it to github and open and issue on ember-intl. I ran the dummy app and have test coverage around changing locales, so this is unlikely in ember-intl but a side effect of something else happening in your app.


#8

As a side note is it a good idea to use controllers and views with a new app are they not being phased out ? Components are all the rage now


#9

@jasonmit tried with a fresh app, same issue…

@Ben_Glancy is this going to happen? Because since the time they’re talking about it…


#10

The docs say it almost immediately on the guides for controllers. Also I think it does make sense I found controllers a bit too easy to get messy… I think they are probably going to put in more support for components. I used to get confused by the subtle differences between views and components and I prefer these things to be the same makes it easier to think about app structure.


#11

Alrighty, I’ll focus on developing components then.


#12

Can you push it to github so I can look


#13

@jasonmit I wanted to limit the number of translation files I have to create for locales with the same language, e.g.:

  • en: en-US, en-CA, en(used by some browsers)
  • fr: fr-FR, fr-CA, fr

Hence, I did something like this:

config file: intl: { baseLocale: ‘en’, supportedLocales: { ‘en’: [‘en’, ‘en-EN’, ‘en-CA’, ‘en-US’], ‘fr’: [‘fr’, ‘fr-FR’] } }

Then in the app route: import Ember from ‘ember’; import config from ‘…/config/environment’;

export default Ember.Route.extend({

intl: Ember.inject.service(‘intl’),

init() { this.get(‘intl’).reopen({ initiateLocale() { let loc = (navigator.language && this.isSupportedLocale(navigator.language)) ? this.getLanguage(navigator.language) : (navigator.userLanguage && this.isSupportedLocale(navigator.userLanguage)) ? this.getLanguage(navigator.userLanguage) : config.intl.baseLocale; this.setLocale(loc); },

  //Adding two extra functions to support more languages
  //with the supportedLocales added in the config
  isSupportedLocale(loc) {
    for(let language in this.get('supportedLocales')) {
      if(this.get('supportedLocales.' +language).indexOf(loc)) {
        return true;
      }
    }
    return false;
  },

  getLanguage(loc) {
    for(let language in this.get('supportedLocales')) {
      if(this.get('supportedLocales.' +language).indexOf(loc)) {
        return language;
      }
    }
    return null;
  }

});

this.set('intl.locales', Object.keys(config.intl.supportedLocales));
this.set('intl.supportedLocales', config.intl.supportedLocales);
this.get('intl').initiateLocale();
this.get('intl').on('localeChanged', this, function() {
  //This is to update the title when the locale change without transition
  this.refresh();
});

}

});

So then I only have to manage fr and en. But maybe you are offering the same capabilities but I have not been able to find out how.


#14

I removed the controller and view and went all in component and I do not get the bug anymore, so far so good :slight_smile: