The Official Ionic Blog

Build amazing native and progressive web apps with HTML5

In part 1 of this series, we started building an end-to-end Ionic 2 app with a Node.js backend. If you’re just joining us now, you’ll probably want to stop and go back to the first post.

Up to this point, we’ve worked primarily in the server.js file where we created an Express app and defined the api endpoints for the todos stored in a MongoDB database, provisioned by mLab via Heroku.

In this next post, we’ll write Angular 2 code using TypeScript to create the frame for our Ionic 2 todo app, communicate with our backend and display the todo that we added to the database.

Sound good? Great, let’s get going.

Display all of the todos

Now that our backend server and database are super-hardcore, we can start building out the frontend todo app. Out of the box, when the Ionic CLI created our project, the app folder looks like this:

app folder

From here, let’s start adding the pieces to display our todos.

The Todo Object

All of the todo data that will be sent from our API will be in a JSON format that we stealthily defined in the last post when we added a todo. If we specify what that data will look like as an object, we’ll be able to take advantage of strong typing (type checking) in TypeScript to ensure each Todo has the correct properties. In order to do this, create a file in your app/ folder called todo.ts and put the following code:

  //todo.ts
  export class Todo {
    _id: number;
    description: string;
    isComplete: boolean;
  }

We’ll use this object in other files shortly.

The TodoService

In our Angular 2 app, we want to abstract HTTP services away from our view logic. In Angular we do this by creating a service, also known as a provider. We can use the Ionic CLI to generate a new provider:

  $/todo-ionic2-heroku > ionic g provider TodoService

The ionic g command just created /app/providers/todo-service/todo-service.ts for us. If you go into this new file, you’ll see it generated a load() { … } function for us. Let’s update this file to get our todos:

  //todo-service.ts
  import {Injectable} from [email protected]/core';
  import {Http, Headers} from [email protected]/http';
  import {Observable} from 'rxjs/Observable';
  import 'rxjs/Rx';
  import {Todo} from '../../todo.ts';

  @Injectable()
  export class TodoService {
    todosUrl = "/api/todos"

    constructor(public http: Http) {}

    // Get all todos
    load(): Observable<Todo[]> {
      return this.http.get(this.todosUrl)
                 .map(res => res.json())
                 .catch(this.handleError);
    }

    handleError(error) {
      console.error(error);
      return Observable.throw(error.json().error || 'Server error');
    }
  }

Our load function uses the HTTP module from Angular 2, made available as a class variable in our TodoService’s constructor. For http.get, we’ll get an Observable back with JSON data. We’ll subscribe to that data in our Component class in the next section.

Note that we updated our import statements to include Observables, which we’ll use instead of built-in ES6 Promises. Just know that you could have just as easily worked with Promises instead. We also included the Todo class to allow for type checking and HTTP headers which we’ll use in our other HTTP methods that we’ll add in the next post.

For now, let’s go ahead and update our app/pages/home/home.ts file to load() the Todo list using our awesome TodoService class.

The HomePage Component

Head into your home.ts file found in /app/pages/home/ and start by importing the TodoService:

  import {TodoService} from '../../providers/todo-service/todo-service';

Then we update our @Component decorator with metadata specifying our TodoService as a provider:

  @Component({
    templateUrl: 'build/pages/home/home.html',
    providers: [TodoService]
  })

Now, we can update our empty constructor, create a todos property, import the TodoService, and assign it to a property of the class.

  public todos: Todo[];

  constructor(public todoService: TodoService) {
    this.loadTodos();
  }

With our TodoService ready to roll, let’s add a loadTodos method to use our TodoService where we’ll subscribe to the Observable&lt;Todo[]&gt; returned by our HTTP request and extract our todos into our class’ todos parameter:

  loadTodos() {
    this.todoService.load()
        .subscribe(data => {
          this.todos = data;
        })
  }

If you don’t subscribe to the Observable returned by the methods in our `TodoService, that “observable is cold. This means the request won’t go out until something subscribes to the Observable” (Angular 2 Docs).

Put this all together and we have the home.ts file below (which also includes an additional import statement to include the Item Sliding component).

  //home.ts
  import {Component} from "@angular/core";
  import {ItemSliding, Item} from 'ionic-angular';
  import {TodoService} from '../../providers/todo-service/todo-service';
  import {Todo} from '../../todo.ts';

  @Component({
    templateUrl: 'build/pages/home/home.html',
    providers: [TodoService]
  })
  export class HomePage {
  public todos: Todo[];

  constructor(public todoService: TodoService) {
    this.loadTodos();
  }

  loadTodos() {
    this.todoService.load()
        .subscribe(data => {
          this.todos = data;
        })
    }
  }

The HomePage Component’s Template

The final step is to update the home.html template to display the todos we stored in our todos array.

To do this let’s add the following markup:

<!-- home.html -->
  <ion-navbar primary *navbar>
    <ion-title>
      MEAN Ionic 2 Todo App
    </ion-title>
  </ion-navbar>
  <ion-content class="home" padding>
    <h2>Add a Todo</h2>
    <ion-item no-lines>
      <ion-input #newTodo (keyup.enter)="return;" type="text" placeholder="Add new todo..." ></ion-input>
      <button clear large item-right (click)="return;">
        <ion-icon name="add"> </ion-icon>
      </button>
    </ion-item>
    <h2>Todo List <small>Swipe a todo to edit or delete</small></h2>
    <ion-list no-lines>
      <ion-item-sliding #slidingItem *ngFor="let todo of todos">
        <ion-item>
          <ion-checkbox (click)="return;" [checked]="todo.isComplete"></ion-checkbox>
          <ion-item text-wrap item-left [class.completed]="todo.isComplete">
            {{ todo.description }}
          </ion-item>
        </ion-item>
        <ion-item-options (ionSwipe)="">
          <button (click)="return;">Edit</button>
          <button danger (click)="return;">
            <ion-icon name="trash"></ion-icon>
          </button>
        </ion-item-options>
      </ion-item-sliding>
    </ion-list>
  </ion-content>

While we’ve added quite a bit into this file, the most important piece to notice is the *ngFor directive we have on the ionic-item-sliding component.

  *ngFor="let todo of todos"

Here we are defining a loop variable with let todo that will receive a Todo from our todos array defined in our Home Component class. Within this newly instantiated template for each Todo, we’ll access and alter the properties of each todo.

Right off the bat, you can see us using todo.description to render {{ }} our Todo’s description, and todo.isComplete on several binding properties [ ] to either add a class or check a checkbox. Finally, you will also notice several spots where we’re handling events such as (click) and (keyup.enter), but instead of making actual function calls, we will just return; for each event. We’ll get those working in the next post.

Styles to Make Things Purdy

Although the utilitarian among us may be shaking their heads, let’s go ahead and add styles for our app to the app/theme/app.core.scss file. Below the import statement, add the following:

  // app.core.scss
  h2 {
    color: color($colors, dark);
    font-size: 1.7rem;
    margin:10px 0 0;
    small {
      color: lighten(color($colors, dark), 20%);
      font-weight: normal;
    }
  }

  ion-list {
    >ion-item-sliding {
      border-bottom: 1px solid color($colors, light);

      .item,
      &:last-child .item {
        border:none;
        margin:0;
      }

      .completed {
          text-decoration: line-through;
          font-style: italic;
      }
    }
  }

  ion-textarea{
    .text-input {
      font-size: 1.6rem;
      color: color($colors, dark);
      margin:0;
      padding:8px;
      border:1px solid color($colors, light);
      border-radius:3px;
    }
  }

You might notice some unused things in there — don’t worry, we’ll be adding more markup to our app in next post.

Serve it Up

If you’d like to see what things look like thus far, head over to your terminal and open up two tabs, one to run the ionic build server and one to run our backend server.

  $/todo-ionic2-heroku > ionic serve
  $/todo-ionic2-heroku > node server.js

Ionic will open up port 8100 for the build server, but if we want to see our Todos, open localhost:8080. Assuming you’re also using a device viewer in your browser of choice, you should hopefully see something like this:

todo app todo app slide

So awesome, right!? I just called my Grandma to tell her we just displayed real data using Ionic 2/Angular 2. She’s so proud!

Concluding Thoughts

Literally nothing can stop you now. You’re becoming a Pokémon Master… I mean an Ionic 2 Wizard! In this post we wrote an HTTP service to collect todos from our MongoDB database, set up our view logic to utilize that service, and designed the front end of our simple app. We also learned a bit about Observables and did some type-checking with TypeScript.

In the next and final post, we’ll flesh out the details to allow us to fully interact with our Ionic 2 app. Specifically, we’ll flesh out the TodoService to POST (add), PUT (update) and DELETE (..um, well, delete) Todos. Then we’ll link this up to the app’s view. We’ll also add another page to our app to isolate and edit todos.

  • http://writingonlife.com writingonlife

    Hello,

    I don’t not have an app folder in my project. Is that something I need to create, or is it supposed to be there already?

    Thanks!

    • Graciele E. Victor

      did you follow the first post completely? when you create an Ionic app you get this automatically..

  • Graciele E. Victor

    Hi Justin,

    there is a minor typo in the code:

    // todo-service.ts
    import {Injectable} from ‘@angular/core’;
    import {Http, Headers} from ‘@angular/http’;
    import {Observable} from ‘rxjs/Observable’;
    Import ‘rxjs/Rx’;
    import {Todo} from ‘../../todo.ts’;

    *** Import ‘rxjs/Rx’ is written with capital I… Import ***

  • Graciele E. Victor

    Hi Justin,

    can’t run the code.. getting Cannot read property ‘visitStatement’ of undefined error.

    I think it’s due to the empty clicks.. because when I comment them it runs..

    any idea on why that happens?

    • http://goscicki.eu Marcin Gościcki

      Same here :/

    • mixersoft
    • jplwood

      Yea, Apologies, the tutorial was originally intended to be done in a single go, if you go to Part 3, you’ll put function calls for each event in there and get rid of that error. I will see if we can make an edit to have empty `return`s for the events for the end of part two of this series.

      • Evert S.

        thanks for the update, but you forgot the (ionSwipe) eventbindings,
        it worked after I also changed those to (ionSwipe)=”return;”