Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ionic3 - Template parse error in custom component

I have been working on a project to get up to speed on all the changes to Ionic3 recently. Its mostly based off Ionic-Team's demo conference-app.

I'm still trying to lean angular/ionic, I had most of the app running in ionic2. The shared components.module lazy loading messed me up for a while. But now I've run into a problem using Ionic's builtin components within my custom components. I'm trying to build a custom component that uses ion-card to display information passed into it through a @Input.

The error it gives:

Error: Template parse errors:
'ion-card-header' is not a known element:
1. If 'ion-card-header' is an Angular component, then verify that it is part of this module.
2. If 'ion-card-header' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message. ("
<ion-card>

[ERROR ->]<ion-card-header>
    <h3>{{ recipe.title }}</h3>
</ion-card-header>
"): ng:///ComponentsModule/RecipeCardComponent.html@3:2
'ion-card-content' is not a known element:
1. If 'ion-card-content' is an Angular component, then verify that it is part of this module.
2. If 'ion-card-content' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message. ("
</ion-card-header>

[ERROR ->]<ion-card-content>
    <p>{{ recipe.description }}</p>
</ion-card-content>
"): ng:///ComponentsModule/RecipeCardComponent.html@7:2
'ion-card' is not a known element:
1. If 'ion-card' is an Angular component, then verify that it is part of this module.
2. If 'ion-card' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message. ("
[ERROR ->]<ion-card>

<ion-card-header>
"): ng:///ComponentsModule/RecipeCardComponent.html@1:0

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';
import { IonicStorageModule } from '@ionic/storage';
import { HttpModule } from '@angular/http';

import { UserDataProvider } from '../providers/user-data/user-data';
import { RecipeDataProvider } from '../providers/recipe-data/recipe-data';

import { ComponentsModule } from '../components/components.module';
import { PipesModule } from '../pipes/pipes.module';

import { MyApp } from './app.component';
import { IntroPage } from '../pages/intro/intro';
import { RecipeListPage } from '../pages/recipe-list/recipe-list';
import { FavoratesPage } from '../pages/favorates/favorates';
import { CookbooksPage } from '../pages/cookbooks/cookbooks';
import { QuickTimerPage } from '../pages/quick-timer/quick-timer';
import { ShoppingListPage } from '../pages/shopping-list/shopping-list';
import { SettingsPage } from '../pages/settings/settings';
import { AboutPage } from '../pages/about/about';
import { DatabaseProvider } from '../providers/database/database';
import { UtilitiesProvider } from '../providers/utilities/utilities';


@NgModule({
declarations: [
    MyApp,
    IntroPage,
    RecipeListPage,
    FavoratesPage,
    CookbooksPage,
    QuickTimerPage,
    ShoppingListPage,
    SettingsPage,
    AboutPage
],
imports: [
    BrowserModule,
    HttpModule,
    IonicModule.forRoot(MyApp, {}, {
    links: [
        { component: IntroPage, name: 'IntroPage', segment: 'intro' },
        { component: RecipeListPage, name: 'RecipeListPage', segment: 'recipe-list' },
        { component: FavoratesPage, name: 'FavoratesPage', segment: 'favorates' },
        { component: CookbooksPage, name: 'CookbooksPage', segment: 'cookbooks' },
        { component: QuickTimerPage, name: 'QuickTimerPage', segment: 'quick-timer' },
        { component: ShoppingListPage, name: 'ShoppingListPage', segment: 'shopping-list' },
        { component: SettingsPage, name: 'SettingsPage', segment: 'settings' },
        { component: AboutPage, name: 'AboutPage', segment: 'settings' },
    ]
    }),
    IonicStorageModule.forRoot({
    name: '__mise',
    driverOrder: ['indexeddb', 'websql', 'localstorage']
    }),
    ComponentsModule,
    PipesModule
],
bootstrap: [IonicApp],
entryComponents: [
    MyApp,
    IntroPage,
    RecipeListPage,
    FavoratesPage,
    CookbooksPage,
    QuickTimerPage,
    ShoppingListPage,
    SettingsPage,
    AboutPage
],
providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler},
    UserDataProvider,
    RecipeDataProvider,
    DatabaseProvider,
    UtilitiesProvider
]
})
export class AppModule {}

app.component.ts

import { Component, ViewChild } from '@angular/core';
import { Nav, Platform } from 'ionic-angular';
import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';

import { Storage } from '@ionic/storage';

import { UserDataProvider } from '../providers/user-data/user-data';
import { RecipeDataProvider } from '../providers/recipe-data/recipe-data';

import { IntroPage } from '../pages/intro/intro';
import { RecipeListPage } from '../pages/recipe-list/recipe-list';


export interface PageInterface {
title: string;
name: string;
component: any;
icon: string;
logsOut?: boolean;
index?: number;
tabName?: string;
tabComponent?: any;
}

@Component({
templateUrl: 'app.html'
})
export class MyApp {
@ViewChild(Nav) nav: Nav;

pages: Array<{title: string, component: any}>;

// List of pages that can be navigated to from the left menu
// the left menu only works after login
// the login page disables the left menu
appPages: PageInterface[] = [
    { title: 'intro', name: 'IntroPage', component: 'IntroPage', icon: 'help' },
    { title: 'Recipes', name: 'RecipeListPage', component: 'RecipeListPage', icon: 'list' },
    { title: 'Favorates', name: 'FavoratesPage', component: 'FavoratesPage', icon: 'star' },
    { title: 'Cookbooks', name: 'CookbooksPage', component: 'CookbooksPage', icon: 'folder' },
    { title: 'Quick timer', name: 'QuickTimerPage', component: 'QuickTimerPage', icon: 'help' },
    { title: 'Shopping list', name: 'ShoppingListPage', component: 'ShoppingListPage', icon: 'help' },
    { title: 'Settings', name: 'SettingsPage', component: 'SettingsPage', icon: 'help' },
    { title: 'About', name: 'AboutPage', component: 'AboutPage', icon: 'help' },
];

rootPage: any;

constructor(
    public platform: Platform, 
    public statusBar: StatusBar,
    public storage: Storage, 
    public splashScreen: SplashScreen,
    public userData: UserDataProvider,
    public recipeData: RecipeDataProvider
) {

    // Check if the user has already seen the tutorial
    this.storage.get('hasSeenTutorial')
    .then((hasSeenTutorial) => {
        if (hasSeenTutorial) {
        console.log('HasSeenTurorial = true');
        this.rootPage = RecipeListPage;
        } else {
        console.log('HasSeenTurorial = false');
        this.rootPage = IntroPage;
        }
        this.platformReady()
    });

    // load the recipe data
    this.recipeData.init();

}

openPage(page: PageInterface) {
    this.nav.setRoot(page.name);
}

openTutorial() {
    this.nav.setRoot(IntroPage);
}

listenToLoginEvents() {
    // this.events.subscribe('user:login', () => {
    //   this.enableMenu(true);
    // });

    // this.events.subscribe('user:signup', () => {
    //   this.enableMenu(true);
    // });

    // this.events.subscribe('user:logout', () => {
    //   this.enableMenu(false);
    // });
}

enableMenu(loggedIn: boolean) {
    // this.menu.enable(loggedIn, 'loggedInMenu');
    // this.menu.enable(!loggedIn, 'loggedOutMenu');
}

platformReady() {
    // Call any initial plugins when ready
    this.platform.ready().then(() => {
    this.splashScreen.hide();
    });
}

isActive(page: PageInterface) {
    let childNav = this.nav.getActiveChildNavs()[0];

    // Tabs are a special case because they have their own navigation
    if (childNav) {
    if (childNav.getSelected() && childNav.getSelected().root === page.tabComponent) {
        return 'primary';
    }
    return;
    }

    if (this.nav.getActive() && this.nav.getActive().name === page.name) {
    return 'primary';
    }
    return;
}
}

recipe-list.ts

import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';

import { RecipeDataProvider } from '../../providers/recipe-data/recipe-data';
import { UserDataProvider } from '../../providers/user-data/user-data';

import { ComponentsModule } from '../../components/components.module';


@IonicPage()
@Component({
    selector: 'page-recipe-list',
    templateUrl: 'recipe-list.html',
})
export class RecipeListPage {

    recipeList: any = [];
    things: any = ["One", "Two", "Three"];

    constructor(
        public navCtrl: NavController, 
        public navParams: NavParams,
        public recipeData: RecipeDataProvider,
        public userData: UserDataProvider
    ) {}

    goToRecipe(recipeId: any){

    };

    printRecipeList(){
        console.log(this.recipeList);
    }


    ionViewDidLoad() {
        console.log('ionViewDidLoad RecipeListPage');
        this.recipeList = this.recipeData.recipeList;
    }

}

recipeList.html

<ion-header>
    <ion-navbar>
        <button ion-button menuToggle>
        <ion-icon name="menu"></ion-icon>
        </button>
        <ion-title>Recipe List</ion-title>
    </ion-navbar>
</ion-header>


<ion-content padding>
    <recipe-card *ngFor="let recipe of recipeList; let i = index" [recipe]="recipeList[i]"></recipe-card>
</ion-content>

components.module.ts

import { NgModule } from '@angular/core';
import { RecipeCardComponent } from './recipe-card/recipe-card';
import { TimelineComponent } from './timeline/timeline';
import { TimerComponent } from './timer/timer';
@NgModule({
    declarations: [RecipeCardComponent,
    TimelineComponent,
    TimerComponent],
    imports: [],
    exports: [RecipeCardComponent,
    TimelineComponent,
    TimerComponent]
})
export class ComponentsModule {}

recipe-card.ts

import { Component, Input } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
// Providers
import { UserDataProvider } from "../../providers/user-data/user-data";
import { RecipeDataProvider } from "../../providers/recipe-data/recipe-data";
// Model
import { Recipe } from "../../models/recipe-model";
// Pages
import { RecipeDetailPage } from "../../pages/recipe-detail/recipe-detail";


@Component({
    selector: 'recipe-card',
    templateUrl: 'recipe-card.html'
})
export class RecipeCardComponent {

    @Input() recipe: Recipe;

    constructor(
        public navCtrl: NavController,
        public navParams: NavParams,
        private userData: UserDataProvider,
        private recipeData: RecipeDataProvider
    ) {}

    delete(){
        console.log('dalete recipe');
        // this.recipeData.delete(this.recipe.recipeId);
    }

    goToRecipeDetailPage() {
        console.log('goToRecipe() recipe');
        this.navCtrl.push('RecipeDetailPage', this.recipe);
    }

}

recipe-card.html

<ion-card>

    <ion-card-header>
        <h3>{{ recipe.title }}</h3>
    </ion-card-header>

    <ion-card-content>
        <p>{{ recipe.description }}</p>
    </ion-card-content>

</ion-card>

for completeness here is my ionic sys info:

cli packages: (...)

    @ionic/cli-utils  : 1.12.0
    ionic (Ionic CLI) : 3.12.0

global packages:

    cordova (Cordova CLI) : 7.0.1

local packages:

    @ionic/app-scripts : 3.0.0
    Cordova Platforms  : none
    Ionic Framework    : ionic-angular 3.7.1

System:

    Node : v6.10.2
    npm  : 3.10.7
    OS   : Windows 10

Misc:

    backend : pro

If i use the following code for recipe-card.html it works fine.

<h3>{{ recipe.title }}</h3>
<p>{{ recipe.description }}</p>

But if I include the following code, it's the ion-icon that triggers the Template parse error.

<h3>{{ recipe.title }}</h3>

<p>{{ recipe.description }}</p>

<button ion-button icon-left>
    <ion-icon name="home"></ion-icon>
    Left Icon
</button>

So it would seem that accessing nested ionic builtin components within your own custom component is what is going wrong. It might be something glaringly obvious to do with lazy loading or the shared components.module, but for the life of me i can't see it. Any help would be great :) Thanks in advance.

like image 735
aidansmyth Avatar asked Oct 06 '17 18:10

aidansmyth


1 Answers

Add **IonicModule** in ComponentsModule without forRoot()

imports: [IonicModule]
like image 182
prasanthnv Avatar answered Oct 20 '22 17:10

prasanthnv