2

I have a modalComponent that I create dynamically.

<div class="modal">
  <div class="modal-body">
    Test
  </div>

  <div class="modal-footer">
    <button (click)="callbackFunction()">success</button>
    <button>abort</button>
 </div>
</div>

This component has an Input callbackFunction that'a function that I want to invoke from my parent component.

import {
  Component,
  Input,
  OnInit,
  QueryList,
  ViewChildren
} from "@angular/core";
import { ModalService } from "../modal.service";

@Component({
  selector: "app-modal",
  templateUrl: "./modal.component.html",
  styleUrls: ["./modal.component.css"]
})
export class ModalComponent implements OnInit {
  @Input() callbackFunction: () => void;

  constructor(private modalService: ModalService) {}

  ngOnInit() {}
}

After that I created a service:

import {
  ApplicationRef,
  ComponentFactoryResolver,
  ComponentRef,
  Injectable,
  Injector
} from "@angular/core";

import { ModalComponent } from "./modal/modal.component";

@Injectable()
export class ModalService {
  dialogComponentRef: ComponentRef<ModalComponent>;

  open(callbackFunction: any) {
   const modalComponentFactory = this.cfResolver.resolveComponentFactory(ModalComponent);

   const modalComponent = modalComponentFactory.create(this.injector);

   modalComponent.instance.callbackFunction = callbackFunction;

   this.dialogComponentRef = modalComponent;

   document.body.appendChild(modalComponent.location.nativeElement);

   this.appRef.attachView(modalComponent.hostView);
  }

  close() {
    this.appRef.detachView(this.dialogComponentRef.hostView);
  }

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

After componentFactoryResolver I pass my function as instance.

In my parent controller I create a function

sayHello(
 this.myService.doSomething();
}

and after that I create a function for opening a modal

open(this.sayHello());

When I click on the button and I invoke callback function, "this" is not referred to Parent component but to Modal Component and sayHello is undefined. How can I fix this situation?

I don't want to use emit.

This is my stackblitz: Example

2
  • 1
    "I don't want to use emit." But that would easily and smoothly solve the whole situation. This is a perfect example why EventEmitters exist. Commented Jan 14, 2021 at 14:09
  • Does this answer your question? How to access the correct `this` inside a callback? Commented Jan 14, 2021 at 14:13

2 Answers 2

8

Basically there are three solutions for this: Output + EventEmitter, @ViewChild and Subject

ViewChild solution

  • This one can be used when the button is defined on the Parent and you want to get something from the Child.
///////parent.component.ts
...
import { ChildComponent } from 'child/child.component';
...
export class ParentComponent {
  @ViewChild(ChildComponent) childComponent: ChildComponent;

  public buttonClick(): void {
    let childResponse = this.childComponent.getValues();//will return '1234'
    ...
  }
}
///////child.component.ts
export class ChildComponent {
  valueInsideChild = '1234';

  public getValues(): string {
    return this.valueInsideChild;
  }
}

Output + EventEmitter solution

//////parent.component.html
<child-selector
  ($buttonClicked)=clickAction($event)>
</child-selector>
//////parent.component.ts
...
export class ParentComponent {
  public clickAction(value: string): void {
    console.log(value);//will log 'something1234 when child button is clicked
  }
}
//////child.component.ts
...
import { Output, Component, EventEmitter } from '@angular/core';
...
export class ChildComponent {
  @Output() $buttonClicked = new EventEmitter<string>();

  public click(): void {
    this.$buttonClicked.emit('something1234');
  }
}
//////child.component.html
<button (click)="click()">

Subject

  • Interface responses using your modalService+subject+observables
///app.component.ts
...
export class AppComponent {
...
  open() {
    //subscribe to the observable :)
    this.modalService.open(this.sayHello).subscribe(response => {
      alert(response.text);
    });
  }
...
}
///modal.component.html
...
  <button (click)="click()">success</button>
...
///modal.component.ts
...
export class ModalComponent {
  constructor(private modalService: ModalService) {}
  ...
  public click(): void {
    this.modalService.close({text: 'Hello World'});
  }
}
///modal.service.ts
...
import { Subject, Observable } from 'rxjs';
...
export class ModalService {
...
  private _modalResponse = new Subject<any>();
...
  open(): Observable<any> {//this is your open function
    ...
    return this._modalResponse.asObservable();//return an observable where the modal responses will be emitted
  }
  
  close(response: any): void {
    //receives a value from the modal component when closing
    this.appRef.detachView(this.dialogComponenRef.hostView);
    this._modalResponse.next(response);//emit the response on the Observable return when open was called
  }
}
Sign up to request clarification or add additional context in comments.

Comments

1

I suggest you to use an Output and a EventEmitter to call the parent component function from the child component, Angular documentation provides a good example on how to do it.

https://angular.io/guide/inputs-outputs#sending-data-to-a-parent-component

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.