Vingtoft
Vingtoft

Reputation: 14656

How to preserve the 'this' instance in a callback function

In Angular2 I have a component that uses a service for uploading a file to Amazon S3.

My component (simplified):

private _loading = true;

// use service to upload file
this._s3service.uploadFile(fileObject, this.uploadCallback)

// use this function as a callback
uploadCallback(err, data) {
  this._loading = false; // this crashes because of 'this' is referring to service instead of component
} 

My service (simplified):

    private getS3(): any {
       // Get or create AWS instance
       return s3;
    }

    public uploadFile(selectedFile, callback): boolean {
       this.getS3().upload({
            Key: key_name,
            ContentType: file.type,
            Body: file,
            StorageClass: 'STANDARD',
            ACL: 'private'
          }, function(err, data){ // <=== What to do here?!
            callback(err, data)
          });
    }

The problem is that when the callback function is fired from the service, this is referring to the service and this._loading cannot be found.

Question: How can I preserve the this instance in my callback function, (this in the callback must point to component and not service)

Upvotes: 4

Views: 520

Answers (3)

lintu
lintu

Reputation: 1102

You could change your uploadFile to return a promise.

and handle the error case from your component as it should be. Something like

    public uploadFile(selectedFile): boolean {
        return new Promise((resolve, reject) => {
          this.getS3().upload({
            Key: key_name,
            ContentType: file.type,
            Body: file,
            StorageClass: 'STANDARD',
            ACL: 'private'
         }, function(err, data){ // <=== What to do here?!
            resolve(err, data)
        });
       }
  });

And you could from your component do this

 this._s3service.uploadFile(fileObject).then((err, data)=> {
   this._loading = false;
 });

Upvotes: 0

n00dl3
n00dl3

Reputation: 21584

While @Gunter is right, I think you want to keep the this in the callback you actualy pass to that function :

uploadCallback(err, data) {
  this._loading = false; // this is the "this" you want to keep
} 

then it would be something like this:

this._s3service.uploadFile(fileObject, ()=>this._loading = false);
// or 
this._s3service.uploadFile(fileObject, ()=>this.uploadCallback());
// or
this._s3service.uploadFile(fileObject, this.uploadCallback.bind(this));

Also note that it might be interesting to use an Observable instead of passing a callback :

public uploadFile(selectedFile): Observable<any> { // "<any>" because I don't know what is the type of "data"
    return Observable.create((observer) => {
        this.getS3().upload({
            Key: key_name,
            ContentType: file.type,
            Body: file,
            StorageClass: 'STANDARD',
            ACL: 'private'
        }, (err, data)=> {
            if(err)
              observer.error(err);
            else
              observer.next(data);
            observer.complete();
        });
    });
}

then:

this._s3service.uploadFile(fileObject).subscribe(data=>console.log(data))

Upvotes: 3

G&#252;nter Z&#246;chbauer
G&#252;nter Z&#246;chbauer

Reputation: 658027

Use arrow functions

  }, (err, data) => { // <=== What to do here?!

they are for exactly that purpose, for this to keep pointing at the class instance where the function is declared.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions

If you pass a function reference .bind(this) might be more convenient, because it doesn't require to list the parameters at all while => would require them twice

myCallback(err, data){ // <=== What to do here?!
        callback(err, data)
}

public uploadFile(selectedFile, callback): boolean {
   this.getS3().upload({
        Key: key_name,
        ContentType: file.type,
        Body: file,
        StorageClass: 'STANDARD',
        ACL: 'private'
      }, this.myCallback.bind(this));
}

The same which arrow functions

public uploadFile(selectedFile, callback): boolean {
   this.getS3().upload({
        Key: key_name,
        ContentType: file.type,
        Body: file,
        StorageClass: 'STANDARD',
        ACL: 'private'
      }, (err, data) => this.myCallback(err, data));
}

Upvotes: 11

Related Questions