Parsing RSS data is not a simple task given that most feeds are based on XML. Within this post we will build a simple RSS reader with Ionic 2 using the Yahoo API to transform our feeds into more readable JSON.
The Ionic 2 RC0 is out so we can work on a pretty solid foundation and API by now. Changes might still occur, but they hopefully won’t break the code. I give credits to Raymond Camden where I first saw the Yahoo API in action, so follow him, he’s an awesome guy who likes Star Wars and cats.
Let’s dig into the fun and start a blank new app!
Starting a new Ionic 2 App
As always, we use the Ionic CLI to start a blank new Ionic 2 app by passing the –v2 flag. Additional we generate a page and a provider for our app which we can easily use later for our reader. We also install the Ionic storage package using NPM which allows us to store key/value pairs, and finally a cordova plugin to open a browser inside the app. Now go ahead and run from your command line:
ionic start devdactic-rss blank --v2 ionic g page feedList ionic g provider feedService npm install @ionic/storage --save --save-exact ionic plugin add cordova-plugin-inappbrowser
To use our Pages and providers we must properly inject them inside the src/app/app.module.ts so replace everything with:
import { NgModule } from '@angular/core'; import { IonicApp, IonicModule } from 'ionic-angular'; import { MyApp } from './app.component'; import { HomePage } from '../pages/home/home'; import { FeedListPage } from '../pages/feed-list/feed-list'; import { FeedService } from '../providers/feed-service'; import { Storage } from '@ionic/storage'; @NgModule({ declarations: [ MyApp, HomePage, FeedListPage ], imports: [ IonicModule.forRoot(MyApp) ], bootstrap: [IonicApp], entryComponents: [ MyApp, HomePage, FeedListPage ], providers: [FeedService, Storage] }) export class AppModule {}
These are all the resources we need for now, let’s start with the heart of our app first.
If you want the complete template for download, make sure to get it below!
Get the Ionic 2 RSS Template
Enter your Email below to receive the template directly to your inbox!
Crafting the RSS Service
The RSS service will take care of delivering all the information our app needs. All the logic for storing and loading feeds plus actually grabbing feed data takes place in this class.
We define 2 classes, FeedItem to hold one article of a feed and Feed to represent an RSS feed from a website. Using TypeScript it’s really a good approach to wrap your information in objects like these!
Open the previously generated provider at src/providers/feed-service.ts and replace everything with:
import { Injectable } from '@angular/core'; import { Http } from '@angular/http'; import 'rxjs/add/operator/map'; import { Storage } from '@ionic/storage'; import { Observable } from 'rxjs/Observable'; export class FeedItem { description: string; link: string; title: string; constructor(description: string, link: string, title: string) { this.description = description; this.link = link; this.title = title; } } export class Feed { title: string; url: string; constructor(title: string, url: string) { this.title = title; this.url = url; } } @Injectable() export class FeedService { constructor(private http: Http, public storage: Storage) {} public getSavedFeeds() { return this.storage.get('savedFeeds').then(data => { let objFromString = JSON.parse(data); if (data !== null && data !== undefined) { return JSON.parse(data); } else { return []; } }); } public addFeed(newFeed: Feed) { return this.getSavedFeeds().then(arrayOfFeeds => { arrayOfFeeds.push(newFeed) let jsonString = JSON.stringify(arrayOfFeeds); return this.storage.set('savedFeeds', jsonString); }); } public getArticlesForUrl(feedUrl: string) { var url = 'https://query.yahooapis.com/v1/public/yql?q=select%20title%2Clink%2Cdescription%20from%20rss%20where%20url%3D%22'+encodeURIComponent(feedUrl)+'%22&format=json'; let articles = []; return this.http.get(url) .map(data => data.json()['query']['results']) .map((res) => { if (res == null) { return articles; } let objects = res['item']; var length = 20; for (let i = 0; i < objects.length; i++) { let item = objects[i]; var trimmedDescription = item.description.length > length ? item.description.substring(0, 80) + "..." : item.description; let newFeedItem = new FeedItem(trimmedDescription, item.link, item.title); articles.push(newFeedItem); } return articles }) } }
The getSavedFeeds
and addFeed
work on our storage object and gather or save a new added Feed. By doing this we can keep the once added Feeds inside our list like in every good Feed reader app!
The getArticlesForUrl
is the function doing the work of extracting all the Feed data from one feed using the Yahoo API. It looks a bit weird and I never thought this would work, but it actually fetches the data quite well. The only thing missing here is an image, apparently I found no solution to retrieve that information as well.
Let me know if you can find a solution for this, that would really add a few extra stars to that API.
Once we have the data we apply some transformation to convert the JSON response into FeedItems which we can then return to our view.
We got the heart of the app, the rest now will be easy simply using our service!
Showing and Adding new Feeds inside a Side Menu
As seen before we have the functions to store and retrieve stored Feeds from the storage. We want to have a simple side menu view where we have the different feeds inside the menu while displaying the actual articles from one feed inside the main view.
We start with the class for the side menu so go ahead and insert everything below into the src/pages/home/home.ts:
import {Component, ViewChild} from '@angular/core'; import {NavController, AlertController, Nav} from 'ionic-angular'; import {FeedListPage} from '../feed-list/feed-list'; import {FeedService, Feed} from '../../providers/feed-service'; @Component({ selector: 'page-home', templateUrl: 'home.html' }) export class HomePage { @ViewChild(Nav) nav: Nav; rootPage = FeedListPage; feeds: Feed[]; constructor(private navController: NavController, private feedService: FeedService, public alertCtrl: AlertController) {} public addFeed() { let prompt = this.alertCtrl.create({ title: 'Add Feed URL', inputs: [ { name: 'name', placeholder: 'The best Feed ever' }, { name: 'url', placeholder: 'http://www.myfeedurl.com/feed' }, ], buttons: [ { text: 'Cancel', role: 'cancel' }, { text: 'Save', handler: data => { let newFeed = new Feed(data.name, data.url); this.feedService.addFeed(newFeed).then( res => { this.loadFeeds(); } ); } } ] }); prompt.present(); } private loadFeeds() { this.feedService.getSavedFeeds().then( allFeeds => { this.feeds = allFeeds; }); } public openFeed(feed: Feed) { this.nav.setRoot(FeedListPage, {'selectedFeed': feed}); } public ionViewWillEnter() { this.loadFeeds(); } }
We use the ionViewWillEnter
to load the feeds from our service, and if we have the data we simply assign it to the this.feeds variable which will be used in our view in the next step.
The actual longer part is to add a feed, which is simply displaying an Alert with 2 inout fields for the URL and a clear name for the Feed. Inside the handler for the result of this alert we can catch the user input and create (add) a new feed again just by using our service and calling a reload on the view to update the list.
If the user selects a feed we set the root of our navigation to the FeedListPage and pass the selected feed object as a parameter. We will see how to handle these params in the next section.
Before we come to that we need to show the list of feeds inside our side menu view, so go ahead and open the src/pages/home/home.html:
<ion-menu [content]="content"> <ion-toolbar secondary> <ion-title>Recent articles</ion-title> </ion-toolbar> <ion-content> <ion-list> <button menuClose ion-item *ngFor="let feed of feeds" (click)="openFeed(feed)"> {{feed.title}} </button> </ion-list> <button ion-button full (click)="addFeed()" action secondary> <ion-icon name="add"></ion-icon> Add Feed </button> </ion-content> </ion-menu> <ion-nav [root]="rootPage" #content swipeBackEnabled="false"></ion-nav>
At the bottom we define our navigation where the root is set to the variable rootPage. The rest of the view defines the actual content of the side menu, where we display a list of our feeds and assign click events to each of them. Below the list is a button to add new feeds, which will then bring up the alert view.
So far we are almost done, let’s finish it up with the view for our articles!
Loading Feed Data and Showing Articles
Once the user selects a feed from the side menu we need to update the main view. We already saw how to pass params using the setRoot function, now we need to extract these values inside the constructor of our FeedListPage.
We use again the ionViewWillEnter
but add a check if we actually have a selected feed. We also add a little fallback to start loading the first Feed of our array if no feed is selected (at startup).
Our loadArticles
is now taking care of loading all the articles for a specific feed, and once we got the value we can set our array of articles and also set a variable to indicate loading progress to false.
Now go ahead and insert in your src/pages/feed-list/feed-list.ts:
import { Component } from '@angular/core'; import { NavController, NavParams} from 'ionic-angular'; import { InAppBrowser } from 'ionic-native'; import { Http } from '@angular/http'; import {FeedService, FeedItem, Feed} from '../../providers/feed-service'; @Component({ selector: 'page-feed-list', templateUrl: 'feed-list.html' }) export class FeedListPage { articles: FeedItem[]; selectedFeed: Feed; loading: Boolean; constructor(private nav: NavController, private feedService: FeedService, private navParams: NavParams) { this.selectedFeed = navParams.get('selectedFeed'); } public openArticle(url: string) { InAppBrowser.open(url, '_blank'); // window.open(url, '_blank'); } loadArticles() { this.loading = true; this.feedService.getArticlesForUrl(this.selectedFeed.url).subscribe(res => { this.articles = res; this.loading = false; }); } public ionViewWillEnter() { if (this.selectedFeed !== undefined && this.selectedFeed !== null ) { this.loadArticles() } else { this.feedService.getSavedFeeds().then( feeds => { if (feeds.length > 0) { let item = feeds[0]; this.selectedFeed = new Feed(item.title, item.url); this.loadArticles(); } } ); } } }
To finally open an article we can use the Cordova InAppBrowser and simply pass the URL of the feed and also say we want to open it in a blank view.
The view of the articles is again a simple list iterating over our array of articles. We also add the spinner which will depend on the loading variable. Open your src/pages/feed-list/feed-list.ts and insert:
<ion-header> <ion-navbar secondary> <button ion-button menuToggle> <ion-icon name="menu"></ion-icon> </button> <ion-title *ngIf="!selectedFeed">Newest Articles</ion-title> <ion-title>{{selectedFeed?.title}}</ion-title> </ion-navbar> </ion-header> <ion-content class="feed-list" padding> <ion-spinner *ngIf="loading" id="feed-spinner"></ion-spinner> <ion-list *ngIf="selectedFeed" class="spinner"> <ion-item *ngFor="let item of articles" (click)="openArticle(item.link)" class="feed-article"> <div class="article-title">{{item.title}}</div><br> <p [innerHtml]="item.description"></p> </ion-item> </ion-list> <div *ngIf="!selectedFeed" class="center-placeholder">Please select a feed!</div> </ion-content>
The description is not simply printed but added as innerHtml
as this seemed to work best for whatever comes from the service.
The view looks ok, but it’s never a bad idea to add some styling so we can tweak the appearance with some easy styles inside the src/pages/feed-list/feed-list.scss:
page-feed-list { .feed-list { background: #d1ffdc; .feed-article { height: 80px; background: #ffffff; border-radius: 10px; margin-bottom: 10px; border-top: 0px; color: #000000; } ion-list > .item:first-child, ion-list > .item-wrapper:first-child .item { border-top: 0px; } ion-list .item .item-inner { border-bottom: 0px; } .article-title { font-weight: bold; } } #feed-spinner { margin: auto; position: absolute; top: 0; left: 0; bottom: 0; right: 0; height: 100px; } .center-placeholder { margin: auto; position: absolute; top: 0; left: 0; bottom: 0; right: 0; width: 200px; height: 100px; } }
Now our own little RSS Reader with Ionic 2 is ready to be used and should look somehow like this:
I added the Devdactic feed for testing, but you can obviously pick any Feed you like (and subscribe to mine in your favorite RSS reader!).
Conclusion
The simple RSS reader using Ionic 2 has it’s limitations as the Yahoo API is not the best API in the world to convert our data from XML to JSON. If you have a good approach please share your thoughts below, also I hope you subscribe to my blog to get future updates and more awesome tutorials!
You can find a video version of this article below!
Happy Coding,
Simon
The post Building Your Own Simple RSS Reader with Ionic 2 appeared first on Devdactic.