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

Andy88
  • 647
  • 2
  • 9
  • 21
  • 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. – JSON Derulo Jan 14 '21 at 14:09
  • Does this answer your question? [How to access the correct \`this\` inside a callback?](https://stackoverflow.com/questions/20279484/how-to-access-the-correct-this-inside-a-callback) – JSON Derulo Jan 14 '21 at 14:13

2 Answers2

5

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
  }
}
Aracy
  • 134
  • 3
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

Sean Gonzalez
  • 163
  • 16