Ionic2 File-transfer callback scoping issue


#1

Im having issues with call back scopes within the file-transfer plugin. I want my user to take a picture, upload to the server and the server to send back the image ID for reference in a larger form.

The capture and upload process works great but when I trigger the success callback I appear to be running into a scoping issue. The value gets set into the form but on the next field edit the value is removed. I believe it might have to do with the scope of the call back and it only updating a copy of the question class and not the actual question class. Here is the code

    export class DynamicFormQuestionComponent {

      @Input() question:QuestionBase<any>;
      @Input() form:ControlGroup;
      @Input() idx:any;
      @Input() taskID: any;

      success(response){
        this.form.value[this.question.key] = parseInt(response.response);
        this.form.controls[this.question.key].markAsTouched();
      }
      uploadImage(key){

        Camera.getPicture(options).then((imageData) => {
          
          // Upload the file
          var ft = new FileTransfer();
          var filename = "q" + this.question.key + ".jpg";
          var options = new FileUploadOptions();
          
          options.fileKey = "file";
          options.fileName = filename;
          options.mimeType = "image/jpeg";
          options.chunkedMode = false;
          options.headers = {
            'Content-Type' : undefined,
            'Authorization' : 'Bearer '+localStorage.getItem('id_token') // this should send through the JWT
          };
          options.params = {
            questionId: this.question.key,
            taskId: this.taskID,
            fileName: filename
          };

          ft.upload(imageData, "http://thisthing.com/api/v1/image/save", this.success.bind(this), this.failed, options);

        }, (err) => {
          this.imageProcess = "Camera Error";
          setTimeout(() => {
            this.imageProcess = null;
          }, 2000);
        });
      }

    }

Any pointers would be great!


#2

I’m not sure if form.value is meant to be used for updating controls’ values or only to retrieve them (I’m still new to Angular myself so I may be wrong). Try to update control’s value using the updateValue() function. I would also recommend you to use arrow functions for the callbacks (to avoid scoping issues and because it’s more common practice).

success(response) {
    this.form.controls[this.question.key].updateValue(parseInt(response.response));
    this.form.controls[this.question.key].markAsTouched();
}

#3

I’ve had the same problem with my uploading logic. See thread. It is probably because file transfer is not a ionic native plugin it yet and somehow it does not know how to update the bindings.

I’ve used a dirty jquery workaround to update the dom elements in my case.


#4

I think dtaalbers is on to something.

Could you try wrapping your success handler in a setTimeout?

success(response){
	setTimeout(() => {
		this.form.value[this.question.key] = parseInt(response.response);
		this.form.controls[this.question.key].markAsTouched();
	}, 0);
}

I think the setTimeout will force angular to trigger an update (I’m sure there’s a more direct approach using Zone.js, but I’m new to angular 2 as well).

EDIT: Try this:

import {NgZone} from 'angular2/angular2';

export class DynamicFormQuestionComponent {

	@Input() question:QuestionBase<any>;
	@Input() form:ControlGroup;
	@Input() idx:any;
	@Input() taskID: any;

	constructor(protected _zone: NgZone) {

	}

	success(response){
		this._zone.run(() => {
			this.form.value[this.question.key] = parseInt(response.response);
			this.form.controls[this.question.key].markAsTouched();
		});
	}

	[...snip...]

}

You could also call callbacks like this:

ft.upload(imageData, "http://thisthing.com/api/v1/image/save", this._zone.run(this.success), this._zone.run(this.failed), options);

#5

Thanks guys!! I cant seem to give you both credit, sorry!

It was a combination of using this._zone.run(() => {} along with .updateValue that cracked it!

Here is the working version for reference

import {Component, Input, NgZone} from 'angular2/core';
import {Camera} from 'ionic-native';
import {ControlGroup}     from 'angular2/common';
import {QuestionBase}     from '../../models/question-base';

@Component({
  selector:'df-question',
  templateUrl:'build/components/dynamic-form-question/dynamic-form-question.component.html'
})
export class DynamicFormQuestionComponent {

  @Input() question:QuestionBase<any>;
  @Input() form:ControlGroup;
  @Input() idx:any;
  @Input() taskID: any;

  imageProcess: any;
  imageLink: any;
  imageId: any;
  ft: any;

  constructor(protected _zone: NgZone){
    this.imageProcess = null;
    this.imageLink = null;
  }

  get isValid() { return this.form.controls[this.question.key].valid; }

  success(response){
    this._zone.run(() => {
      this.form.controls[this.question.key].updateValue(parseInt(response.response));
      this.form.controls[this.question.key].markAsTouched();
      this.imageProcess = "Success";
      setTimeout(() => {
        this.imageProcess = null;
      }, 2000);
    });
  }

  failed(error){
    this._zone.run(() => {
      this.imageLink = null;
      this.imageProcess = "Upload error, please try again";
      setTimeout(() => {
        this.imageProcess = null;
      }, 2000);
    });

  }

  // Upload an image to the server and add the campaign and question ID.
  // This will allow background processing of the image
  uploadImage(key){

    let options = {
      quality: 50,
      destinationType: 1,
      allowEdit: false,
      targetWidth: 800,
      targetHeight: 600
    };

    Camera.getPicture(options).then((imageData) => {
      
      this.imageLink = imageData;

      this.imageProcess = "Uploading";
      
      // Upload the file
      this.ft = new FileTransfer();
      var filename = "q" + this.question.key + ".jpg";
      var options = new FileUploadOptions();
      
      options.fileKey = "file";
      options.fileName = filename;
      options.mimeType = "image/jpeg";
      options.chunkedMode = false;
      options.headers = {
        'Content-Type' : undefined,
        'Authorization' : 'Bearer '+localStorage.getItem('id_token') // this should send through the JWT
      };
      options.params = {
        questionId: this.question.key,
        taskId: this.taskID,
        fileName: filename
      };

      this.ft.upload(imageData, "http://app.com/api/v1/image/save", this.success.bind(this), this.failed.bind(this), options);

    }, (err) => {
      this.imageProcess = "Camera Error";
      setTimeout(() => {
        this.imageProcess = null;
      }, 2000);
    });
  }

}

#6

You should check out my final working code below, perhaps it can help you.