Angular 2 и TypeScript Promises

Я пытаюсь использовать функцию routerCanDeactivate для компонента в моем приложении. Простой способ его использования заключается в следующем:

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

Моя единственная проблема в том, что это уродливо. Он просто использует приглашение на подтверждение браузера. Я действительно хочу использовать пользовательский модальный, например, Bootstrap modal. У меня есть модальность Bootstrap, возвращающая значение true или false на основе кнопки, которую они нажимают. routerCanDeactivate Я реализую, могу принять истинное/ложное значение или обещание, которое разрешает true/false.

Вот код для компонента, который имеет метод routerCanDeactivate:

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

Когда мои файлы TypeScript компилируются, я получаю следующие ошибки в терминале:

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

Когда я пытаюсь покинуть компонент, начинается модальный, но затем компонент деактивируется и не ждет, пока обетование решится.

Моя проблема заключается в попытке выполнить обещание, чтобы метод routerCanDeactivate ожидал, что обещание будет разрешено. Есть ли причина, по которой возникает ошибка, указывающая, что на Promise<boolean> нет свойства 'resolve'? Если я смогу выполнить эту часть, то что я должен вернуть в методе routerCanDeactivate, чтобы он ожидал разрешения/отказа от обещания?

Для справки здесь определено Определенное обещание обещания. Там явно есть функция разрешения и отклонения.

Спасибо за вашу помощь.

UPDATE

Вот обновленный файл с инициализацией Promise:

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

и handleResponse:

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

Он по-прежнему работает неправильно, но модальный показывается и ждет ответа. Когда вы говорите "да", он остается на компоненте. Кроме того, первый res, который регистрируется, является правильным значением, возвращаемым компонентом, но функция внутри внутри .then не совпадает с той, которая была передана функции handleResponse.

Дополнительные обновления

После выполнения большего числа показаний в объявлении promise он устанавливает значение resolve, а promise имеет это значение независимо от того, что. Поэтому, хотя позже я вызываю метод .then, он не меняет значение promise, и я не могу сделать его истинным и переключить компоненты. Есть ли способ сделать promise не иметь значения по умолчанию и что он должен ждать, пока его метод .then не будет вызван?

Обновленные функции:

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

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

Еще раз спасибо за помощь.

Последнее обновление

После рассмотрения многих предложений я решил создать класс Deferred. Он работал очень хорошо, но когда я делаю deferred.reject(anyType), я получаю сообщение об ошибке в консоли:

EXCEPTION: Error: Uncaught (in promise): null

То же самое происходит, когда я перехожу в null, a string или boolean. Попытка предоставить функцию catch в классе Deferred не работает.

Отложенный класс

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

Ответ 1

Я не знаком с загрузочным модальным api, но я бы ожидал, что когда-нибудь создаст способ привязки к закрытому событию.

export class MyComponent implements CanDeactivate {

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

}

Вы пытаетесь использовать Promise как Deferred. Если вам нужен такой API, напишите себе класс Deferred.

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(): Promise<boolean> {
        $("#modal").modal("show");
        return this.deferred.promise;
    }

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

Ответ 2

Есть ли причина, по которой возникает ошибка, заявляющая, что в Promise нет свойства разрешения?

Да, и это то, что tsc не может найти правильные тиски для es6-promise. Чтобы избежать этой и других проблем с типизацией в проектах ng2, с бета-версии 6, вам нужно явно включить

///<reference path="node_modules/angular2/typings/browser.d.ts"/>

где-то в вашем приложении (обычно это делается в верхней части основного файла начальной загрузки). *

Остальная часть вашего вопроса менее понятна (и, скорее всего, это проблема XY, где x - проблема типизации, рассмотренная выше). Но, если я правильно понимаю, что вы определили такое обещание:

private promise: Promise<boolean> = new Promise(
    ( resolve: (res: boolean)=> void, reject: (res: boolean)=> void) => {
        const res: boolean = false;
        resolve(res); // <=== how would this ever resolve to anything but false??? 
    }
);

Как вы ожидаете, что это решится на что-либо, кроме false?

const res: boolean = false;
resolve(res); //always false

эквивалентно

resolve(false);  //always false

* note: это (предположительно) временное и не понадобится в более поздних версиях бета-версии.

Обновить в ответ на ваш комментарий:

не кажется очевидным, как я могу ждать выполнения функции handleResponse и ждать ответа

Я все еще не понимаю, что вы здесь делаете, но в целом вы хотели бы вернуть handleResponse свое обещание, а затем:

private promise: Promise<boolean> = new Promise((resolve, reject) => {
  handlePromise.then(resultOfHandleResult => {
    //assuming you need to process this result somehow (otherwise this is redundant) 
    const res = doSomethingWith(resultOfHandleResult); 
    resolve(res); 
  })
});

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

Или, (далеко) более предпочтительно использовать Observables:

var promise = handleResult() //returns an observable
                .map(resultOfHandleResult => doSomethingWith(resultOfHandleResult))

Ответ 3

Вот техника, которая сработала для меня. Это довольно похоже на ответ @iliacholy, но использует модальный компонент вместо модального jQuery. Это делает его несколько более "Angular 2". Я считаю, что это все еще актуально для вашего вопроса.

Сначала создайте компонент Angular для модального:

import {Component, Output, EventEmitter} from '@angular/core;
@Component({
    selector: 'myModal',
    template: `<div class="myModal" [hidden]="showModal">
          <!-- your modal HTML here... -->
          <button type="button" class="btn" (click)="clickedYes()">Yes</button>
          <button type="button" class="btn" (click)="clickedNo()">No</button>
        </div>
    `
})

export class MyModal{
    private hideModal: boolean = true;
    @Output() buttonResultEmitter = new EventEmitter();
    constructor(){}
    openModal(){
        this.hideModal = false;
    }
    closeModal(){
        this.hideModal = true;
    }
    clickedYes(){
        this.buttonResultEmitter.emit(true);
    }
    clickedNo(){
        this.buttonResultEmitter.emit(false);
    }
}

Затем на вашем компоненте с RouterCanDeactivate() импортируйте и ссылайтесь на экземпляр MyModal:

import {MyModal} from './myModal';
@Component({
    ....
    directives: [MyModal]
})

и в коде класса:

private myModal: MyModal;

Создайте метод, возвращающий обещание, которое подписано на eventEmitter на myModal:

userClick(): Promise<boolean> {
    var prom: new Promise<boolean>((resolve, reject) => {
        this.myModal.buttonResultEmitter.subscribe(
            (result) => {
                if (result == true){
                    resolve(true);
                } else {
                    reject(false);
                }
         });
     });
     return prom;
}

и, наконец, в крюке RouterCanDeactivate:

routerCanDeactivate(next: ComponentInstruction, prev: ComponentInstruction) {
    this.myModal.openModal();
    return this.userClick().catch( function(){return false;} );
}

Как упоминалось в @drewmoore, использование Observables предпочтительнее в Angular 2, но A), что не было вашим вопросом, и B) Крюк routerCanDeactivate разрешен для boolean | Обещай, поэтому этот подход казался мне более естественным.

Ответ 4

Поскольку все говорят о Observables, я подумал, что я посмотрю и опишу ответ @petryuno1.

Начиная с его ModalComponent:

import {Component, Output, ViewChild} from '@angular/core;
@Component({
    selector: 'myModal',
    template: `<div class="myModal" [hidden]="showModal">
          <!-- your modal HTML here... -->
          <button type="button" class="btn" (click)="clickedYes($event)">Yes</button>
          <button type="button" class="btn" (click)="clickedNo($event)">No</button>
        </div>
    `
})

export class MyModal{
    private hideModal: boolean = true;
    private clickStream = new Subject<boolean>();
    @Output() observable = this.clickStream.asObservable();

    constructor(){}
    openModal(){
        this.hideModal = false;
    }
    closeModal(){
        this.hideModal = true;
    }
    clickedYes(){
        this.clickStream.next(true);
    }
    clickedNo(){
        this.clickStream.next(false);
    }
}

Затем переходим к AppComponent:

import { Component, ViewChild} from '@angular/core';
import {MyModal} from './myModal';
import {Subscription} from "rxjs";

@Component({
    ....
    directives: [MyModal]
})

export class AppComponent {
    @ViewChild(ConfirmModal) confirmModal: ConfirmModal;
    constructor(){...};

    public showModal(){
        this.myModal.openModal();
        this.subscription = this.myModal.observable.subscribe(x => {
            console.log('observable called ' + x)
// unsubscribe is necessary such that the observable doesn't keep racking up listeners
            this.subscription.unsubscribe();
        });
    }
}

Элегантность наблюдаемых заключается в том, что теперь мы получаем гораздо меньше кода, чтобы делать то же самое.

Ответ 5

Также можно сделать из коробки в мире Angular2 +, используя Subject s:

export class MyComponent {
  private subject: Subject<boolean>;

  routerCanDeactivate(): PromiseLike<boolean> {
    $('#modal').modal('show');
    return this.subject.toPromise();
  }

  handleRespone(res: boolean) {
    this.subject.next(res);
  }
}