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