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.
Add **IonicModule**
in ComponentsModule
without forRoot()
imports: [IonicModule]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With