【问题标题】:How return value from subscribed subject back to service订阅主题的返回值如何返回服务
【发布时间】:2021-08-06 11:30:22
【问题描述】:

我有一个问题,是否可以从 Subject.next() 调用中返回值。或任何其他可能的方法如何在所描述的场景中获得响应。

我的情况: 我有一个在应用程序中随处使用的通知服务(它应该向用户显示消息框,最小按钮 ok,我需要知道用户点击了这个按钮):

import { Injectable } from '@angular/core';
import { Subject, Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class NotifyMessagesService {
   private setMessageBoxData = new Subject<INotifyMessage>();

   constructor() { }

   getMessageBoxData(): Observable<INotifyMessage> {
      return this.setMessageBoxData.asObservable();
   }
    
   public notifyMessageBox(message: string, header?: string)/*: Promise<any>*/ {
      /*return new Promise(resolve => {*/
      
      this.setMessageBoxData.next({ message: message, header: header });
      /*resolve();*/ //HERE should go the response from next()
      /* });*/
   }
}

export interface INotifyMessage {
  header?: string;
  message: string;
}

我有一个组件,它订阅了这项服务:

export class NotifyControllerComponent implements OnInit, OnDestroy {

@ViewChild('messageBox', null) messageBox: MessageBoxComponent;

subscription: Subscription;

constructor(private notifyService: NotifyMessagesService) {

   this.subscription = this.notifyService
      .getMessageBoxData()
      .subscribe(message => {
        if (message) {
          this.messageBox.show(`${message.message}`
            , `${message.header}`).then(() => {
              //HERE I need notify NotifyMessagesService back, that user click to the message box
            });
        }
      });

  }

  ngOnInit() { }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

请告知如何更新代码示例以实现: 从我调用服务的任何地方,它会在用户确认消息框后返回

export class AnyComponent implements OnInit{
   constructor(private notifyMessagesService: NotifyMessagesService){
   }

   showMessage(){
      this.notifyMessagesService.notifyMessageBox('Hi','it works').then(res=>{
         console.log('User reaction ' + res);
         //code continue
      });  
   } 
}

-> 所以我认为应该更新服务方法以返回 Promise 或 Observable(如示例中所述),但是如何?

【问题讨论】:

  • 我认为您可能以错误的方式处理此问题。添加一个回调函数作为可选参数,然后在用户单击按钮时执行该回调如何?
  • 类似的东西我已经尝试过了,但效果不好。然后需要为这个回调订阅组件,要知道,用户点击,我需要以某种方式找到哪个代码在等待这个响应以及在哪里继续。
  • 我不知道所有细节和用例,但最简单的方法是在您的服务中添加公共方法,该方法将发出另一个可观察的事件。您也可以用不同的类型将其包装为相同的 observable :) 然后您可以订阅不同的 observable 或相同的不同类型并按照您的意愿进行操作。

标签: angular typescript rxjs


【解决方案1】:

支持回调的自定义确认对话框

这种方法利用ComponentFactoryResolver 创建一个对话框组件,然后我们可以在需要为用户创建确认对话框时动态构建它。

使用这种方法需要解决的问题

  • 构造一个组件,作为我们确认对话框的模板
  • 创建一个可以为我们的对话创建新实例的服务
  • 联系我们的服务ViewContainerRef

最后一点需要妥协,因为服务没有ViewContainerRef 可以自行附加,我们必须编排我们的应用程序,以便我们的对话服务可以访问引用,然后才能创建对话框组件。

确认对话框组件

让我们首先看一下将用作确认对话框的组件。

这是一个带有几个按钮的简单组件,以及我们稍后要传递给它的回调的函数引用。

@Component({
  selector: 'confirm-dialog',
  templateUrl: 'confirm-dialog.component.html',
  styleUrls: ['confirm-dialog.component.css']
})

export class ConfirmDialogComponent implements OnInit {
  componentReference!: ComponentRef<ConfirmDialogComponent>;
  confirmCallback!: () => void | undefined;
  messageOption!: MessageOption;

  constructor() {
  }

  ngOnInit(): void {
  }

  confirm() {
    this.confirmCallback();
    this.destroy();
  }

  destroy() {
    this.componentReference?.destroy();
  }

}
<div class="confirm-dialog">
  <div class="confirm-header">{{messageOption?.title}}</div>
  <div class="confirm-message">{{messageOption?.description}}</div>
  <div class="confirm-button-group">
    <button class="confirm" (click)="confirm()">Confirm</button>
    <button (click)="destroy()">Cancel</button>
  </div>
</div>
export interface MessageOption {
  title: string;
  description: string;
}

为了简洁,我省略了 css

对话服务

现在是下一个难题,将构建我们的确认对话框的服务。

@Injectable({providedIn: 'root'})
export class DialogService {
  private _view!: ViewContainerRef

  constructor(private resolver: ComponentFactoryResolver) {

  }

  set view(ref: ViewContainerRef) {
    this._view = ref;
  }

  get view() {
    return this._view;
  }

  confirmDialog(messageOpts: MessageOption, callback: () => void = () => {
  }) {
    // create the dialog on the view reference.
    const factory = this.resolver.resolveComponentFactory(ConfirmDialogComponent);
    const ref = this._view.createComponent(factory) as ComponentRef<ConfirmDialogComponent>;
    // set the properties
    ref.instance.messageOption = messageOpts;
    ref.instance.componentReference = ref;
    ref.instance.confirmCallback = callback;
  }

  invokedByCallback() {
    console.log("I was invoked via a callback, unless you changed the code you naughty dev you")
  }
}

服务利用ComponentFactoryResolverViewContainerRef 构造ConfirmDialogComponent 的新实例。

然后我们将所需的引用传递到新的ConfirmDialogComponent 实例中,包括其新创建的ComponentRef,这样我们就可以在用户单击确认或取消时删除对话框。

设置 ViewRef

该解决方案在当前形式下不起作用,因为 ViewRef 当前未定义。

要解决这个问题,我们需要在引导您的应用程序的组件中注入服务,并从那里设置 ViewRef。

app.component.ts

export class AppComponent {
  constructor(private dialogService: DialogService, public view: ViewContainerRef) {
    dialogService.view = view;
  }
}

您现在可以像往常一样在 Angular 应用中的任何位置使用 DialogService 来创建确认对话框。

堆栈闪电战

Here is an example of usage

【讨论】:

  • 谢谢 Mikkel Christensen,这正是我所需要的。我很少为我的项目更新它并且它有效。 :) 非常感谢
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-14
  • 1970-01-01
  • 2019-12-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多