Tutorial

Creating reusable components

Just as the coronavirus outbreak taught us to isolate ourselves for our own benefit as well as for the benefit of everyone around us, isolating parts of your code can give you a lot of benefits:

  • smaller code

  • bugs only in one place

  • no code duplication

  • better code structure

Creating a component and (re)using is very simple.

In this tutorial, we will create a component that will mock publishing the current page to social media.

Creating the app

Let’s create a new Ionic Capacitor app and add a folder named components inside the app folder.

> ionic start components blank

Once you are done with adding the folder your project structure will look like this

...
 src
   app
     components
...

Let’s now create a component using by using the Ionic CLI

Generating the component

> ionic generate component components/social-share

This will create all the needed files (.html, .scss, and .ts) and we are ready to add functionality to our component!

// social-share.component.html

<ion-button (click)="onShareClick()">
  <ion-icon 
    slot="icon-only" 
    name="share-social-outline">
  </ion-icon>
</ion-button>

// social-share.component.ts

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

@Component({
  selector: 'app-social-share',
  templateUrl: './social-share.component.html',
  styleUrls: ['./social-share.component.scss'],
})
export class SocialShareComponent {
  @Input() currentPage: string;

  constructor() { }

  onShareClick() {
    alert(`Sharing ${this.currentPage} to the world!`);
  }
}

Now that we have a basic component that uses the @Input() currentPage to know from where it has been called there are just two more things to be done.

Creating a components module

In order to use the component, it has to be declared inside one (and only one) module. If you plan to use a component just on a single page it makes sense to declare it inside that page’s module. However, we expect this component to be used across the app so we will create a components module from which then other parts of our application can use this specific component.

// components.module.ts

import from '@angular/core';
import from '@ionic/angular';

import from './social-share/social-share.component';

const components = [
  SocialShareComponent
];

@NgModule({
  imports: [
    IonicModule
  ],
  declarations: components,
  exports: components
})
export class ComponentsModule { }

Using the component

Let’s open our home.page.html file and add the component.

// home.page.html

<ion-header [translucent]="true">
    <ion-toolbar>
        <ion-title>
            Components
        </ion-title>
    </ion-toolbar>
</ion-header>

<ion-content>
    <app-social-share currentPage="Home"></app-social-share>
</ion-content>

The element app-social-share is the one we have defined as the selector for our component in the social-share.component.ts file. Keep in mind that you can name the selector anything you want but you should stay away from keywords like button, div, etc. Now we only need to run the app and try it out.

> ionic serve

If you followed everything you should now see a blank screen. By opening up the developer console you will see the following message

ERROR Error: Uncaught (in promise): Error: Template parse errors:
'app-social-share' is not a known element:
1. If 'app-social-share' is an Angular component, then verify that it
is part of this module.
2. If 'app-social-share' is a Web Component then add
'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this 
component to suppress this message. ("

<ion-content>
    [ERROR ->]<app-social-share></app-social-share>
</ion-content>
"): ng:///HomePageModule/HomePage.html@9:1
Error: Template parse errors:
'app-social-share' is not a known element:
1. If 'app-social-share' is an Angular component, then verify that 
it is part of this module.
2. If 'app-social-share' is a Web Component then add
'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this 
component to suppress this message. ("

<ion-content>
    [ERROR ->]<app-social-share></app-social-share>
</ion-content>
"): ng:///HomePageModule/HomePage.html@9:1

We get this error message because we didn’t include the ComponentModule into our HomeModule and as a result the HomeComponent has no way of knowing how to render our app-social-share element.

Let’s fix this quickly by adding it.

@NgModule({
  imports: [
    ...
    ComponentsModule
  ],
  ...
})
export class HomePageModule {}

Rerunning the app now will display the button and clicking on the it will execute the click event handler with a simple share function.

final.gif

To end this blog post on a high note (and learn a little more) while still staying on the topic of self isolation and quarantines I thought it might be fun to learn about the origin of quarantines. Instead of me writting another post about it I’ll just drop a line from the CDC’s website.

The practice of quarantine, as we know it, began during the 14th century in an effort to protect coastal cities from plague epidemics. Ships arriving in Venice from infected ports were required to sit at anchor for 40 days before landing. This practice, called quarantine, was derived from the Italian words quaranta giorni which mean 40 days.
— Centers for Disease Control and Prevention

As always, you can check out the full code here.

Until next time,
stay healthy.

Simple multi level menu in Ionic/Angular

While working on a project, a new requirement emerged where the client wanted to have a multi level menu displayed. After some digging around I didn’t find anything that was simple enough. Pretty much all of them were using some kind of id + level tags on menu items which then helped them know if they should expand something or not.

I like to keep things as simple as possible so I tried to take advantage of Angular’s component system and build something a little easier to understand.

Hello world (kind of)

Let’s first create an Ionic app with a predefined side menu and move from there. You just need to run this command in your terminal and Ionic will do the rest.

> ionic start multilevel-menu sidemenu

Now you can start your app with the following command

> ionic serve

This will start your (default) browser and you should see something like this:

What we start with

What we start with

Looks nice, but nothing too exciting.

1. Housekeeping

Before we start, let’s remove a bunch of things before we start. Let’s remove everything referencing list. Remove the entire list folder as well as all of it’s traces from the app.components.ts and app-routing.module.ts files. If you rerun ionic serve now, you will see just a single home menu item.

2. Creating a model

What better thing to have as a model for our multi level menu than a restaurant menu. Unfortunately, restaurant menus can get quite complicated but it will serve our needs perfectly.

We will create a very simple model with just three properties: name, id, and children. Create a new folder inside the app folder and name it models. Add another file, named menu-item.ts into it.

//src/app/models/menu-item.ts

export class MenuItem {
  name: string;
  id: number;
  children: Array<MenuItem>;
}

3. Creating some data

Based on this model, let’s creating some data for our app to use. We will create another folder named data inside the assets folder and add a new menu-items.ts file into it with the following content

//src/assets/data/menu-items.ts

export const menuItems = [
  {
    name: 'Appetizers',
    id: 1,
    children: [
      {
        name: 'Fresco Salsa',
        id: 6,
        children: null
      }
    ]
  },
  {
    name: 'Main dishes',
    id: 2,
    children: [
      {
        name: 'Beef',
        id: 7,
        children: [
          {
            name: 'Crispy Orange Beef',
            id: 10,
            children: null
          }
        ]
      },
      {
        name: 'Burger',
        id: 8,
        children: [
          {
            name: 'Chorizo Burger',
            id: 11,
            children: null
          }
        ]
      },
      {
        name: 'Vegetarian',
        id: 9,
        children: [
          {
            name: 'Chile Rellenos',
            id: 12,
            children: null
          }
        ]
      }
    ]
  },
  {
    name: 'Side dishes',
    id: 3,
    children: [
      {
        name: 'Baked potato',
        id: 13,
        children: null
      }
    ]
  },
  {
    name: 'Salads',
    id: 4,
    children: [
      {
        name: 'Taco Slaw',
        id: 14,
        children: null
      }
    ]
  },
  {
    name: 'Desserts',
    id: 5,
    children: [
      {
        name: 'Crepes',
        id: 15,
        children: null
      }
    ]
  }
];

It’s a mix of everything, so I hope there is something for you in this menu as well!

4. Final work before the good stuff

The last thing we have to do is go back to our app.component.ts and replace appPages with our new menuItems

//src/app/app.component.ts

import { menuItems } from '../assets/data/menu-items';

// ...

export class AppComponent {
  private menu = menuItems;
  // ...
}

The last thing we have to do is to remove the old markup used to render the menu and replace it with something simpler (for now). Let’s go into the app.component.html file and do that right now.

<!--src/app/app.component.html-->

<ion-menu-toggle auto-hide="false" *ngFor="let menuItem of menu">
  <ion-item>
    {{menuItem.name}}
  </ion-item>
</ion-menu-toggle>

Saving everything you should see your new menu in all it’s glory.

The first level of menu items

The first level of menu items

It’s pretty, isn’t it?

5. Brainstorming

Let’s think about how we are going to proceed here. From the model we can see that we have a menu item that can hold a bunch of menu items and so on.

What we will do then is to create a component which will pass its children to another component of the same type.

If the current component doesn’t have any children it’s a dish and we will display a simple button for the user to tap on, otherwise it’s just a category and we will display it differently.

6. Creating the component

If you are like me you don’t know all the bits and pieces a component needs right away. Lucky for us Ionic and Angular have us covered. Just switch to your terminal and type in the following command

> ionic g c menu-item

This will tell the Ionic (and underlying Angular) CLI to g(enerate) a new c(omponent) named menu-item.

We, also, need to let Ionic know about our new component so we will add it into the declarations array of the app.module.ts file.

//src/app/app.module.ts

import { MenuItemComponent } from './menu-item/menu-item.component';

// ...

@NgModule({
  declarations: [AppComponent, MenuItemComponent],
  
  /// ...
}

Let’s change the markup in app.component.html file to use the new component:

<!--src/app/app.component.html-->

 <ion-menu-toggle auto-hide="false" *ngFor="let menuItem of menu">
   <!-- <ion-item>
     {{menuItem.name}}
   </ion-item> -->
   <app-menu-item></app-menu-item>
 </ion-menu-toggle>

Saving it all now will result in this:

Looks like my CS degree is paying off

Looks like my CS degree is paying off

There is one last step until we can call it a day…

7. Finishing the component

As we discussed above, if the current menu item has children, we want it to display its name and pass its children down to another component of the same type. If there are no children, display a button which the user can click.

The final component will look something like this:

<!--src/app/menu-item/menu-item.component.html-->

<div>
  <p *ngIf="menuItem.children; else finalItem">
    <ion-button
      [color]="isRoot ? 'primary' : 'secondary'"
      [expand]="isRoot ? 'full' : 'block'"
      (click)="isOpen = !isOpen"
    >
      {{ menuItem.name }}
    </ion-button>

    <span *ngIf="isOpen">
      <app-menu-item
        *ngFor="let item of menuItem.children"
        [menuItem]="item"
      ></app-menu-item>
    </span>
  </p>
</div>

<ng-template #finalItem>
  <p>
    <ion-button color="light" expand="full" 
                (click)="onMenuItemSelected(menuItem)">
      {{ menuItem.name }}
    </ion-button>
  </p>
</ng-template>

The trick here is simply to toggle each component’s isOpen property and toggle between showing and hiding the children. Also, if you aren’t familiar with the if - else syntax from here, check out one of my previous posts.

Let’s just clean up the app.component.html a little and we are done

<!--src/app/app.component.html-->

<app-menu-item
  *ngFor="let menuItem of menu"
  [menuItem]="menuItem"
  [isRoot]="true">
</app-menu-item>

The final product will look like this:

Final multile level menu

Final multile level menu


If you had trouble following along, or just want to see the code fully. You can check it out here.

Until next time,
happy coding.