【问题标题】:Angular 2 and TypeScript PromisesAngular 2 和 TypeScript 承诺
【发布时间】:2016-08-05 05:36:38
【问题描述】:

我正在尝试将routerCanDeactivate 函数用于我的应用程序中的组件。简单的使用方法如下:

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

我唯一的问题是它很难看。它只是使用浏览器生成的确认提示。我真的很想使用自定义模式,比如 Bootstrap 模式。我让 Bootstrap 模式根据他们单击的按钮返回真值或假值。我正在实现的routerCanDeactivate 可以接受真/假值或解析为真/假的承诺。

下面是具有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>'.

当我尝试离开组件时,模式启动,但随后组件停用并且不等待承诺解决。

我的问题是试图解决 Promise,以便 routerCanDeactivate 方法等待 Promise 解决。是否有错误提示 'resolve' 上没有 Promise&lt;boolean&gt; 属性的原因?如果我可以解决这部分问题,我必须在 routerCanDeactivate 方法中返回什么,以便它等待承诺的解决/拒绝?

供参考,here is the DefinitelyTyped Promise definition。那里显然有一个解决和拒绝功能。

感谢您的帮助。

更新

这是更新后的文件,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 的值,我也无法使其成为 true 并切换组件。有没有办法让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

当我传入nullstringboolean 时,也会发生同样的事情。尝试在 Deferred 类中提供 catch 函数不起作用。

延迟类

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

【问题讨论】:

  • 我可能错了,因为我不知道打字稿,但你不应该先初始化this.promise吗?更准确地说,您不应该返回一个承诺,并保留其resolvereject 函数的引用,然后调用这些函数吗?
  • @Amit 你是对的,我更新了原帖。
  • 什么是调用句柄响应?
  • @iliacholy 这是一个在模态关闭时调用的函数。这是我制作的 Angular 2 模态组件的输出。我还确认它被正确调用并且响应正在到来。
  • 不是您问题的答案,但您的承诺定义非常冗长。可以是private promise = new Promise&lt;boolean&gt;((resolve, reject) =&gt; resolve(false));

标签: typescript angular


【解决方案1】:

也可以使用Subjects 在 Angular2+ 世界中开箱即用地完成:

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

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

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

【讨论】:

    【解决方案2】:

    我不熟悉引导模式 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);
            }
        }
    }
    

    【讨论】:

    • 感谢您的建议!真的离上班很近了。由于某种原因, deferred.reject 错误,如果你用 false 解决它也有点吓坏了,所以我只需要弄清楚它为什么会这样。
    • 我终于在我的项目中回到了这个问题,这效果最好。在我尝试之前创建一个 Deferred 类没有用,但是当我使用你在模式关闭时监听事件的最佳建议时,这部分对我有用。谢谢!
    【解决方案3】:

    由于每个人都在谈论 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();
            });
        }
    }
    

    可观察对象的优雅之处在于,我们现在可以编写更少的代码来做同样的事情。

    【讨论】:

      【解决方案4】:

      这是一种对我有用的技术。它与@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;
      

      创建一个返回promise的方法,它订阅了myModal上的eventEmitter:

      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 提到的,在Angular 2 中使用Observables 是首选,但是A)这不是你的问题,B)routerCanDeactivate 钩子解析为布尔|承诺,所以这种方法对我来说似乎更自然。

      【讨论】:

      • 感谢您的评论,我一定会尝试这个,并回复您它对我的工作原理。
      • 这是一个很棒的回应。我不得不修改userClick() 函数来记住订阅,这样我才能取消订阅。 this.subscription = this.myModal.buttonResultEmitter.subscribe(...)。同样在routerCanDeactivate,我需要this.userClick().then(result=&gt;{console.log(result); this.subscription.unsubscribe();})的一部分
      【解决方案5】:

      是否有错误提示 Promise 上没有“resolve”属性的原因?

      是的,就是 tsc 找不到 es6-promise 的正确类型。为了避免在 ng2 项目中出现这个和其他类型的问题,as of beta.6 你需要明确地包含

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

      在您的应用程序中的某个位置(通常在主引导文件的顶部完成)。*

      您的问题的其余部分不太清楚(可能是XY problem,其中 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??? 
          }
      );
      

      您如何期望这会解决除错误之外的任何问题?

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

      等价于

      resolve(false);  //always false
      

      *注意:这(可能)是临时的,在以后的 beta/release 版本中不需要。

      根据您的评论进行更新:

      我如何等待 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;
          });
      }
      

      或者,(far) 更优选地,使用 Observables:

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

      【讨论】:

      • 所以当我开始进行更多挖掘时,我意识到我将其设置为始终为 false,但我似乎并不明显如何等待 handleResponse 函数运行并等待承诺解决之前的响应。另外,我认为错误来自未正确初始化承诺实例,我相信我已经修复了。
      • @pjlamb12 查看编辑...这是您要找的吗?
      • 我确实需要一种方法让routerCanDeactivate 方法等待基于handleResult 方法解析为真或假,我只是不确定最好的方法。上面 Deferred 类的答案几乎可以工作,但是当 promise 被解析为 false 或被拒绝时会出现错误,因此结果它不起作用。
      猜你喜欢
      • 1970-01-01
      • 2017-06-18
      • 1970-01-01
      • 2018-01-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-12-22
      • 2015-09-09
      相关资源
      最近更新 更多