Our website is made possible by displaying online advertisements to our visitors. Please consider supporting us by disabling your ad blocker.

Build An RSS Reader Mobile App With Ionic 2 And Angular

TwitterFacebookRedditLinkedInHacker News

If you’re a long time follower of my blog you’ll remember I wrote an article about creating an RSS reader using Ionic Framework. That tutorial not only used the first version of Ionic Framework, but it now also uses a deprecated Google service, once known as the Google Feed API. Because of the deprecated API, it no longer works.

I still get a lot of people asking me about the creation of RSS readers, so I figured it was time to come up with a new solution. With Ionic 2 being all the rage, it makes sense to explore feeds with this version of the framework.

Here we’ll be building an RSS feed reader for Android and iOS using Ionic 2 and Angular with TypeScript.

To get an idea of what we’ll be creating, we’ll be creating a simple multi-page Ionic 2 application that will load stories from an RSS feed and list them within the application. These stories will be able to be navigated to for the full content.

Ionic 2 RSS Feed Reader

To make this tutorial possible, there will be data parsing involved since RSS feeds are commonly XML format, which isn’t exactly usable out of the box in an Ionic 2 application. All of this will be explored as the tutorial progresses.

The Requirements

There are a few requirements that must be met to make this project possible. They should be configured before continuing through the tutorial:

  • Node.js 4.0+
  • The Ionic 2 CLI
  • The Android SDK or Xcode

We need Node.js because it ships with the Node Package Manager (NPM) which is necessary for installing various packages, including the Ionic 2 CLI. This project, being Ionic 2, requires version 2 of the CLI. Using the standard Ionic Framework CLI will not allow you to build Ionic 2 applications. Finally, if you wish to build Android applications you’ll need the Android SDK, and if you wish to build iOS applications you’ll need Xcode.

Creating a New Android and iOS Ionic 2 Application

To make this tutorial easy to follow, we’re going to start with a fresh Ionic 2 project for Android and iOS. From the Command Prompt (Windows) or Terminal (Linux and Mac), execute the following:

ionic start rss-project blank --v2
cd rss-project
ionic platform add ios
ionic platform add android

A few things to note in the above commands. Notice that we’re using the --v2 tag. This means we’ll be creating an Ionic 2 application that uses Angular and TypeScript. As mentioned earlier in the requirements, you’ll need Xcode installed if you wish to build for the iOS platform. Xcode is a Mac OS application.

Adding the Necessary Project Plugins and Dependencies

There are a few plugins that must be installed to make this project a success. For example, this project will store data in a SQLite database and use the devices default web browser to view full content. Both of these use native platform features which require plugins.

Starting with the Apache Cordova SQLite plugin, execute the following from the Terminal or Command Prompt:

ionic plugin add cordova-sqlite-storage

The above command will install the SQLite plugin for both Android and iOS.

Next we want to install the Apache Cordova InAppBrowser plugin for viewing links in the default browser. From the Command Prompt or Terminal, execute the following:

ionic plugin add cordova-plugin-inappbrowser

The above command will install the InAppBrowser plugin for both iOS and Android.

Understanding the SQLite Database Schema

We will be storing data in a database for this particular guide. Specifically we will be storing the URLs to all the XML feeds that we wish to display within our application.

To make this possible, we’ll be using a SQLite database with the following schema:

idlink
integertext

The above schema can be created using the following SQL query, which will be further shown at a different point in this tutorial:

CREATE TABLE IF NOT EXISTS sources (id INTEGER PRIMARY KEY AUTOINCREMENT, link TEXT)

We won’t need to worry about any database relationships because the complexity of our application doesn’t call for that. The only thing being stored are the source links. All feed data will be populated on the fly for freshness.

Creating the SQLite Database Provider

To prevent SQL queries from showing up all over our application, we’re going to create a shared provider that will handle all interaction with the database.

To create the shared database provider, execute the following from your Command Prompt or Terminal:

ionic g provider database

The above command will generate an src/providers/database directory with a database.ts file inside of it. By default, it has a lot of code that isn’t particularly useful to us. Instead, open the file and include the following code:

import { Injectable } from '@angular/core';
import { SQLite } from "ionic-native";

@Injectable()
export class Database {

    private storage: SQLite;
    private isOpen: boolean;

    public constructor() {
        if(!this.isOpen) {
            this.storage = new SQLite();
            this.storage.openDatabase({name: "data.db", location: "default"}).then(() => {
                this.storage.executeSql("CREATE TABLE IF NOT EXISTS sources (id INTEGER PRIMARY KEY AUTOINCREMENT, link TEXT)", []);
                this.isOpen = true;
            });
        }
    }

    public createSource(link: string) {
        return new Promise((resolve, reject) => {
            return this.storage.executeSql("INSERT INTO sources (link) VALUES (?)", [link]).then((data) => {
                resolve(data);
            }, (error) => {
                reject(error);
            });
        });
    }

    public getSources() {
        return new Promise((resolve, reject) => {
            this.storage.executeSql("SELECT * FROM sources", []).then((data) => {
                let sources = [];
                if(data.rows.length > 0) {
                    for(var i = 0; i < data.rows.length; i++) {
                        sources.push({id: data.rows.item(i).id, link: data.rows.item(i).link});
                    }
                }
                resolve(sources);
            }, (error) => {
                reject(error);
            });
        });
    }

    public deleteSource(source: any) {
        return this.storage.executeSql("DELETE FROM sources WHERE id = ?", [source.id]);
    }

}

So what is happening in the above code?

The first thing we’re doing is importing a few classes that we need. We need SQLite for saving our data with Ionic Native.

Inside our Database class we have a constructor method that will initialize our storage solution and create a new database table. Our database table will only be created if it does not exist, otherwise it will be ignored.

There are three methods that we will use for database interaction. Starting with the createSource method we have:

public createSource(link: string) {
    return new Promise((resolve, reject) => {
        return this.storage.executeSql("INSERT INTO sources (link) VALUES (?)", [link]).then((data) => {
            resolve(data);
        }, (error) => {
            reject(error);
        });
    });
}

In the above method we insert a new source link. When successful, the source id will be returned.

Next we have the getSources method which will get all the sources that exist in the database. It is seen below:

public getSources() {
    return new Promise((resolve, reject) => {
        this.storage.executeSql("SELECT * FROM sources", []).then((data) => {
            let sources = [];
            if(data.rows.length > 0) {
                for(var i = 0; i < data.rows.length; i++) {
                    sources.push({id: data.rows.item(i).id, link: data.rows.item(i).link});
                }
            }
            resolve(sources);
        }, (error) => {
            reject(error);
        });
    });
}

The neat thing about the above method is that we are not returning the result rows. Instead we are parsing the rows into an array of objects, something easier to work with throughout the pages of our application.

Finally we have the deleteSource method as seen below:

public deleteSource(source: any) {
    return this.storage.executeSql("DELETE FROM sources WHERE id = ?", [source.id]);
}

In the above method we are deleting a particular source by its id column. We don’t really care what is returned by this function so we’ll just return the default JavaScript promise.

More information on SQLite in an Ionic 2 mobile application can be read about here.

At this point our database can be used throughout the application.

Bootstrapping the Application Wide Providers

Before we can use the database provider, or any provider, in our application, it must be added in the @NgModule block of our project. To do this, open the project’s src/app/app.module.ts file and include the following source code:

import { NgModule, ErrorHandler } from '@angular/core';
import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { Database } from '../providers/database';

@NgModule({
  declarations: [
    MyApp,
    HomePage
  ],
  imports: [
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage
  ],
  providers: [{provide: ErrorHandler, useClass: IonicErrorHandler}, Database]
})
export class AppModule {}

A few things to notice in the above code.

First off, we are including the database provider that we created previously. After importing the Database component we need to inject it in the providers array of the @NgModule block.

At this point the provider can be used in every page of our application.

Generate the Page for Displaying the News Feed

Now we can work on our first application page. This page will be responsible for reading the RSS sources from our database and displaying their feeds in a list.

Before we can start programming, we need to create the application page. Using the Ionic generators, execute the following from the Terminal or Command Prompt:

ionic g page feed

The above command will generate an src/pages/feed directory with a TypeScript, HTML, and SCSS file included in it. We’re going to start with the TypeScript logic file and work our way into the HTML UI file.

Open the project’s src/pages/feed/feed.ts file and include the following code:

import { Component } from '@angular/core';
import { Http, Request, RequestMethod } from "@angular/http";
import { NavController } from 'ionic-angular';
import { InAppBrowser } from 'ionic-native';
import { SourcesPage } from "../sources/sources";
import { Database } from "../../providers/database";
import 'rxjs/Rx';

@Component({
    templateUrl: 'feed.html',
})
export class FeedPage {

    public feedList: Array<Object>;

    public constructor(private navCtrl: NavController, private http: Http, private database: Database) {
        this.feedList = [];
    }

    public ionViewDidEnter() {
        this.feedList = [];
        this.database.getSources().then((results: any) => {
            for(let i = 0; i < results.length; i++) {
                this.load(results[i].link);
            }
        }, (error) => {
            console.log("ERROR: ", error);
        });
    }

    private load(url: string) {
        this.http.request(new Request({
            method: RequestMethod.Get,
            url: "https://query.yahooapis.com/v1/public/yql?q=select%20title%2C%20description%2C%20link%20from%20rss%20where%20url%3D%22" + url + "%22&format=json&diagnostics=true&callback="
        }))
        .map(result => result.json())
        .subscribe((result) => {
            let items = result.query.results.item;
            for(let i = 0; i < items.length; i++) {
                items[i].description = this.cleanText(items[i].description);
                items[i].description = items[i].description.substring(0, items[i].description.indexOf("...") + 3);
            }
            this.feedList = this.feedList.concat(items);
        }, (error) => {
            console.log(error);
        });
    }

    private cleanText(text: string) {
        let cleaned = text;
        cleaned = cleaned.replace(/(<([^>]+)>)/ig,"");
        cleaned = cleaned.replace(/&#8217;/gi, "\'");
        cleaned = cleaned.replace(/&#039;/gi, "\'");
        cleaned = cleaned.replace(/\[&#8230;\]/gi, "...");
        return cleaned;
    }

    public open(item: any) {
        let browser = new InAppBrowser(item.link, "_blank");
        browser.show();
    }

    public add() {
        this.navCtrl.push(SourcesPage);
    }

}

There is a lot going on in the above code, so we should probably break it down.

In the imports section we are importing Http for making requests against the source URLs and InAppBrowser for displaying the original content using Ionic Native. We are importing the yet to be created SourcesPage for adding new sources and then the previously created Database provider.

This brings us to the FeedPage class. In this class we have a public array variable which will contain feed data and be bound to the UI. This variable is initialized in the constructor method:

public constructor(private navCtrl: NavController, private http: Http, private database: Database) {
    this.feedList = [];
}

In the constructor method we are also injecting the database provider and the provider used for making web requests.

When the page finishes loading, we want to load the data from the database. This can be done through Ionic’s ionViewDidEnter method:

public ionViewDidEnter() {
    this.feedList = [];
    this.database.getSources().then((results: any) => {
        for(let i = 0; i < results.length; i++) {
            this.load(results[i].link);
        }
    }, (error) => {
        console.log("ERROR: ", error);
    });
}

For every source retrieved from the database, we do an HTTP request to get the appropriate feed data. The feed requests happen in our load method:

private load(url: string) {
    this.http.request(new Request({
        method: RequestMethod.Get,
        url: "https://query.yahooapis.com/v1/public/yql?q=select%20title%2C%20description%2C%20link%20from%20rss%20where%20url%3D%22" + url + "%22&format=json&diagnostics=true&callback="
    }))
    .map(result => result.json())
    .subscribe((result) => {
        let items = result.query.results.item;
        for(let i = 0; i < items.length; i++) {
            items[i].description = this.cleanText(items[i].description);
            items[i].description = items[i].description.substring(0, items[i].description.indexOf("...") + 3);
        }
        this.feedList = this.feedList.concat(items);
    }, (error) => {
        console.log(error);
    });
}

The load method is probably our most important method. Let me explain. RSS feeds are in XML format which is something JavaScript cannot natively process. Instead we need to convert the XML feed data into JSON, something JavaScript can work with. In my previous tutorial I used the Google Feed API for this, but the API no longer exists. This time around, I took Raymond Camden’s advice to use the Yahoo Query Language (YQL) to parse XML into JSON.

A YQL query for an RSS feed essentially looks like the following:

select title, description, link from rss where url="https://www.engadget.com/rss.xml"

The above query will take the RSS feed from Engadget and extract only the title, description, and link XML tags and create JSON out of them.

We cannot just run YQL in an Ionic Framework application. It is an API service that we use in the load method. The result of the request gets converted into a JavaScript object which we then loop through to clean up. We need to do cleanup because there may be a bunch of HTML tags and unicode characters in the description. Cleanup happens in the cleanText function. When cleanup is complete, the feed items are added to the feed list.

In terms of the cleanText function, it is seen below:

private cleanText(text: string) {
    let cleaned = text;
    cleaned = cleaned.replace(/(<([^>]+)>)/ig,"");
    cleaned = cleaned.replace(/&#8217;/gi, "\'");
    cleaned = cleaned.replace(/&#039;/gi, "\'");
    cleaned = cleaned.replace(/\[&#8230;\]/gi, "...");
    return cleaned;
}

In the above function I’ve only chosen to clean up a few things. Feel free to add more or less, it is up to you.

When any item is clicked in the feed list on the UI, the open method is called:

public open(item: any) {
    let browser = new InAppBrowser(item.link, "_blank");
    browser.show();
}

The URL of the list item is obtained and the InAppBrowser plugin is used to open it.

Finally we have the add method for navigating to the next page.

Before we focus on the next page, we should probably design the UI that gets paired with the previous TypeScript code. Open the project’s src/pages/feed/feed.html file and include the following markup:

<ion-header>
    <ion-navbar>
        <ion-title>Ionic Feed Reader</ion-title>
        <ion-buttons end>
            <button (click)="add()"><ion-icon name="add"></ion-icon></button>
        </ion-buttons>
    </ion-navbar>
</ion-header>

<ion-content padding class="feed">
    <ion-list>
        <ion-item text-wrap *ngFor="let item of feedList" (click)="open(item)">
            <h2>{{item.title}}</h2>
            <p>{{item.description}}</p>
            <ion-icon name="ios-arrow-forward" item-right></ion-icon>
        </ion-item>
    </ion-list>
</ion-content>

The UI has a navigation bar with a single button. When this button is pressed, navigation will occur to the page for adding new sources. The core page content will be the list view.

The list is populated by looping through the public feed array from the TypeScript file. Each row in the list will show the feed item title, the description, and an arrow to show that you should probably click it. If a list item is clicked, the link will be loaded in the default browser.

This brings us to the next application page.

Generate the Page for Adding News Feed Sources

We need to be able to add and remove feed sources in the application. Let’s create a page for this using the generators offered by the Ionic 2 CLI. Using the Command Prompt or Terminal, execute the following:

ionic g page sources

The above command will create an src/pages/sources directory with a TypeScript, SCSS, and HTML file in it. We’re going to focus on the TypeScript file again first, then work our way into the UI.

Open the project’s src/pages/sources/sources.ts file and include the following code:

import { Component } from '@angular/core';
import { NavController, AlertController } from 'ionic-angular';
import { Database } from '../../providers/database';

@Component({
    templateUrl: 'sources.html',
})
export class SourcesPage {

    public sourceList: Array<Object>;

    public constructor(private navCtrl: NavController, private alertCtrl: AlertController, private database: Database) {
        this.sourceList = [];
    }

    public ionViewDidEnter() {
        this.database.getSources().then((results) => {
            this.sourceList = <Array<Object>> results;
        }, (error) => {
            console.log("ERROR: ", error);
        });
    }

    public delete(index: any) {
        this.database.deleteSource(this.sourceList[index]).then((result) => {
            this.sourceList.splice(index, 1);
        }, (error) => {
            console.log("ERROR: ", error);
        });
    }

    public add() {
        let prompt = this.alertCtrl.create({
            title: "Feed Source",
            message: "Enter the full URL to the RSS XML feed",
            inputs: [
                {
                    name: "link",
                    placeholder: "http://"
                },
            ],
            buttons: [
                {
                    text: "Cancel"
                },
                {
                    text: "Save",
                    handler: data => {
                        if(data.link != "") {
                            this.database.createSource(data.link).then((result) => {
                                this.sourceList.push({id: result, link: data.link});
                            }, (error) => {
                                console.log("ERROR: ", error);
                            });
                        }
                    }
                }
            ]
        });
        prompt.present();
    }

}

Again, the above code is a quite a lot, so we’re going to break it down.

Just like with the other TypeScript files, we’re importing the necessary classes, providers, etc. What really matters is found in the SourcesPage class.

In the SourcesPage class we have a public array that will be bound to the UI. This public array is initialized in the constructor method along with various injections.

Since data should not be loaded in the constructor method, we do it all in the ionViewDidEnter method:

public ionViewDidEnter() {
    this.database.getSources().then((results) => {
        this.sourceList = <Array<Object>> results;
    }, (error) => {
        console.log("ERROR: ", error);
    });
}

All sources in the database will be loaded into our public array.

Should we ever want to remove a source from the UI and database we can make use of the delete method:

public delete(index: any) {
    this.database.deleteSource(this.sourceList[index]).then((result) => {
        this.sourceList.splice(index, 1);
    }, (error) => {
        console.log("ERROR: ", error);
    });
}

This delete method will be triggered via a swipe event in the UI, but in the process the particular item index is passed. This will get us what is necessary to make the removal.

Finally we have the add method which is a bit different. To add new data to the database, we’ll do so through an alert prompt like so:

public add() {
    let prompt = this.alertCtrl.create({
        title: "Feed Source",
        message: "Enter the full URL to the RSS XML feed",
        inputs: [
            {
                name: "link",
                placeholder: "http://"
            },
        ],
        buttons: [
            {
                text: "Cancel"
            },
            {
                text: "Save",
                handler: data => {
                    if(data.link != "") {
                        this.database.createSource(data.link).then((result) => {
                            this.sourceList.push({id: result, link: data.link});
                        }, (error) => {
                            console.log("ERROR: ", error);
                        });
                    }
                }
            }
        ]
    });
    prompt.present();
}

When the save button is pressed on the prompt, we check to make sure a URL exists. We are not doing URL validation. You should probably do this in a production application, but for simplicity, we won’t worry about it now. Just make sure you enter proper links. If a link is entered, we will save it, add it to the public array, and then close the alert.

With the page logic out of the way, we can look at the HTML markup that powers the UI. Open the project’s src/pages/sources/sources.html file and include the following markup:

<ion-header>
    <ion-navbar>
        <ion-title>Feed Sources</ion-title>
        <ion-buttons end>
            <button (click)="add()"><ion-icon name="add"></ion-icon></button>
        </ion-buttons>
    </ion-navbar>
</ion-header>

<ion-content padding class="sources">
    <ion-list>
        <ion-item-sliding text-wrap *ngFor="let item of sourceList; let i = index">
            <ion-item>
                {{item.link}}
            </ion-item>
            <ion-item-options>
                <button danger (click)="delete(i)">
                    <ion-icon name="trash"></ion-icon>
                    Delete
                </button>
            </ion-item-options>
        </ion-item-sliding>
    </ion-list>
</ion-content>

Like with the previous page, this too has a navigation bar with a single button. When the button is pressed, the prompt will appear.

We also have a list in our content, like the previous page. However, this time the list has swipeable rows. When swiped, a delete button will appear and if the button is pressed, the delete method of the TypeScript file will be called.

Bringing the Pages Together

The pages are created, but they need to be linked so navigation can happen. Open the project’s src/app/app.module.ts file and include the following:

import { NgModule, ErrorHandler } from '@angular/core';
import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
import { MyApp } from './app.component';
import { FeedPage } from '../pages/feed/feed';
import { SourcesPage } from '../pages/sources/sources';
import { Database } from '../providers/database';

@NgModule({
  declarations: [
    MyApp,
    FeedPage,
    SourcesPage
  ],
  imports: [
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    FeedPage,
    SourcesPage
  ],
  providers: [{provide: ErrorHandler, useClass: IonicErrorHandler}, Database]
})
export class AppModule {}

Notice that we’ve imported both the FeedPage and SourcesPage and injected them into the declarations and entryCmoponents array of the @NgModule block. We’ve also removed the now obsolete HomePage component.

The the pages brought together, we need to define our default page. Open the project’s src/app/app.component.ts file and swap out HomePage with the FeedPage component.

Our pages are now complete!

Testing the RSS Feed Reader Project

After all that hard work, it is time to test the project on a device or in a simulator. If you’ve been keeping up, the project should work without any issues. To run on iOS, execute the following from the Terminal or Command Prompt:

ionic run ios

If you’re not using a Mac or you’d prefer to test the application in Android, execute the following instead:

ionic run android

If you’re interested in testing the project, but haven’t been following along, you can download the full source code to this project here. Before executing one of the run commands, you must download all the project dependencies. This can be done by executing the following:

npm install
ionic state restore

The above command will install the NPM dependencies, the project platforms, and the project plugins. At this point you should be able to run the project.

Conclusion

You just saw how to create an Android and iOS application that can read and present RSS XML feeds. This project was created using Ionic 2 and Angular and it makes use of a few native device features.

If you’d like to download the full project source code, it can be obtained here. Please do not share this source code as I put a lot of time into it as well as this blog article.

Nic Raboy

Nic Raboy

Nic Raboy is an advocate of modern web and mobile development technologies. He has experience in C#, JavaScript, Golang and a variety of frameworks such as Angular, NativeScript, and Unity. Nic writes about his development experiences related to making web and mobile development easier to understand.