Как вернуть значение от подписанного субъекта обратно в службу

#angular #typescript #rxjs

Вопрос:

У меня есть вопрос, можно ли вернуть значение из Subject.next() вызов. Или любой другой возможный пример того, как получить ответ в описанном сценарии.

Моя ситуация: У меня есть служба уведомлений, которая используется везде в приложении (она должна показывать пользователю окно сообщения, минимум с кнопкой ок, и мне нужно знать, что пользователь нажимает на эту кнопку):

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

-> поэтому я думаю, что метод службы должен быть обновлен, чтобы возвращать обещание или наблюдаемый (как указано в примерах), но как?

Комментарии:

1. Я думаю, что вы, возможно, поступаете неправильно. Как насчет добавления функции обратного вызова в качестве необязательного аргумента, а затем выполнения этого обратного вызова всякий раз, когда пользователь нажимает кнопку?

2. Что — то подобное я уже пробовал, но это не очень хорошо. Затем компонент должен быть подписан на этот обратный вызов, чтобы знать, что пользователь нажимает, и мне нужно каким-то образом найти, какой код ожидает этого ответа и где продолжить.

3. Я не знаю всех деталей и вариантов использования, но проще всего было бы просто добавить общедоступный метод в свой сервис, который будет выдавать другое наблюдаемое событие. Вы можете обернуть его так же наблюдаемо, но с другим типом 🙂 Затем вы можете подписаться на эту другую наблюдаемую или ту же с другим типом и делать все, что пожелаете.

Ответ №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")
  }
}
 

Служба использует ComponentFactoryResolver и ViewContainerRef для создания нового экземпляра ConfirmDialogComponent .

Затем мы передаем нужные нам ссылки в новый ConfirmDialogComponent экземпляр, включая его недавно созданный ComponentRef , чтобы мы могли удалить диалоговое окно, когда пользователь нажмет «Подтвердить» или «отменить».

Настройка видовой ссылки

Решение не работает в его текущем виде, так как в ViewRef настоящее время оно не определено.

Чтобы исправить это, нам нужно внедрить службу в компонент, который загружает ваше приложение, и установить оттуда ссылку на представление.

приложение.компонент.ts

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

Теперь вы можете использовать DialogService, как обычно, в любом месте вашего приложения Angular, для создания диалогов подтверждения.

Стакблитц

Вот пример использования

Комментарии:

1. Спасибо, Миккель Кристенсен, это именно то, что мне нужно. Я немного обновил его для своего проекта, и он работает. 🙂 Большое спасибо