Angular 2 and TypeScript Promises

后端 未结 5 1476
孤独总比滥情好
孤独总比滥情好 2021-02-03 22:45

I\'m trying to use the routerCanDeactivate function for a component in my app. The simple way to use it is as follows:

routerCanDeactivate() {
             


        
相关标签:
5条回答
  • 2021-02-03 23:22

    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(): Promise<boolean> {
        let $modal = $('#modal').modal();
        return new Promise<boolean>((resolve, reject) => {
          $modal.on("hidden.bs.modal", 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(): 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 讨论(0)
  • 2021-02-03 23:24

    Since everyone is talking about Observables, I figured that I would take a look and build upon @petryuno1's answer.

    Starting with his 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);
        }
    }
    

    Next, we go to the 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();
            });
        }
    }
    

    The elegance of observables is that we now get to write a lot less code to do the same thing.

    0 讨论(0)
  • 2021-02-03 23:25

    Here is a technique that worked for me. It is pretty similar to @iliacholy's answer, but uses a modal component instead of a jQuery modal. This makes it a somewhat more "Angular 2" approach. I believe it's still relevant to your question.

    First, build an Angular Component for the 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);
        }
    }
    

    Then on your component with RouterCanDeactivate(), import and reference the MyModal instance:

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

    and in the class code:

    private myModal: MyModal;
    

    Create a method returning a promise, which is subscribed to the eventEmitter on 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;
    }
    

    and finally, in the RouterCanDeactivate hook:

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

    As @drewmoore mentioned, using Observables is preferred in Angular 2, but A) that wasn't your question, and B) The routerCanDeactivate hook resolves to boolean | Promise, so this approach seemed more natural to me.

    0 讨论(0)
  • 2021-02-03 23:30

    Is there a reason why there is an error saying that there is no 'resolve' property on Promise?

    Yes, and it's that tsc can't find the correct typings for es6-promise. To avoid this and other typing problems in ng2 projects, as of beta.6 you need to explicitly include

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

    somewhere in your application (typically this is done at the top of your main bootstrap file).*

    The rest of your question is less clear (and is likely an XY problem where x is the typings problem discussed above). But, if I'm correct in understanding that you've defined a promise like this:

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

    How are you expecting this to resolve to anything but false?

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

    is equivalent to

    resolve(false);  //always false
    

    *note: this is (presumably) temporary, and won't be necessary in later beta/release versions.

    Update in response to your comment:

    it doesn't seem obvious how I can wait for the handleResponse function to run and wait for that response

    I'm still not clear on what you're trying to do here, but in general, you'd want to have handleResponse return a promise of its own, and then:

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

    Or, (far) more preferably, use Observables:

    var promise = handleResult() //returns an observable
                    .map(resultOfHandleResult => doSomethingWith(resultOfHandleResult))
    
    0 讨论(0)
  • 2021-02-03 23:34

    Can also be done out of the box in the Angular2+ world using Subjects:

    export class MyComponent {
      private subject: Subject<boolean>;
    
      routerCanDeactivate(): PromiseLike<boolean> {
        $('#modal').modal('show');
        return this.subject.toPromise();
      }
    
      handleRespone(res: boolean) {
        this.subject.next(res);
      }
    }
    
    0 讨论(0)
提交回复
热议问题