3

I have a page, SearchPage, nested within my main page at the project's root, HomePage, and within SearchComponent are two child components: PromptComponent and ResultsComponent. Here's the tree output:

src/app/home/
├── home.module.ts
├── home.page.html
├── home.page.scss
├── home.page.spec.ts
├── home.page.ts
├── home-routing.module.ts
└── search
    ├── prompt
    │   ├── prompt.component.html
    │   ├── prompt.component.scss
    │   ├── prompt.component.spec.ts
    │   └── prompt.component.ts
    ├── results
    │   ├── results.component.html
    │   ├── results.component.scss
    │   ├── results.component.spec.ts
    │   └── results.component.ts
    ├── search.module.ts
    ├── search.page.html
    ├── search.page.scss
    ├── search.page.spec.ts
    ├── search.page.ts
    └── search-routing.module.ts

If I create another page with routing (or component, doesn't matter) within home, enter that route in the address bar, and then navigate to /search, I see the right content and it works fine without errors. However, if I refresh the page from there, or enter /search in the address bar, I get two console errors:

NG0303: Can't bind to 'ngIf' since it isn't a known property of 'app-prompt'. core.js:10105
NG0303: Can't bind to 'ngIf' since it isn't a known property of 'app-results'. core.js:10105

This is referring to a couple of tags within search.page.html:

<app-prompt
  (queryEmitter)="update($event)"
  *ngIf="query === ''"
></app-prompt>
<app-results
  [query]="query"
  (queryEmitter)="update($event)"
  *ngIf="query !== ''"
></app-results>

When I see errors, these components don't get rendered.

When they do work, here's how they work. First, an empty prompt is displayed in <app-prompt>. Later, when the user types something in the prompt, the value of query gets passed from an event emitter to SearchPage, and passed again to ResultsComponent.

The rest of the code is below. How do I get this to work in every case?

search.page.ts

import { Location } from '@angular/common';
import { Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute, ParamMap } from '@angular/router';

/**
 * Main component for the search page and parent to `PromptComponent` and
 * `ResultsComponent`.
 *
 * @decorator `@Component({ selector: 'app-search', templateUrl: './search.page.html', styleUrls: ['./search.page.scss'], })`
 *
 * @alpha
 */
@Component({
  selector: 'app-search',
  templateUrl: './search.page.html',
  styleUrls: ['./search.page.scss'],
})
export class SearchPage implements OnInit {
  /**
   * Holds the value of `ion-searchbar` within each of the child components,
   * and allows for communication across components.
   *
   * @alpha
   */
  query!: string;

  /**
   * Constructor for the search page.
   *
   * @param _route `ActivatedRoute`
   * @param _location `Location`
   *
   * @alpha
   */
  constructor(private _route: ActivatedRoute, private _location: Location) {}

  /**
   * A lifecycle hook that is called after Angular has initialized all
   * data-bound properties of a directive. Takes query parameter from URL and
   * stores it locally.
   *
   * @alpha
   */
  ngOnInit(): void {
    this._route.paramMap.subscribe((paramMap: ParamMap) => {
      const query: null | string = paramMap.get('query');
      this.query = query ? query : '';
    });
  }

  /**
   * Method to update the `query` property and the URL.
   *
   * @param query String from search bar to be used in child components.
   *
   * @alpha
   */
  update(query: string): void {
    this.query = query;
    this._location.replaceState(`search${query ? '/' + query : ''}`);
  }
}

prompt.component.ts

import {
  AfterViewChecked,
  Component,
  EventEmitter,
  Output,
  ViewChild,
} from '@angular/core';
import { SearchbarChangeEventDetail } from '@ionic/core/dist/types/components/searchbar/searchbar-interface.d';

/**
 * Prompt component. Child component of Search page responsible for prompting
 * the user and indirectly passing data to its sibling component, Results, with
 * the help of Word page.
 *
 * @selector `@Component({ selector: 'app-prompt', templateUrl: './prompt.component.html', styleUrls: ['./prompt.component.scss'], })`
 *
 * @alpha
 */
@Component({
  selector: 'app-prompt',
  templateUrl: './prompt.component.html',
  styleUrls: ['./prompt.component.scss'],
})
export class PromptComponent implements AfterViewChecked {
  /**
   * Emitter object for passing query (text) value to the parent component for
   * its use and use by ResultsComponent.
   *
   * @alpha
   */
  @Output() queryEmitter = new EventEmitter<string>();

  /**
   * Reference to `ion-searchbar` element to be set to focus.
   *
   * @alpha
   */
  @ViewChild('searchbar') searchbar: HTMLIonInputElement;

  query:string;

  /**
   * Constructor that takes no arguments and does nothing.
   *
   * @alpha
   */
  constructor() {}

  /**
   * An Angular lifecycle hook that is called after the default change detector
   * has completed checking a component's view for changes. This method simply
   * calls setFocus() on the searchbar element.
   *
   * @alpha
   */
  ngAfterViewChecked(): void {
    this.searchbar.setFocus();
  }

  /**
   * Takes `ion-searchbar` value from template and emits it to the parent
   * component.
   *
   * @param $event The `ionChange` event object passed in via template.
   *
   * @alpha
   */
  onChange($event: CustomEvent<SearchbarChangeEventDetail>): void {
    this.queryEmitter.emit($event.detail.value);
  }
}

results.component.ts

import {
  AfterViewChecked,
  Component,
  EventEmitter,
  Input,
  Output,
  ViewChild,
} from '@angular/core';
import { IonSearchbar } from '@ionic/angular';
import { SearchbarChangeEventDetail } from '@ionic/core/dist/types/components/searchbar/searchbar-interface.d';

/**
 * Results component. Child component of Search page responsible for taking in
 * user input and displaying search results. Currently, though, all it does is
 * take in input and switch to its sibling component, Prompt, when the input is
 * cleared.
 *
 * @selector `@Component({ selector: 'app-results', templateUrl: './results.component.html', styleUrls: ['./results.component.scss'], })`
 *
 * @alpha
 */
@Component({
  selector: 'app-results',
  templateUrl: './results.component.html',
  styleUrls: ['./results.component.scss'],
})
export class ResultsComponent implements AfterViewChecked {
  /**
   * Query text passed in from parent component, which receives it either from
   * the `:query` URL parameter or from `PromptComponent`.
   *
   * @alpha
   */
  @Input() query: string;

  /**
   * Emitter object for passing query (text) value to the parent component for
   * its use.
   *
   * @alpha
   */
  @Output() queryEmitter = new EventEmitter<string>();

  /**
   * Reference to `ion-searchbar` element to be set to focus.
   *
   * @alpha
   */
  @ViewChild('searchbar') searchbar: IonSearchbar;

  /**
   * Constructor that takes no arguments and does nothing apart from returning
   * the component instance.
   *
   * @alpha
   */
  constructor() {}

  /**
   * Ionic lifecycle hook that gets called when the component is about to enter
   * view. Here, the search results are updated.
   *
   * @alpha
   */
  ionViewWillEnter() {
    this.search();
  }

  /**
   * Angular lifecycle hook called after calls to ngAfterContentChecked() have
   * finished. This hook simply calls setFocus() on the searchbar element.
   *
   * @alpha
   */
  ngAfterViewChecked(): void {
    this.searchbar.setFocus();
  }

  /**
   * Takes `ion-searchbar` value from template and stores it locally. Then it
   * checks for an non-empty string, and if that passes, the search results are
   * updated. Finally, it emits the query value to the parent component, empty
   * or not.
   *
   * @param $event The `ionChange` event object passed in via template.
   *
   * @alpha
   */
  onChange($event: CustomEvent<SearchbarChangeEventDetail>): void {
    const query = $event.detail.value;
    this.query = query;

    if (query) {
      this.search();
    }

    this.queryEmitter.emit(query);
  }

  /**
   * Emits a blank query value to the parent component so focus is returned to
   * PromptComponent.
   *
   * @alpha
   */
  clear(): void {
    this.queryEmitter.emit('');
  }

  /**
   * Skeleton code for search functionality. Currently, it is used to log the
   * local value of `query` for debugging purposes.
   *
   * @alpha
   */
  private search(): void {
    console.log(this.query);
  }
}

Edit

app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { CommonModule } from '@angular/common';

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [
    BrowserModule,
    IonicModule.forRoot(),
    AppRoutingModule,
    CommonModule,
  ],
  providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }],
  bootstrap: [AppComponent],
})
export class AppModule {}

home.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';

import { IonicModule } from '@ionic/angular';

import { HomePageRoutingModule } from './home-routing.module';

import { HomePage } from './home.page';

@NgModule({
  imports: [
    CommonModule,
    FormsModule,
    IonicModule,
    HomePageRoutingModule
  ],
  declarations: [HomePage]
})
export class HomePageModule {}

search.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';

import { IonicModule } from '@ionic/angular';

import { SearchPageRoutingModule } from './search-routing.module';

import { SearchPage } from './search.page';
import { PromptComponent } from './prompt/prompt.component';
import { ResultsComponent } from './results/results.component';

@NgModule({
  imports: [
    CommonModule,
    FormsModule,
    IonicModule,
    SearchPageRoutingModule,
  ],
  declarations: [SearchPage, PromptComponent, ResultsComponent]
})
export class SearchPageModule {}

Other questions are solved by adding CommonModule, to *.module.ts, but as you can see above, that's already included.

Matt McCarthy
  • 424
  • 6
  • 19
  • 1
    Does this answer your question? [Can't bind to 'ngIf' since it isn't a known property of 'div'](https://stackoverflow.com/questions/39058075/cant-bind-to-ngif-since-it-isnt-a-known-property-of-div) – vicnoob Jul 14 '21 at 04:05
  • 2
    I think this question is duplicated, the problem maybe because you didn't import `Common Module` to your Module – vicnoob Jul 14 '21 at 04:06

1 Answers1

2

PLease go to the correct module.ts file of your project and check if you have imported the CommonModule .

Otherwise, import it as

import { CommonModule } from '@angular/common';  

Then, add it to the import array.

 @NgModule({
    imports: [CommonModule],    // Import here
  ...
})
class YourModule {}
Udith Indrakantha
  • 860
  • 11
  • 17