Data Models and Observables

In this lesson we're going to design a data model for the checklists that we will use in the application, which will also incorporate Observables. A data model is not something that is specific to Ionic, a model in programming is a generic concept. Depending on the context, the exact definition of a model may vary, but in general a model is used to store or represent data.

In the context of Ionic & Angular, if we wanted to keep a reference to some data we might do something like this:

this.myDataArray = ['1', '2', '3'];

However, if we were to create a model it might look something like this:

this.myDataArray = [
  new MyDataModel('1'),
  new MyDataModel('2'),
  new MyDataModel('3')
];

So instead of storing plain data, we are creating an object that holds that data instead. At first it might be hard to see why we would want to do this, for simple data like the example above it just looks a lot more complicated, but it does provide a lot of benefits. The main benefit for us in this application will be that it:

Hopefully this lesson will show you how useful creating a data model can be, but let me preface this by saying this isn't something that is absolutely required. You can quite easily just define some data directly in your class if you like.

We're also going to be creating and making use of our own Observable in this data model, but let's cross that bridge when we get there.

###Creating a Data Model

Usually if we wanted to create a data model we would create a class that defines it (it's basically just a normal object), along with its helper functions, like this:

class PersonModel {
  
  constructor(name, age){
    this.name = name;
    this.age = age;
  }

  increaseAge(){
    this.age++;
  }

  changeName(name){
    this.name = name;
  }

}

Then we could create any number of instances (objects) from it like this:

let person1 = new PersonModel('Jason', 43);
let person2 = new PersonModel('Louise', 22);

and we can call the helper functions on any individual instance (object) like this:

person1.increaseAge();

The idea in Ionic is pretty much exactly the same, except to do it in the Ionic / Angular way we create an Injectable (which we discussed in the basics section). Remember that an Injectable is used to create services that can be injected into any of our other components, so if we want to use the data model we create we can just inject it anywhere that we want to use it.

Let's take a look at what the data model will actually look like, and then walk through the code.

> Modify src/models/checklist-model.ts to reflect the following:

export class ChecklistModel {

  checklist: any;
  checklistObserver: any;

  constructor(public title: string, public items: any[]){

    this.items = items;

  }

  addItem(item): void {

    this.items.push({
      title: item,
      checked: false
    });

  }

  removeItem(item): void {

    let index = this.items.indexOf(item);

    if(index > -1){
      this.items.splice(index, 1);
    }    

  }

  renameItem(item, title): void {

    let index = this.items.indexOf(item);

    if(index > -1){
      this.items[index].title = title;
    }

  }

  setTitle(title): void {
    this.title = title;
  }

  toggleItem(item): void {
    item.checked = !item.checked; 
  }

}

What we're trying to do with this data model is essentially create a blueprint for what an individual checklist is. A checklist has a title and it can have any number of items associated with it that need to be completed. So we set up member variables to hold these values: a simple string for the title, and an array for the items.

Notice that we allow the title and the items to be passed in through the constructor. A title must be supplied to create a new checklist, but providing an array of items is optional. If we want to immediately add items to a checklist we can supply an items array when we instantiate it, otherwise it will just be initialised with an empty array.

We include a bunch of helper functions which are all pretty straight forward, they allow us to either change the title of the checklist, or modify any of the checklists items (by changing their name, removing an item, adding a new item to the checklist, or toggling the completion state of an item).

Also notice that we have added : void after all of the functions. Just like we can declare that a variable has a certain type by doing something like this:

checklist: any;

we can also declare what type of data a function returns. In this case, no data is being returned so we use void. If one of these functions were to return a string, then we would instead use : string on the function.

With all of that set up, we can easily create a new checklist in any component where we have imported the Checklist Model (which we will be doing in the next lesson) by using the following code:

let newChecklist = new ChecklistModel('My Checklist', []);

or

let newChecklist = new ChecklistModel('My Checklist', myItemsArray);

We're going to get a little bit fancier now and incorporate an Observable into our data model so that we can tell when any checklist has been modified (which will allow us to trigger a save to memory later).

###Adding an Observable

You've had a little bit of exposure to Observables already in the basics section of this course - to refresh your memory we can use the Observable the Http service returns like this:

    this.http.get('https://www.reddit.com/r/gifs/new/.json?limit=10').map(res => res.json()).subscribe(data => {
        console.log(data);
    });

We call the get method, and then subscribe to the Observable it returns. Remember that an Observable, unlike a Promise, is a stream of data and can emit multiple values over time, rather than just once. This concept isn't really demonstrated when using the Http service, since in most cases we are just retrieving the data once. The Observable is also already created for us in the case of Http.

We are about to create our very own Observable from scratch in our data model, which will allow other parts of our application to listen for when changes occur to our checklist (because we will emit some data every time a change occurs). When implementing this Observable you will see how to create an observable from scratch, and you'll also see how an Observer can emit more than one value over time.

Before we get to implementing it, let's talk about Observables in a little more detail, in the context of what we're actually trying to do here. In the subscribe method in the code above we are only handling one response:

this.http.get(url).subscribe(data => {
  console.log(data);
});

which is actually the onNext response from the Observable. Observers also provide two other responses, onError and onCompleted, and we could handle all three of those if we wanted to:

this.http.get(url).subscribe(

  (data) => {
    console.log(data);
  },

  (err) => {
    console.log(err);
  },

  () => {
    console.log("completed");
  }
);

In the code above the first event handler handles the onNext response, which basically means "when we detect the next bit of data emitted from the stream, do this". The second handler handles the onError response, which as you might have guessed will be triggered when an error occurs. The final handler handles the onCompleted event, which will trigger once the Observable has returned all of its data.

The most useful handler here is onNext and if we create our own observable, we can trigger that onNext response as many times as we need by calling the next method on the Observable, and providing it some data.

Now that we have the theory out of the way, let's look at how to implement the observable.

> Modify src/models/checklist-model.ts to reflect the following:

import {Observable} from 'rxjs/Observable';

export class ChecklistModel {

  checklist: any;
  checklistObserver: any;

  constructor(public title: string, public items: any[]){

    this.items = items;

    this.checklist = Observable.create(observer => {
      this.checklistObserver = observer;
    });

  }

  addItem(item): void {

    this.items.push({
      title: item,
      checked: false
    });

    this.checklistObserver.next(true);

  }

  removeItem(item): void {

    let index = this.items.indexOf(item);

    if(index > -1){
      this.items.splice(index, 1);
    }    

    this.checklistObserver.next(true);

  }

  renameItem(item, title): void {

    let index = this.items.indexOf(item);

    if(index > -1){
      this.items[index].title = title;
    }

    this.checklistObserver.next(true);

  }

  setTitle(title): void {
    this.title = title;
    this.checklistObserver.next(true);
  }

  toggleItem(item): void {
    item.checked = !item.checked; 
    this.checklistObserver.next(true);
  }

  checklistUpdates(): Observable<any> {
    return this.checklist;
  }

}

The first thing to notice here is that we are now importing Observable from the RxJS library. Then in our constructor, we set up the Observable:

    this.checklist = Observable.create(observer => {
      this.checklistObserver = observer;
    });

Our this.checklist member variable in the code above is now our very own observable. Since it is an observable, we can subscribe to it, and since it is part of our data model, we can subscribe to it on any checklist we have created in our application. We've also added another helper function called checklistUpdates which just returns this observable - this isn't strictly required (we could just access the checklist member variable directly) but it makes its function more clear. Since this function returns an obserable, we give the function a type of Observable<any> which means that it can return any kind of Observable. For example, we could do the following:

let newChecklist = new ChecklistModel('My Checklist', []);

newChecklist.checklistUpdates().subscribe(data => {
  console.log(data);
});

Of course, we aren't doing anything with the Observable yet so it's never going to trigger that onNext response. This is why we have added the following bits of code to each of our helper functions:

this.checklistObserver.next(true);

So whenever we use one of our helper functions to change the title, or add a new item, or anything else, it will notify anything that is subscribed to its Observable. All we want to know is that a change has occurred so we are just passing back a boolean (true or false), but we could also easily pass back some data if we wanted.

The result of this is that now we can "observe" any checklists we create for changes that occur. Later on we will make use of this by listening for these changes and then triggering a save.

Summary

In this lesson we've gone a little bit beyond the beginner level and created a pretty robust data model. As I've mentioned, this certainly has it's benefits but don't feel too intimidated if you had trouble following along with this lesson - as a beginner you can mostly get away with just defining data directly on the class and not worrying about data models and observables.

I particularly don't want to freak you out too much with the Observabes - they are confusing (until you get your head around them) and outside of subscribing to responses from the Http service, you really don't have to use them in most simple applications. But once you do understand them, you can do some powerful stuff with them.

Although this lesson was a little more advanced, it's a great way to demonstrate how you might make use of Observables in your project, and if you've kept up through this lesson then hopefully the next ones should be a breeze!

Copyright © 2016 Joshua Morony | Privacy Policy | Contact

This is a preview of Building Mobile Apps With Ionic 2 LEARN MORE