pjlamb12 pjlamb12 - 3 months ago 57
TypeScript Question

Angular 2 and TypeScript Promises

I'm trying to use the

routerCanDeactivate
function for a component in my app. The simple way to use it is as follows:

routerCanDeactivate() {
return confirm('Are you sure you want to leave this screen?');
}


My only issue with this is that it's ugly. It just uses a browser generated confirm prompt. I really want to use a custom modal, like a Bootstrap modal. I have the Bootstrap modal returning a true or false value based on the button they click. The
routerCanDeactivate
I'm implementing can accept a true/false value or a promise that resolves to true/false.

Here is the code for the component that has the
routerCanDeactivate
method:

export class MyComponent implements CanDeactivate {
private promise: Promise<boolean>;

routerCanDeactivate() {
$('#modal').modal('show');
return this.promise;
}

handleRespone(res: boolean) {
if(res) {
this.promise.resolve(res);
} else {
this.promise.reject(res);
}
}
}


When my TypeScript files compile, I get the following errors in the terminal:

error TS2339: Property 'resolve' does not exist on type 'Promise<boolean>'.
error TS2339: Property 'reject' does not exist on type 'Promise<boolean>'.


When I try to leave the component, the modal starts, but then the component deactivates and doesn't wait for the promise to resolve.

My issue is trying to work out the Promise so that the
routerCanDeactivate
method waits for the promise to resolve. Is there a reason why there is an error saying that there is no
'resolve'
property on
Promise<boolean>
? If I can work that part out, what must I return in the
routerCanDeactivate
method so that it waits for the resolution/rejection of the promise?

For reference, here is the DefinitelyTyped Promise definition. There is clearly a resolve and reject function on there.

Thanks for your help.

UPDATE

Here is the updated file, with the Promise being initialized:

private promise: Promise<boolean> = new Promise(
( resolve: (res: boolean)=> void, reject: (res: boolean)=> void) => {
const res: boolean = false;
resolve(res);
}
);


and the
handleResponse
function:

handleResponse(res: boolean) {
console.log('res: ', res);
this.promise.then(res => {
console.log('res: ', res);
});
}


It still doesn't work correctly, but the modal shows up and waits for the response. When you say yes leave, it stays on the component. Also, the first
res
that is logged is the correct value returned from the component, but the one inside
.then
function is not the same as the one passed in to the
handleResponse
function.

More Updates

After doing some more reading, it appears that in the
promise
declaration, it sets the
resolve
value, and the
promise
has that value no matter what. So even though later on I call the
.then
method, it doesn't change the value of the
promise
and I can't make it true and switch components. Is there a way to make the
promise
not have a default value and that it has to wait until the its
.then
method is invoked?

Updated functions:

private promise: Promise<boolean> = new Promise((resolve, reject) => resolve(false) );

handleResponse(res: any) {
this.promise.then(val => {
val = res;
});
}


Thanks again for the help.

Last Update

After looking at many suggestions, I decided to create a
Deferred
class. It's worked pretty well, but when I do the
deferred.reject(anyType)
, I get an error in the console of:

EXCEPTION: Error: Uncaught (in promise): null


This same thing happens when I pass in
null
, a
string
, or a
boolean
. Trying to provide a
catch
function in the
Deferred
class didn't work.

Deferred Class

export class Deferred<T> {
promise: Promise<T>;
resolve: (value?: T | PromiseLike<T>) => void;
reject: (reason?: any) => void;

constructor() {
this.promise = new Promise<T>((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
}
}

Answer

I'm not familiar with the bootstrap modal api, but I'd expect there to be a way to bind to a close event somehow when creating it.

export class MyComponent implements CanDeactivate {

  routerCanDeactivate() {
    let $modal = $('#modal').modal();
    return new Promise<boolean>((resolve, reject) => {
      $modal.on("close", result => {
        resolve(result.ok);
      });
      $modal.modal("show");
    });
  }

}

You're trying to use the Promise like Deferred. If you want that kind of API, write yourself a Deferred class.

class Deferred<T> {

  promise: Promise<T>;
  resolve: (value?: T | PromiseLike<T>) => void;
  reject:  (reason?: any) => void;

  constructor() {
    this.promise = new Promise<T>((resolve, reject) => {
      this.resolve = resolve;
      this.reject  = reject;
    });
  }
}

export class MyComponent implements CanDeactivate {

    private deferred = new Deferred<boolean>();

    routerCanDeactivate() {
        $('#modal').modal('show');
        return this.deferred.promise;
    }

    handleRespone(res: boolean) {
        if(res) {
            this.deferred.resolve(res);
        } else {
            this.deferred.reject(res);
        }
    }
}
Comments