0

I know that this question already exists here: Ionic open Popover from another component but I don't get the solution.

I have a page which includes a component for the toolbar which has an button. This button should trigger a function within the page. When I trigger the function from inside the page it works. I can also see that onInit the popoverController is filled, but later when the function is executed it's undefined.

ERROR TypeError: Cannot read property 'create' of undefined
at HeaderComponent.push.6176.LocationSelectionPage.openAddLocation (location-selection.page.ts:92)
at HeaderComponent_ion_buttons_7_Template_ion_buttons_click_0_listener (template.html:14)
at executeListenerWithErrorHandling (core.js:15285)
at wrapListenerIn_markDirtyAndPreventDefault (core.js:15323)
at HTMLElement.<anonymous> (platform-browser.js:560)
at ZoneDelegate.invokeTask (zone.js:406)
at Object.onInvokeTask (core.js:28664)
at ZoneDelegate.invokeTask (zone.js:405)
at Zone.runTask (zone.js:178)
at ZoneTask.invokeTask [as invoke] (zone.js:487)

The function looks like this:

openAddLocation() {
this.popover
  .create({
    component: LocationAddPage,
    cssClass: '',
    showBackdrop: true,
  })
  .then((popoverElement) => {
    popoverElement.present();
  });

}

I have another popover which works fine but it is triggered in the same page.

header.component.ts

import { Component, Input, OnInit } from '@angular/core';
import { Router } from '@angular/router';

@Component({
  selector: 'app-header',
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.scss'],
})
export class HeaderComponent implements OnInit {
  @Input() title: string;
  @Input() openAddLocation: Function;
  showBackBtn: boolean = false;
  showMenuBtn: boolean = true;
  path: string;

  constructor(private router: Router) {}

  ngOnInit() {
    this.path = this.router.url;
    if (/labsite-selection|retailers|location-selection/.test(this.path)) {
      this.showBackBtn = true;
      this.showMenuBtn = false;
    } else {
      this.showBackBtn = false;
      this.showMenuBtn = true;
    }
  }
}

components.module.ts

import { NgModule } from '@angular/core';
import { IonicModule } from '@ionic/angular';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { HeaderComponent } from './header/header.component';
import { SearchComponent } from './search/search.component';

@NgModule({
  imports: [IonicModule, CommonModule, FormsModule],
  declarations: [HeaderComponent, SearchComponent],
  exports: [HeaderComponent, SearchComponent],
  providers: [],
})
export class ComponentsModule {}

header.component.html

<ion-header mode="md">
  <ion-toolbar color="primary">
    <ion-title>{{ title }}</ion-title>
    <ion-buttons slot="start">
      <ion-menu-button *ngIf="showMenuBtn" autoHide="false"></ion-menu-button>
      <ion-back-button
        *ngIf="showBackBtn"
        defaultHref="start"
      ></ion-back-button>
    </ion-buttons>
    <ion-buttons
      *ngIf="path.includes('location-selection')"
      slot="end"
      (click)="openAddLocation()"
    >
      <ion-button>
        <ion-icon slot="icon-only" name="add-outline"></ion-icon>
      </ion-button>
    </ion-buttons>
  </ion-toolbar>
</ion-header>
4
  • use Observables Commented Jul 16, 2021 at 11:56
  • You should register your popover for each component where do you use it. Commented Jul 17, 2021 at 0:27
  • @Danil I registered the popover in the page module and the component module (where all components are registered) and also in the popover page itself ofc Commented Jul 17, 2021 at 12:53
  • @kdc Can you add the code of the component (with imports and declarations) which produces the error? Commented Jul 17, 2021 at 13:00

1 Answer 1

1

First of all, solution. You use your app-header component in this way:

<app-header [openAddLocation]="openAddLocation"></app-header>

and you have in your page this code:

openAddLocation() {
    this.popover.create({
        component: LocationAddPage,
        cssClass: '',
        showBackdrop: true,
    }).then((popoverElement) => {
        popoverElement.present();
    });
}

You should change it to this:

openAddLocation = () => {
    this.popover.create({
        component: LocationAddPage,
        cssClass: '',
        showBackdrop: true,
    }).then((popoverElement) => {
        popoverElement.present();
    });
}

Explanation:

In the first case, you pass a Function. This method does not pass this context to the component. When the component tries to call @Input function It calls with this of the component but not this of the page.

In the second case, you pass a closure. And here will uses this of the page in both calls.

Another solution

I think the first solution is easy to implement for you but I don't think that this is a good approach. I think It will be better to use @Output params instead of @Input to call the function.

In your header.component.ts:

- import { Component, Input, OnInit } from '@angular/core';
+ import { Component, EventEmitter, Input, Output, OnInit } from '@angular/core';

- @Input() openAddLocation: Function;
+ @Output() openAddLocation: EventEmitter<void> = new EventEmitter();

header.component.html:

- (click)="openAddLocation()"
+ (click)="openAddLocation.emit()"

Then you can use it on your page in this way:

<app-header (openAddLocation)="openAddLocation()"></app-header>

More info about @Input and @Output params in documentation: https://angular.io/guide/inputs-outputs

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you for the help. Very much appreciated.

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.