2016-04-13 64 views
21

Sto provando a utilizzare la funzione routerCanDeactivate per un componente nella mia app. Il modo semplice per usarlo è il seguente:Promesse Angular 2 e TypeScript

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

Il mio unico problema è che è brutto. Utilizza solo un prompt di conferma generato dal browser. Voglio davvero usare una modal personalizzata, come un modal Bootstrap. Ho il modal Bootstrap restituendo un valore vero o falso in base al pulsante che fanno clic. Il routerCanDeactivate che sto implementando può accettare un valore vero/falso o una promessa che si risolve in vero/falso.

ecco il codice per il componente che ha il metodo 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); 
     } 
    } 
} 

Quando i miei file dattiloscritto compilazione, ottengo i seguenti errori nel terminale:

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

Quando provo a lascia il componente, l'avvio modale, ma poi il componente si disattiva e non aspetta che la promessa si risolva.

Il mio problema sta tentando di elaborare la Promessa in modo che il metodo routerCanDeactivate attenda la promessa da risolvere. C'è un motivo per cui c'è un errore che dice che non c'è la proprietà 'resolve' su Promise<boolean>? Se riesco a elaborare quella parte, cosa devo restituire nel metodo routerCanDeactivate in modo che sia in attesa della risoluzione/rifiuto della promessa?

Per riferimento, here is the DefinitelyTyped Promise definition. C'è chiaramente una funzione di risoluzione e rifiuto lì.

Grazie per il vostro aiuto.

UPDATE

Qui è il file aggiornato, con la promessa di essere inizializzato:

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

e la funzione handleResponse:

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

Si continua a non funzionare correttamente, ma il modale appare e attende la risposta. Quando tu dici di lasciare, rimane sul componente. Inoltre, il primo res registrato è il valore corretto restituito dal componente, ma quello all'interno della funzione .then non è uguale a quello passato alla funzione handleResponse.

ulteriori aggiornamenti

Dopo aver fatto un po 'di lettura, sembra che nella dichiarazione promise, si imposta il valore resolve, e il promise ha quel valore non importa quale. Quindi, anche se in seguito chiamo il metodo .then, non cambia il valore di promise e non riesco a renderlo vero e cambio componenti. C'è un modo per fare in modo che promise non abbia un valore predefinito e che debba attendere fino a quando non viene richiamato il suo metodo .then?

funzioni Aggiornato:

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

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

Grazie ancora per l'aiuto.

Ultimo aggiornamento

Dopo aver guardato molti suggerimenti, ho deciso di creare una classe Deferred. Ha funzionato abbastanza bene, ma quando lo faccio la deferred.reject(anyType), ottengo un errore nella console di:

EXCEPTION: Error: Uncaught (in promise): null 

Questa stessa cosa accade quando passo in null, un string, o un boolean. Il tentativo di fornire una funzione catch nella classe Deferred non ha funzionato.

differita Classe

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

potrei sbagliarmi poiché non so dattiloscritto, ma dovrebbe non prima * inizializzare * 'questo. promise'? Più precisamente, non dovresti restituire una promessa e mantenere il riferimento delle sue funzioni 'resolve' e' reject', quindi chiamarle? – Amit

+0

@Se sei corretto, ho aggiornato il post originale. – pjlamb12

+0

cosa sta invocando la risposta handle? –

risposta

24

Non ho dimestichezza con le API modale bootstrap, ma mi aspetto che ci sia un modo per legarsi a un evento in qualche modo vicino al momento della creazione.

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

} 

si sta cercando di utilizzare il Promise come Deferred. Se desideri questo tipo di API, scrivi una classe 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); 
     } 
    } 
} 
+0

Grazie per il suggerimento! È davvero vicino al lavoro. Gli errori deferred.reject per qualche motivo, e se risolvi con false è un po 'fuori di me, quindi devo solo capire perché lo fa. – pjlamb12

+0

Ho finalmente fatto il giro di questo numero sul mio progetto, e questo funziona al meglio. Fare una lezione differita non ha funzionato prima quando ho provato, ma quando ho usato il tuo suggerimento principale di ascoltare l'evento quando la modale è chiusa, quella parte ha funzionato per me. Grazie! – pjlamb12

2

C'è un motivo per cui si verifica un errore dicendo che non v'è alcuna proprietà 'risolvere' la promessa?

Sì, ed è che tsc non riesce a trovare i tipi corretti per es6-promise. Per evitare questo e altri problemi di battitura nei progetti NG2, as of beta.6 è necessario includere esplicitamente

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

da qualche parte nell'applicazione (in genere questo viene fatto nella parte superiore del file di bootstrap principale). *

Il resto della tua domanda è meno chiaro (ed è probabilmente un XY problem dove x è il problema di tipizzazione discusso sopra). Ma, se non sbaglio nel capire che avete definito una promessa come questa:

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

Come vi aspettate questo da risolvere a nulla, ma falso?

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

è equivalente a

resolve(false); //always false 

* Nota: Questo è (presumibilmente) temporanea, e non sarà necessario nelle versioni beta/release successiva.

Aggiornamento in risposta al tuo commento:

non sembra ovvio come posso aspettare per la funzione handleResponse di correre e aspettare che la risposta

io non sono ancora chiaro su ciò che si sta cercando di fare qui, ma in generale, che ci si vuole avere handleResponse cambio una promessa a sé stante, e poi:

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

Oppure, (gran lunga) più preferibilmente, utilizzare osservabili:

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

Così come ho iniziato a fare più scavi, mi sono reso conto che lo stavo impostando per essere sempre falso, ma non mi sembra ovvio come posso aspettare che la funzione 'handleResponse' venga eseguita e attendi quella risposta prima che la promessa sia risolta . Inoltre, penso che l'errore è venuto dal non inizializzare correttamente l'istanza di promessa, che credo di aver risolto. – pjlamb12

+0

@ pjlamb12 vedi modifica ... è quello che stai cercando? – drewmoore

+0

Ho bisogno di un modo per fare in modo che il metodo 'routerCanDeactivate' attenda la risoluzione su true o false in base al metodo' handleResult', non sono sicuro che sia il modo migliore per farlo. La risposta sopra con la classe 'Deferred' funziona quasi, ma c'è un errore quando la promessa viene risolta in falsi o rifiutati, quindi si è scoperto che non funziona. – pjlamb12

2

Ecco una tecnica che ha funzionato per me. È abbastanza simile alla risposta di @ iliacholy, ma usa un componente modale invece di un modale jQuery. Questo lo rende un approccio un po 'più "angolare 2". Credo che sia ancora pertinente alla tua domanda.

In primo luogo, costruire un componente angolare per il modal:

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

Poi sul componente con RouterCanDeactivate(), l'importazione e fare riferimento l'istanza MyModal:

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

e nel codice della classe:

private myModal: MyModal; 

Creare un metodo che restituisce una promessa, che è sottoscritta all'eventoE mitter su 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; 
} 

e, infine, nel gancio RouterCanDeactivate:

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

Come @drewmoore menzionato, utilizzando osservabili è preferito in angolare 2, ma A) che non era proprio domanda e B) L'hook routerCanDeactivate si risolve in booleano | Prometti, quindi questo approccio mi è sembrato più naturale.

+0

Grazie per il commento, proverò sicuramente a provare questo e vi tornerò su come funziona per me. – pjlamb12

+0

Questa è una risposta fantastica. Ho dovuto modificare la funzione 'userClick()' per ricordare anche l'abbonamento, in modo da poter annullare l'iscrizione. 'this.subscription = this.myModal.buttonResultEmitter.subscribe (...)'. Anche in 'routerCanDeactivate', avevo bisogno di una sezione' this.userClick(). Then (risultato => {console.log (risultato); this.subscription.unsubscribe();}) ' – Jon

2

Dal momento che tutti parlano di Osservabili, ho pensato che avrei dato un'occhiata e ho costruito la risposta di @ petryuno1.

partire con il suo 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); 
    } 
} 

Avanti, andiamo al 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(); 
     }); 
    } 
} 

L'eleganza di osservabili è che ora arriva a scrivere molto meno codice per fare lo stesso cosa.

0

può essere effettuata anche fuori dalla scatola del Angular2 + mondo utilizzando 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); 
    } 
}