Building your first PWA: Objects

person carlosrojasofolder_opencrashcourseaccess_time September 7, 2017

Hello, Startuper and Welcome to this new lesson.

Until this moment has you learned to:

How to connect an App with Ionic Cloud.

Foundations about Ionic and the technologies that we are using.

Today we are going to learn about Classes and Objects and how to build our news feed.

What is an Object ?

An Object is compound data. Mix several data types into a unique package. The big think about objects is that mix data types with code.

What is a Class?

Classes are the data type for objects. Each Class defines values and data types that going to handle, in addition to the procedures you will use to manipulate that data and deliver results.

You can see a class as a template and the object as the product of that template.

Objects have two features, attributes, and methods. Attributes are properties that have objects and methods are functions that objects can use. You can see an example in this image.

If you put an eye on your App we can find this element in our Project.

login.ts

import { FormBuilder, FormGroup, Validators } from '@angular/forms';

export class LoginPage {
myForm: FormGroup;
}

If you see this piece of code, you can see the words import and export.

import is the word that tells Ionic that you are going to create objects from the classes you are importing. In the same way, the word export tells Ionic that the class you are creating could be imported in the future.

What is a Service?

For this class, we will use a service. A service is something like a piece of code with methods that you are going to share in different views of your app. to create a provider we will use the generate command.

$ ionic g provider feed

and now we need to tell Ionic that we are going to use this service.


...
import { FeedProvider } from './../providers/feed/feed';
...
@NgModule({
 declarations: [
 MyApp,
...
...
 providers: [FeedProvider]
})
export class AppModule {}

Ok, we are ready to continue with our App. So far we have the registration system of our App, but I want that when people register and login into my App they can see a list of news that is going to be brought from an RSS. For this, I will be based on the work of two great tutorials that already explain how to do this.

Ionic 2 First Drive: Making an RSS Reader Building Your Own Simple RSS Reader with Ionic

https://dzone.com/articles/time-for-ionic-2

Adding new Pages.


$ ionic g page feedList

Adding Plugins.

$ionic cordova plugin add cordova-plugin-inappbrowser

$ionic cordova plugin add cordova-sqlite-storage

here we will call to IonicStorage which will allow us to store our URLs.

Ok let’s first of all create the provider src/providers/feed/feed.ts

feed.ts

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';
import { Storage } from '@ionic/storage';

/*
  Generated class for the FeedProvider provider.

  See https://angular.io/docs/ts/latest/guide/dependency-injection.html
  for more info on providers and Angular DI.
*/

  /*
  Based in: https://devdactic.com/rss-reader-ionic2/
  */
  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 FeedProvider {

  constructor(private http: Http, public storage: Storage) {}
 
  public getSavedFeeds() {
    return this.storage.get('savedFeeds').then(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
    })
  }

}

Ok, basically what we do here is to process the XML file that delivers the RSS and store it in articles. Don’t worry if you not understand it one hundred percent, later when you have more experience you can come back here and analyze it again.

Additionally, you can see that we have created two objects, Feed and FeedItem so that it is easier to manipulate the information.

Now having the Service ready, let’s modify the template src/pages/home/home.html

home.html

<!--
  Generated template for the HomePage page.

  See http://ionicframework.com/docs/components/#navigation for more info on
  Ionic pages and navigation.
-->
<ion-menu [content]="content">
  <ion-header>
    <ion-toolbar secondary>
      <ion-title>Recientes</ion-title>
    </ion-toolbar>
  </ion-header>
 
  <ion-content>
    <ion-list>
    <a ion-button block clear (click)="logOut()">
      Salir
    </a>
    </ion-list>
    <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> Agregar
    </button>
  </ion-content>
 
</ion-menu>
 
<ion-nav [root]="rootPage" #content swipeBackEnabled="false"></ion-nav>

In the template we have created a side menu which we will then use to add new URLs, additionally, we have placed a function that will close the session and return us to loginPage.

 

All the implementation will be seen in the component logic src/pages/home/home.ts.


import { Component, ViewChild } from '@angular/core';
import { IonicPage, AlertController, NavController, Nav } from 'ionic-angular';
import { AngularFireAuth } from 'angularfire2/auth';
import { LoginPage } from '../../pages/login/login';
import { FeedProvider, Feed } from '../../providers/feed/feed';

/**
 * Generated class for the HomePage page.
 *
 * See http://ionicframework.com/docs/components/#navigation for more info
 * on Ionic pages and navigation.
 */
@IonicPage()
@Component({
  selector: 'page-home',
  templateUrl: 'home.html',
})
export class HomePage {
  @ViewChild(Nav) nav: Nav;
 
  rootPage = 'FeedListPage';
  feeds: Feed[];

  constructor(private navController: NavController, public afAuth: AngularFireAuth, private feedProvider: FeedProvider, public alertCtrl: AlertController) { }

  ionViewDidLoad() {
    console.log('ionViewDidLoad HomePage');
  }

  logOut(){
    this.afAuth.auth.signOut();
    this.navController.setRoot(LoginPage);
  }

  public addFeed() {
    let prompt = this.alertCtrl.create({
      title: 'Agregar RSS URL',
      inputs: [
        {
          name: 'nombre',
          placeholder: 'El mejor sitio'
        },
        {
          name: 'url',
          placeholder: 'https://www.ion-book.com/feed.xml'
        },
      ],
      buttons: [
        {
          text: 'Cancel',
          role: 'cancel'
        },
        {
          text: 'Save',
          handler: data => {
            let newFeed = new Feed(data.name, data.url);
            this.feedProvider.addFeed(newFeed).then(
              res => {
                this.loadFeeds();
              }
            );
          }
        }
      ]
    });
    prompt.present();
  }
 
  private loadFeeds() {
    this.feedProvider.getSavedFeeds().then(
      allFeeds => {
        this.feeds = allFeeds;
      });
  }
 
  public openFeed(feed: Feed) {
    this.nav.setRoot('FeedListPage', { 'selectedFeed': feed });
  }
 
  public ionViewWillEnter() {
    this.loadFeeds();
  }

}

Now let’s focus on the FeedListPage I like to start by explaining the template since it makes us understand some of the attributes we are using src/pages/feed-list/feed-list.html

&amp;lt;ion-header&amp;gt;
<ion-header>
  <ion-navbar secondary>
    <ion-buttons start>
      <button ion-button menuToggle>
      <ion-icon name="menu"></ion-icon>
    </button>
    </ion-buttons>
    <ion-title *ngIf="!selectedFeed">Articulos</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>
  <ion-row *ngIf="!selectedFeed">
    <ion-col text-center>
      Seleccione una lista.
    </ion-col>
  </ion-row>
</ion-content>

In general what we are doing is bringing all the articles with the *ngFor directive and placing them inside the ion-item component I invite you to level up by looking at the components and the directives in the documentation and see how they work.

Thanks to our Service now our component is much clearer to read src/pages/feed-list/feed-list.ts


import { Component } from '@angular/core';
import { NavParams, IonicPage } from 'ionic-angular';
import { InAppBrowser } from '@ionic-native/in-app-browser';
import { FeedProvider, FeedItem, Feed } from '../../providers/feed/feed';
 
@IonicPage({
  name: 'FeedListPage'
})
@Component({
  selector: 'page-feed-list',
  templateUrl: 'feed-list.html'
})
export class FeedListPage {
  articles: FeedItem[];
  selectedFeed: Feed;
  loading: Boolean;
 
  constructor(
    private iab: InAppBrowser, 
    private feedProvider: FeedProvider,
    public navParams: NavParams) {
    this.selectedFeed = navParams.get('selectedFeed');
  }
 
  public openArticle(url: string) {
    this.iab.create(url, '_blank');
    // window.open(url, '_blank');
  }
 
  loadArticles() {
    this.loading = true;
    this.feedProvider.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.feedProvider.getSavedFeeds().then(
        feeds => {
          if (feeds.length > 0) {
            let item = feeds[0];
            this.selectedFeed = new Feed(item.title, item.url);
            this.loadArticles();
          }
        }
      );
    }
  }
}

and finally we will add the styles of the page through SaSS in the src/pages/feed-list/feed-list.scss

page-feed-list {
  .feed-list {
    background: #4E8EF7;
    .feed-article {
      height: 80px;
      background: #ffffff;
      border-radius: 10px;
      margin-bottom: 10px;
      border-top: 0px;
      color: #666;
    }
    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;
  }
}

Ready. already joining all these pieces we must have our first App, running and you can send it to your device and see how it feels to create an App 🙂

Liked it? Take a second to support carlosrojaso on Patreon!