Search code examples
ember.jsember.js-2

Ember pass callback to service - undefined this


I have a service to upload many large files in chunks

export default Ember.Service.extend({
  run(files, callbacks) {
    // ugly async FileReader and ajax
    // calling callbacks during the process
  }
})

I need a bunch of callbacks to show progress, but the problem is that this in undefined within these callbacks

export default Ember.Component.extend({
  upload: Ember.inject.service(),

  didInsertElement() {
    // bind fileinput change event to set up pending files
  },

  ondonesingle(self, file, uuid) {
    // this is undefined
    // self is real this
  },

  actions: {
    submit() {
      let callbacks = {
        ondoneall: this.ondoneall,
        ondonesingle: this.ondonesingle,
        onprogressall: this.onprogressall,
        onprogresssingle: this.onprogresssingle,
        onerror: this.onerror,
        object: this // will be passed as first argument to each callback
      };
      this.get('upload').run(this.get('pending_files'), callbacks);
    },
  }
})

To work around this I have to carry reference to this everywhere.

It works, but it feels terribly wrong. What is the best practice to do this in Ember? Observable property also feels wrong, how would I observe progress of 2000 files? Put everything in one big object and share it across the app?


Solution

  • The reason this is is coming back undefined is that when function is passed around it's context (this) changes. You can create a new function that has it's context explicitly set using function.bind. When using function.bind no matter where you call the new function or what value / property you assign it to, it's context will remain the same.

    see MDN for Function.prototype.bind

    export default Ember.Component.extend({
      upload: Ember.inject.service(),
    
      didInsertElement() {
        // bind fileinput change event to set up pending files
      },
    
      ondonesingle(file, uuid) {
      },
    
      actions: {
        submit() {
          let callbacks = {
            ondoneall: this.ondoneall.bind(this),
            ondonesingle: this.ondonesingle.bind(this),
            onprogressall: this.onprogressall.bind(this),
            onprogresssingle: this.onprogresssingle.bind(this),
            onerror: this.onerror.bind(this)
          };
          this.get('upload').run(this.get('pending_files'), callbacks);
        },
      }
    })