Как подписаться на Observable of Component, внедренный через @angular/cdk/portal?

Я пытаюсь реализовать базовую (очень простую) модальную реализацию. У меня есть ModalService и ModalComponent.

ModalService создает экземпляр ModalComponent и внедряет его на страницу с помощью @angular/cdk/portal.

Я могу заставить модальное окно отображаться просто отлично :-)

У ModalComponent есть свойство Observable, на которое я хочу подписаться ModalService, чтобы при нажатии кнопки «закрыть» в модальном окне можно было выдать значение, а ModalService могло закрыть модальное окно.

Однако когда компонент выдает значение, Служба не воздействует на него. Со стороны службы это выглядит так, как будто я подписан, но со стороны компонента показывает 0 наблюдателей.

Я подумал, может быть, я мог бы использовать типичный @Output() EventEmitter, но я не уверен, что смогу его подключить, так как в модальном классе, поскольку дочерний элемент изначально не существует.

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

Любые идеи, что я делаю неправильно?

Сервис

export class ModalService {

    private modalPortal: ComponentPortal<any>;
    private bodyPortalHost: DomPortalHost;

    constructor(private componentFactoryResolver: ComponentFactoryResolver,
                private appRef: ApplicationRef,
                private injector: Injector) {
    }

    showModal(modalName: string) {

        // set up the portal and portal host
        this.modalPortal = new ComponentPortal(ModalComponent);
        this.bodyPortalHost = new DomPortalHost(
            document.body,
            this.componentFactoryResolver,
            this.appRef,
            this.injector);

        // display the component in the page
        let componentRef = this.bodyPortalHost.attach(this.modalPortal);


        // listen for modal's close event
        componentRef.instance.onModalClose().subscribe(() => {
            console.log('I will be very happy if this this gets called, but it never ever does');
            this.closeModal();
        });

        // console.log(componentRef.instance.onModalClose()) shows 1 subscriber.
    }

    closeModal() {
        this.bodyPortalHost.detach();
    }
}

Компонент

export class ModalComponent {

    private modalClose: Subject<any> = new Subject();

    onModalClose(): Observable<any> {
        return this.modalClose.asObservable();
    }

    closeModal() {
        // console.log(this.onModalClose()) **shows zero observers**   :-(
        this.modalClose.next();
        this.modalClose.complete();
    }
}

Кроме того, если я задаю неправильный вопрос, имея в виду, что есть лучший общий подход, я открыт для предложений. :-)

Спасибо!


person BizzyBob    schedule 30.09.2018    source источник
comment
Вы должны добавить this, чтобы указать, что свойство является частью класса. (он же console.log(onModalClose()) должен быть console.log(this.onModalClose())   -  person Edric    schedule 30.09.2018
comment
спасибо за комментарий @Edric, но на самом деле я использовал this, я просто по ошибке пропустил его в своем вопросе. Я обновил код выше.   -  person BizzyBob    schedule 30.09.2018
comment
Пожалуйста, проверьте этот пример stackblitz.com/edit/angular-xwrgpo? file=app/modal.service.ts выглядит правильно   -  person yurzui    schedule 05.10.2018
comment
Ух ты! Я, кажется, работаю там.   -  person BizzyBob    schedule 05.10.2018
comment
точно такой же код, а? Теперь я должен выяснить, почему это не работает локально..!   -  person BizzyBob    schedule 05.10.2018
comment
О, забыл сказать СПАСИБО! :-) @yurzui   -  person BizzyBob    schedule 05.10.2018
comment
Когда я разместил этот вопрос, я включил упрощенный код. Как отметил @yurzui, упрощенный код действительно работает! Я понял, в чем моя проблема. Компонент, который я вводил, на самом деле был специализированным модальным окном (CustomModal), которое включало директиву Modal внутри. Итак, elementRef, который я получал от ModalService, относился к внешнему CustomModal, а не к самому модальному. Я исправил это, просто подписавшись CustomModal на события Modal и повторно отправив их.   -  person BizzyBob    schedule 05.10.2018


Ответы (1)


Этот код работает для меня с очень небольшими изменениями, поэтому я не понимаю, в чем ваша проблема. Сначала я создал проект с помощью Angular CLI, используя ng new. Затем я установил материал ng, следуя инструкциям на их сайте.

Я создал модальный сервис, который идентичен вашему:

    import {ApplicationRef, ComponentFactoryResolver, Injectable, Injector} from '@angular/core';
import {DomPortalHost, ComponentPortal} from "@angular/cdk/portal";
import {ModalComponent} from "./modal/modal.component";

@Injectable({
  providedIn: 'root'
})
export class ModalService {

  private modalPortal: ComponentPortal<any>;
  private bodyPortalHost: DomPortalHost;

  constructor(private componentFactoryResolver: ComponentFactoryResolver,
              private appRef: ApplicationRef,
              private injector: Injector) {
  }

  showModal(modalName: string) {

    // set up the portal and portal host
    this.modalPortal = new ComponentPortal(ModalComponent);
    this.bodyPortalHost = new DomPortalHost(
      document.body,
      this.componentFactoryResolver,
      this.appRef,
      this.injector);

    // display the component in the page
    let componentRef = this.bodyPortalHost.attach(this.modalPortal);


    // listen for modal's close event
    componentRef.instance.onModalClose().subscribe(() => {
      console.log('I will be very happy if this this gets called, but it never ever does');
      this.closeModal();
    });

    // console.log(componentRef.instance.onModalClose()) shows 1 subscriber.
  }

  closeModal() {
    this.bodyPortalHost.detach();
  }
}

Я создал модальный компонент. TypeScript такой же, как у вас:

import { Component, OnInit } from '@angular/core';
import {Observable, Subject} from "rxjs/index";

@Component({
  selector: 'app-modal',
  templateUrl: './modal.component.html',
  styleUrls: ['./modal.component.css']
})
export class ModalComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }

  private modalClose: Subject<any> = new Subject();

  onModalClose(): Observable<any> {
    return this.modalClose.asObservable();
  }

  closeModal() {
    // console.log(this.onModalClose()) **shows zero observers**   :-(
    this.modalClose.next();
    this.modalClose.complete();
  }

}

Вы не дали нам модель для HTML, но я использовал вот это:

<p>
  modal works!
</p>
<button (click)="closeModal()">Close Modal</button>

Вот мой app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';

import { AppComponent } from './app.component';
import {ModalService} from "src/app/modal.service";
import { ModalComponent } from './modal/modal.component';

@NgModule({
  declarations: [
    AppComponent,
    ModalComponent
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule
  ],
  providers: [ModalService],
  bootstrap: [AppComponent],
  entryComponents: [ModalComponent]
})
export class AppModule { }

Примечание. Я определил ModalComponent в entryComponents и ModalService в качестве поставщика.

HTML-код app.component:

<button (click)="showModal()">Show Modal</button>

И машинописный текст app.component:

import { Component } from '@angular/core';
import {ModalService} from "./modal.service";
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app';

  constructor(public modalService: ModalService){}

  showModal(){
       this.modalService.showModal("This is the modal name");
  }

}

Я внедрил ModalService в конструктор и вызвал ваш метод showModal в ответ на нажатие кнопки в основном приложении.

Загрузите приложение: Первоначальная загрузка приложения

Нажмите кнопку, и появится модальное окно. Это не похоже на модальное окно, но это может быть из-за отсутствия стиля:

Приложение с показанным модальным окном

Теперь нажмите кнопку закрытия внутри модального окна:

Модал закрыт

Вы видите, что модальное окно исчезло, а вывод консоли отображает ваше сообщение.

Этот пост поможет вам найти лакомый кусочек, который вы пропустили?


Одна вещь, чтобы добавить. Если вы хотите отправить данные из вашего модального окна в компонент, который его вызвал, просто измените метод closeModal вашего modalComponent:

  closeModal() {
    // console.log(this.onModalClose()) **shows zero observers**   :-(
    this.modalClose.next('Your Data Here, it can be an object if need be');
    this.modalClose.complete();
  }

И ваш модальный сервис может получить доступ к данным в методе onModalClose().subscribe():

componentRef.instance.onModalClose().subscribe((results) => {
  console.log(results);
  console.log('I will be very happy if this this gets called, but it never ever does');
  this.closeModal();
});
person JeffryHouser    schedule 11.10.2018
comment
Привет @JeffryHouser, спасибо за запись. Я предполагаю, что вы не заметили в комментариях к вопросу, @yurzui указал, что мой код уже работает, и я прокомментировал исправление. Это произошло потому, что мой «минимальный пример» для этого поста был проще моего исходного кода, так что он действительно работал. В любом случае, спасибо за работу по составлению кода и скриншотов для будущих посетителей, которые найдут этот вопрос! Награда присуждена :-) - person BizzyBob; 13.10.2018
comment
Кроме того, хорошо, что вы указали на entryComponents, так как это необходимо, я упомяну, что узнал об этом в своих первоначальных попытках, потому что я не настроил его изначально, но у Angular есть потрясающая консоль. ведение журнала, в котором упоминалось, чтобы добавить его, вперед, команда Angular! - person BizzyBob; 13.10.2018
comment
@BizzyBob Я не заметил этого в комментариях, пока не написал это. Хотел бы я прочитать их первым; так как это сэкономило бы мне время. Спасибо за награду. Я рад, что у вас все готово; надеюсь, эта запись поможет кому-то еще в будущем. - person JeffryHouser; 14.10.2018