How to subscribe to Observable of Component injected via @angular/cdk/portal?

馋奶兔 提交于 2019-11-30 17:55:10

问题


I'm trying to implement a basic (very basic) modal implementation. I've got a ModalService and a ModalComponent.

The ModalService creates an instance of the ModalComponent and injects it into the page using the @angular/cdk/portal.

I can get the modal to display just fine :-)

The ModalComponent has an Observable property that I want the ModalService to subscribe to so that when the 'close' button is clicked within the modal, a value can be emitted and the ModalService can close the modal.

However, when the component emits the value, the Service is not acting on it. From the Service side, it looks like I'm subscribed, but from the Component side, it shows 0 observers.

I thought maybe I could use a typical @Output() EventEmitter, but I'm not sure to hook that up since the in the modal class, since the child element doesn't exist initially.

I'm thinking maybe my component reference is not quite right (maybe I have 2 different ones?). I'm trying suggestion from this answer

Any ideas what I'm doing wrong?

Service

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

Component

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

Also, if I happen to be asking the wrong question, meaning there's a better overall approach, I'm open to suggestions. :-)

Thanks!


回答1:


This code works for me with very few changes, so I'm confused as to what your problem is. First I created a project with the Angular CLI using ng new. Then I installed ng material using the instructions on their site.

I created a modal service which is identical to yours:

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

I created a modal component. The TypeScript is the same as yours:

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

}

You didn't give us a model for the HTML, but this is what I used:

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

Here is my 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 { }

Note I Defined the ModalComponent in the entryComponents and the ModalService as a provider.

The app.component HTML:

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

And the app.component typescript:

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

}

I injected the ModalService into the constructor and called your showModal method in response to the button click in the main app.

Load the app:

Click the button and the modal shows up. It doesn't look like a modal, but that could be because of missing styling :

Now click the close button inside the modal:

You see the modal is gone and the console output displays your message.

Does this post help you find the tidbit you missed?


One thing to add. If you want to send data out of your modal to the component that called it, ust change your modalComponent's closeModal method:

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

And your modal service can access the data in the onModalClose().subscribe() method:

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


来源:https://stackoverflow.com/questions/52574188/how-to-subscribe-to-observable-of-component-injected-via-angular-cdk-portal

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!