Ionic2 File-transfer callback scoping issue

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!

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();
}
1 Like

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.

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);
1 Like

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);
    });
  }

}
1 Like

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