my hacker news

An introduction to building mobile apps with Firebase, AngularJS and Ionic

Jamie Sutherland

CTO @ Mallzee

Tweeter me @wedgybo

What are we going to build?

A group chat application based around hacker news posts

 

The battle plan

  1. Get posts from hacker news
  2. View links posted on hacker news
  3. Store some user info to use when chatting
  4. Share posts to the group
  5. Comment on the shared posts with your pals
  6. Improvements (If we have time)

Feel free to shout out with any questions

Install ionic

 

> npm install -g ionic

> ionic start myhackernews

> cd myhackernews

> ionic setup sass 

> ionic serve --lab

Provides AngularJS and an awesome set of components to help quickly build cross platform mobile applications

Setup firebase

 

Build apps using their API to sync data in realtime.

https://www.firebase.com

 

Create a new app

My Hacker News

 

Blue Peter moment:

https://my-hacker-news.firebaseio.com/

ANgularfire

 

> bower install firebase angularfire

<!-- ionic/angularjs js -->
<script src="lib/ionic/js/ionic.bundle.js"></script>
<script src="lib/firebase/firebase.js"></script>
<script src="lib/angularfire/dist/angularfire.min.js"></script>
angular.module('starter', ['ionic', 'starter.controllers', 'starter.services', 'firebase'])

www/index.html

www/js/app.js

Hacker news data SERVICE

Angular uses services as the standard way of interacting with data. Use these to keep your data logic code in once place.

.factory('HackerNews', function ($http) {
  return {
    newest: function () {
      // Returns a promise to the data from the API
      return $http.get('http://hn-api.ionic.io/new/1');
    }
  };
})

www/js/services.js

Hookup hacker news service and Feed YOur SCOPE

We want to get the latest posts and be able to view and share the ones of interest with our buddies

.controller('HackerNewsCtrl', 
  function($scope, $ionicLoading, HackerNews) {

  $scope.posts = [];

  $scope.loadPosts = function () {
    HackerNews.newest().then(function (posts) {
      $scope.posts = posts.data;
    });
  };

  // Load the posts when this controller is loaded
  $scope.loadPosts();
})

www/js/controllers.js

Hacker news posts

  • ng-repeat: Looping through content
  • ng-click: Fire a scope function when the item is clicked
<ion-view title="Hacker News">
  <ion-content>
  <ion-list>
      <ion-item class="item-button-right" ng-repeat="post in posts" ng-click="viewPost(post)">
        <h2>{{post.title}}</h2>
        <p>{{post.submitted_by}} - {{post.submitted_on}}</p>
      </ion-item>
    </ion-list>
  </ion-content>
</ion-view>

www/templates/tab-hackernews.html

POst Viewer service

.factory('PostViewer', function ($rootScope, $sce, $ionicModal) {
  // Create a new scope for this viewer to bind to
  var $scope = $rootScope.$new();
  angular.extend($scope, {
    post: null,
    hideModal: function() {
      return modal.hide();
    },
    trustedUrl: function (url) {
      return $sce.trustAsResourceUrl(url);
    }
  });

  $ionicModal.fromTemplateUrl(
    'templates/post-detail.html', 
    {
      scope: $scope
    }
  ).then(function (ionicModal) {
    modal = ionicModal;
  });

  return {
    viewPost: function (post) {
      // Update the scope with the latest post to view
      $scope.post = post;
      modal.show();
    }
  };
})

www/js/services.js

Post Viewer 

<ion-modal-view>
  <ion-header-bar align-title="left">
    <h1 class="title">{{post.title}}</h1>
    <div class="buttons">
      <button class="button button-outline button-positive" ng-click="hideModal()">
      Close
      </button>
    </div>
  </ion-header-bar>
  <ion-content scroll="false">
    <iframe height="100%" width="100%" ng-src="{{trustedUrl(post.url)}}" border="0"></iframe>
  </ion-content>
</ion-modal-view>
// Inject PostViewer and add this to HackerNewsCtrl

$scope.viewPost = function (post) {
  PostViewer.viewPost(post);
};

www/js/controllers.js

www/templates/post-detail.html

USER service

We need a service to store the information about the user and which group they are participating in

.factory('User', function ($window) {
  var user = {
    name: '',
    group: ''
  };

  // Decode the user in local storage
  user = JSON.parse($window.localStorage.getItem('user')) || user;

  return {
    get: function () {
      return user;
    },
    save: function (updatedUser) {
      user = updatedUser;
      // Save any changes back to local storage for next time
      $window.localStorage.setItem('user', JSON.stringify(user));
    }
  };
});

www/js/services.js

User Account

ng-model: Maps data

ng-change: Automagical saving

<ion-view title="Account">
  <ion-content class="padding">
    <div class="list">
      <label class="item item-input item-label">
        <span class="input-label">Username</span>
        <input type="text" placeholder="Username" 
         ng-model="user.name" ng-change="saveUser(user)">
      </label>
      <label class="item item-input item-label">
        <span class="input-label">Group</span>
        <input type="text" placeholder="Group name"
         ng-model="user.group" ng-change="saveUser(user)">
      </label>
    </div>
  </ion-content>
</ion-view>
$scope.saveUser = function (user) {
  User.save(user);
}

www/js/controllers.js - AccountCtrl

www/templates/tab-account.html

Share a post with the group

Lets create a service to interact with the group

.factory('GroupNews', function ($q, $firebase, User) {

  var ref = new Firebase("https://my-hacker-news.firebaseio.com/groups/" + User.get().group || '');

  return {
    posts: $firebase(ref).$asArray(),
    getPosts: function () {
      return this.posts.$loaded();
    },
    sharePost: function (post) {
      return this.posts.$add(post);
    },
    get: function (postId) {
      return $firebase(ref.child(postId)).$asObject().$loaded();
    }
  };
})
<button class="button button-clear" ion-stop-event="click" ng-click="sharePost(post)">
  <i class="icon ion-ios7-upload-outline"></i>
</button>
$scope.sharePost = function (post) {
  $ionicLoading.show({
    template: 'Sharing post ' + post.title
  });
  GroupNews.sharePost(post).then($ionicLoading.hide);
};

www/js/controllers.js

www/templates/tab-hackernews.html

www/js/services.js

Viewing the shared posts in the group

.controller('ChatsCtrl', function($scope, GroupNews) {
  GroupNews.getPosts().then(function (posts) {
    $scope.posts = posts;
  });
})
<ion-view title="Chats">
  <ion-content>
    <ion-list>
      <ion-item ng-repeat="post in posts" type="item-text-wrap" href="#/chats/{{post.$id}}">
        <h2>{{post.title}}</h2>
      </ion-item>
    </ion-list>
  </ion-content>
</ion-view>

www/templates/tab-chats.html

www/js/controllers.js

Chat details

We'll setup for viewing information about the shared post

  • Set the state
  • Act on the state parameter
.controller('ChatDetailCtrl', 
  function($scope, $stateParams, $firebase, User, GroupNews, PostViewer) {

  GroupNews.get($stateParams.postId).then(function (post) {
      $scope.post = post;
  });

  $scope.viewPost = function (post) {
    PostViewer.viewPost(post);
  };
})
.state('tab.chat-detail', {
  url: 'chats/:postId',
  views: {
    'tab-chats': {
      templateUrl: 'templates/chat-detail.html',
      controller: 'ChatDetailCtrl'
    }
  }
})

www/js/app.js

www/js/controllers.js

Chat details

We need to show some detail on the post

<ion-view title="Group Chat">
  <ion-content padding="true">
    <div class="list card">
      <div class="item item-text-wrap">
        <h2>{{post.title}}</h2>
      </div>
      <div class="item">
        <p>
          Post by: {{post.submitted_by}}
        </p>
        <p>
          <span href="#" class="subdued">{{post.submitted_on}}</span> |
          <span href="#" class="subdued">{{post.points}} Points</span> |
          <span href="#" class="subdued">{{post.comments}} Comments</span>
        </p>
      </div>
      <a class="item item-icon-left item-icon-right" ng-click="viewPost(post)" href="#">
        <i class="icon ion-ios7-world"></i>
        View post
        <i class="icon ion-ios7-arrow-right"></i>
      </a>
    </div>
</ion-view>

www/templates/chat-detail.html

Comments

Lets get some commenting on the go

<ul class="comments">
  <li class="comment" ng-repeat="comment in post.userComments">
    <p>{{comment.text}}<span>{{comment.user}}</span></p>
  </li>
</ul>
</ion-content>
<ion-footer-bar keyboard-attach class="bar-stable item-input-inset">
  <label class="item-input-wrapper">
    <input type="text" placeholder="Type your message" ng-model="comment.text" />
  </label>
  <button class="button button-clear" ng-click="addComment(post.$id, comment)">
    Send
  </button>
</ion-footer-bar>

www/templates/chat-detail.html

Commenting

We need to hookup the ng-click actions to the controller which then adds comments via the GroupNews service

addComment: function (postId, comment) {
  $firebase(ref.child(postId + '/userComments')).$asArray().$add(comment);
},

www/js/services.js - GroupNews

$scope.comment = {
  user: User.get().name
};

$scope.addComment = function (postId, comment) {
  GroupNews.addComment(postId, comment);
};

$scope.viewPost = function (post) {
  PostViewer.viewPost(post);
};

www/js/controllers.js - ChatDetailCtrl

Improvments

(Do we have time?)

Pull to refresh

Lets allow the user to refresh the latest list of Hacker News posts

<ion-refresher
  pulling-text="Pull to refresh..."
  on-refresh="loadPosts()">
</ion-refresher>

www/templates/tab-hackernews.html

HackerNews.newest(1).then(function (posts) {
  $scope.posts = posts;
  $scope.$broadcast('scroll.refreshComplete');
});

www/js/controllers.js - HackerNewsCtrl.loadPosts

Filtering sassy GOODNESS

Lets clean up those comments

.comments {
  margin:0; padding:0;
  list-style-type:none;

  li {
    display:block; clear:both;
    max-width:50%;
    margin:0 0 1rem 0; padding:0;

    p {
      border-radius:.75rem;
      background:#e6e5eb; color:#383641;
      padding:.6875rem; margin:0;
      font-size:.875rem;

      span {
        display: block;
        font-size: 10px;
        font-weight: light;
      }
    }
  }

  li.mine {
    float:right;
    p {
      background:#158ffe; color:#fff;

      span {
        text-align: right;
      }
    }
  }
}
.filter('isMine', function (User) {
  return function (input) {
    return User.get().name === input;
  };
})
ng-class="{'mine': {{comment.user | isMine}}}"

Test on a device

Use the ionic tools to create projects for your device of choice.

 

Live reload changes on the device is pretty damn awesome!

 

> ionic platform add [ios|android]

> ionic run -l [ios|android]

Let me play!

 

Point your mobile browser here and give it a whirl.

 

https://wedgybo.github.io/my-hacker-news

 

Remember to name yourself and set the group to gdgedinburgh in the account page.

If nothing shows. Tell your browser to load unsecure scripts

Thanks for listening

jamie@mallzee.com - +JamieSutherland - @wedgybo