Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
AngularJS Deployment Essentials

You're reading from   AngularJS Deployment Essentials

Arrow left icon
Product type Paperback
Published in Feb 2015
Publisher
ISBN-13 9781783983582
Length 148 pages
Edition 1st Edition
Languages
Arrow right icon
Author (1):
Arrow left icon
Zachariah Moreno Zachariah Moreno
Author Profile Icon Zachariah Moreno
Zachariah Moreno
Arrow right icon
View More author details
Toc

Krakn

As a quick refresher, krakn was constructed using all of the tools that are covered in this chapter. These include Git, GitHub, Node.js, NPM, Yeoman's workflow, Yo, Grunt, Bower, Batarang, and Sublime Text. The application builds on Angular, Firebase, the Ionic Framework, and a few other minor dependencies.

The workflow I used to develop krakn went something like the following. Follow these steps to achieve the same thing. Note that you can skip the remainder of this section if you'd like to get straight to the deployment action, and feel free to rename things where necessary.

Setting up Git and GitHub

The workflow I followed while developing krakn begins with initializing our local Git repository and connecting it to our remote master repository on GitHub. In order to install and set up both, perform the following steps:

  1. Firstly, install all the tool stack dependencies, and create a folder called krakn.
  2. Following this, run $ git init, and you will create a README.md file.
  3. You should then run $ git add README.md and commit README.md to the local master branch.
  4. You then need to create a new remote repository on GitHub called ZachMoreno/krakn.
  5. Following this, run the following command:
    $ git remote add origin git@github.com:[YourGitHubUserName] /krakn.git
    
  6. Conclude the setup by running $ git push –u origin master.

Scaffolding the app with Yo

Scaffolding our app couldn't be easier with the yo ionic generator. To do this, perform the following steps:

  1. Firstly, install Yo by running $ npm install -g yo.
  2. After this, install generator-ionicjs by running $ npm install -g generator-ionicjs.
  3. To conclude the scaffolding of your application, run the yo ionic command.

Development

After scaffolding the folder structure and boilerplate code, our workflow advances to the development phase, which is encompassed in the following steps:

  1. To begin, run grunt server.
  2. You are now in a position to make changes, for example, these being deletions or additions.
  3. Once these are saved, LiveReload will automatically reload your browser.
  4. You can then review the changes in the browser.
  5. Repeat steps 2-4 until you are ready to advance to the predeployment phase.

Views, controllers, and routes

Being a simple chat application, krakn has only a handful of views/routes. They are login, chat, account, menu, and about. The menu view is present in all the other views in the form of an off-canvas menu.

The login view

The default view/route/controller is named login. The login view utilizes the Firebase's Simple Login feature to authenticate users before proceeding to the rest of the application. Apart from logging into krakn, users can register a new account by entering their desired credentials. An interesting part of the login view is the use of the ng-show directive to toggle the second password field if the user selects the register button. However, the ng-model directive is the first step here, as it is used to pass the input text from the view to the controller and ultimately, the Firebase Simple Login. Other than the Angular magic, this view uses the ion-view directive, grid, and buttons that are all core to Ionic.

Each view within an Ionic app is wrapped within an ion-view directive that contains a title attribute as follows:

<ion-view title="Login">

The login view uses the standard input elements that contain a ng-model attribute to bind the input's value back to the controller's $scope as follows:

  <input type="text" placeholder="you@email.com" ng-model="data.email" />

  <input type="password" placeholder= "embody strength" ng-model="data.pass" />

  <input type="password" placeholder= "embody strength" ng-model="data.confirm" />

The Log In and Register buttons call their respective functions using the ng-click attribute, with the value set to the function's name as follows:

  <button class="button button-block button-positive" ng-click="login()" ng-hide="createMode">Log In</button>

The Register and Cancel buttons set the value of $scope.createMode to true or false to show or hide the correct buttons for either action:

  <button class="button button-block button-calm" ng-click="createMode = true" ng-hide="createMode">Register</button>
  <button class="button button-block button-calm" ng-show="createMode" ng-click= "createAccount()">Create Account</button>

  <button class="button button-block button-assertive" ng-show="createMode" ng-click="createMode =     false">Cancel</button>

$scope.err is displayed only when you want to show the feedback to the user:

  <p ng-show="err" class="assertive text-center">{{err}}</p>

</ion-view>

The login controller is dependent on Firebase's loginService module and Angular's core $location module:

controller('LoginCtrl', ['$scope', 'loginService', '$location',
  function($scope, loginService, $location) {

Ionic's directives tend to create isolated scopes, so it was useful here to wrap our controller's variables within a $scope.data object to avoid issues within the isolated scope as follows:

    $scope.data = {
      "email"   : null,
      "pass"   : null,
      "confirm"  : null,
      "createMode" : false
    }

The login() function easily checks the credentials before authentication and sends feedback to the user if needed:

    $scope.login = function(cb) {
      $scope.err = null;
      if( !$scope.data.email ) {
        $scope.err = 'Please enter an email address';
      }
      else if( !$scope.data.pass ) {
        $scope.err = 'Please enter a password';
      }

If the credentials are sound, we send them to Firebase for authentication, and when we receive a success callback, we route the user to the chat view using $location.path() as follows:

      else {
        loginService.login($scope.data.email, $scope.data.pass, function(err, user) {
         $scope.err = err? err + '' : null;
         if( !err ) {
          cb && cb(user);
          $location.path('krakn/chat');
         }
       });
      }
    };

The createAccount() function works in much the same way as login(), except that it ensures that the users don't already exist before adding them to your Firebase and logging them in:

    $scope.createAccount = function() {
      $scope.err = null;
      if( assertValidLoginAttempt() ) {
       loginService.createAccount($scope.data.email, $scope.data.pass,
         function(err, user) {
          if( err ) {
            $scope.err = err? err + '' : null;
          }
          else {
            // must be logged in before I can write to my profile
            $scope.login(function() {
             loginService.createProfile(user.uid, user.email);
             $location.path('krakn/account');
            });
          }
         });
      }
    };

The assertValidLoginAttempt() function is a function used to ensure that no errors are received through the account creation and authentication flows:

    function assertValidLoginAttempt() {
      if( !$scope.data.email ) {
       $scope.err = 'Please enter an email address';
      }
      else if( !$scope.data.pass ) {
       $scope.err = 'Please enter a password';
      }
      else if( $scope.data.pass !== $scope.data.confirm ) {
       $scope.err = 'Passwords do not match';
      }
      return !$scope.err;
    }
   }])

The chat view

Keeping vegan practices aside, the meat and potatoes of krakn's functionality lives within the chat view/controller/route. The design is similar to most SMS clients, with the input in the footer of the view and messages listed chronologically in the main content area. The ng-repeat directive is used to display a message every time a message is added to the messages collection in Firebase. If you submit a message successfully, unsuccessfully, or without any text, feedback is provided via the placeholder attribute of the message input.

There are two filters being utilized within the chat view: orderByPriority and timeAgo. The orderByPriority filter is defined within the firebase module that uses the Firebase object IDs that ensure objects are always chronological.

Tip

The timeAgo filter is an open source Angular module that I found. You can access it at www.jsfiddle.net/i_woody/cnL5T/.

The ion-view directive is used once again to contain our chat view:

<ion-view title="Chat">

Our list of messages is composed using the ion-list and ion-item directives, in addition to a couple of key attributes. The ion-list directive gives us some nice interactive controls using the option-buttons and can-swipe attributes. This results in each list item being swipeable to the left, revealing our option-buttons as follows:

  <ion-list option-buttons="itemButtons" can-swipe= "true" ng-show="messages">

Our workhorse in the chat view is the trusty ng-repeat directive, responsible for persisting our data from Firebase to our service to our controller and into our view and back again:

   <ion-item ng-repeat="message in messages | orderByPriority" item="item" can-swipe="true">

Then, we bind our data into vanilla HTML elements that have some custom styles applied to them:

    <h2 class="user">{{ message.user }}</h2>

The third-party timeago filter converts the time into something such as, "5 min ago", similar to Instagram or Facebook:

    <small class="time">{{ message.receivedTime | timeago }}</small>
    <p class="message">{{ message.text }}</p>
   </ion-item>
  </ion-list>

A vanilla input element is used to accept chat messages from our users. The input data is bound to $scope.data.newMessage for sending data to Firebase and $scope.feedback is used to keep our users informed:

  <input type="text" class="{{ feeling }}" placeholder= "{{ feedback }}" ng-model="data.newMessage" />

When you click on the send/submit button, the addMessage() function sends the message to your Firebase, and adds it to the list of chat messages, in real time:

  <button type="submit" id="chat-send" class="button button-small button-clear" ng-click="addMessage()"><span class="ion-android-send"></span></button>
</ion-view>

The ChatCtrl controller is dependant on a few more modules other than our LoginCtrl, including syncData, $ionicScrollDelegate, $ionicLoading, and $rootScope:

controller('ChatCtrl', ['$scope', 'syncData', '$ionicScrollDelegate', '$ionicLoading', '$rootScope',
   function($scope, syncData, $ionicScrollDelegate, $ionicLoading, $rootScope) {

The userName variable is derived from the authenticated user's e-mail address (saved within the application's $rootScope) by splitting the e-mail and using everything before the @ symbol:

var userEmail = $rootScope.auth.user.e-mail
      userName = userEmail.split('@');

Avoid isolated scope issue in the same fashion, as we did in LoginCtrl:

    $scope.data = {
      newMessage   : null,
      user      : userName[0]
    }

Our view will only contain the latest 20 messages that have been synced from Firebase:

    $scope.messages = syncData('messages', 20);

When a new message is saved/synced, it is added to the bottom of the ng-repeated list, so we use the $ionicScrollDeligate variable to automatically scroll the new message into view on the display as follows:

$ionicScrollDelegate.scrollBottom(true);

Our default chat input placeholder text is something on your mind?:

    $scope.feedback = 'something on your mind?';
    // displays as class on chat input placeholder
    $scope.feeling = 'stable';

If we have a new message and a valid username (shortened), then we can call the $add() function, which syncs the new message to Firebase and our view is as follows:

    $scope.addMessage = function() {
      if(  $scope.data.newMessage
        && $scope.data.user ) {
       // new data elements cannot be synced without adding them to FB Security Rules
       $scope.messages.$add({
                   text    : $scope.data.newMessage,
                   user    : $scope.data.user,
                   receivedTime : Number(new Date())
                 });
       // clean up
       $scope.data.newMessage = null;

On a successful sync, the feedback updates say Done! What's next?, as shown in the following code snippet:

       $scope.feedback = 'Done! What\'s next?';
       $scope.feeling = 'stable';
      }
      else {
       $scope.feedback = 'Please write a message before sending';
       $scope.feeling = 'assertive';
      }
    };

    $ionicScrollDelegate.scrollBottom(true);
])

The account view

The account view allows the logged in users to view their current name and e-mail address along with providing them with the ability to update their password and e-mail address. The input fields interact with Firebase in the same way as the chat view does using the syncData method defined in the firebase module:

<ion-view title="'Account'" left-buttons="leftButtons">

The $scope.user object contains our logged in user's account credentials, and we bind them into our view as follows:

  <p>{{ user.name }}</p>
 …
  <p>{{ user.email }}</p>

The basic account management functionality is provided within this view; so users can update their e-mail address and or password if they choose to, using the following code snippet:

  <input type="password" ng-keypress= "reset()" ng-model="oldpass"/>
 …
  <input type="password" ng-keypress= "reset()" ng-model="newpass"/>
 …
  <input type="password" ng-keypress= "reset()" ng-model="confirm"/>

Both the updatePassword() and updateEmail() functions work in much the same fashion as our createAccount() function within the LoginCtrl controller. They check whether the new e-mail or password is not the same as the old, and if all is well, it syncs them to Firebase and back again:

  <button class="button button-block button-calm" ng-click= "updatePassword()">update password</button>
 …
   <p class="error" ng-show="err">{{err}}</p>
  <p class="good" ng-show="msg">{{msg}}</p>
 …
  <input type="text" ng-keypress="reset()" ng-model="newemail"/>
 …
  <input type="password" ng-keypress="reset()" ng-model="pass"/>
 …
  <button class="button button-block button-calm" ng-click= "updateEmail()">update email</button>
 …
  <p class="error" ng-show="emailerr">{{emailerr}}</p>
  <p class="good" ng-show="emailmsg">{{emailmsg}}</p>
 …
</ion-view>

The menu view

Within krakn/app/scripts/app.js, the menu route is defined as the only abstract state. Because of its abstract state, it can be presented in the app along with the other views by the ion-side-menus directive provided by Ionic. You might have noticed that only two menu options are available before signing into the application and that the rest appear only after authenticating. This is achieved using the ng-show-auth directive on the chat, account, and log out menu items. The majority of the options for Ionic's directives are available through attributes making them simple to use. For example, take a look at the animation="slide-left-right" attribute. You will find Ionic's use of custom attributes within the directives as one of the ways that the Ionic Framework is setting itself apart from other options within this space.

The ion-side-menu directive contains our menu list similarly to the one we previously covered, the ion-view directive, as follows:

<ion-side-menus>
 <ion-pane ion-side-menu-content>
  <ion-nav-bar class="bar-positive">

Our back button is displayed by including the ion-nav-back-button directive within the ion-nav-bar directive:

   <ion-nav-back-button class="button-clear"><i class= "icon ion-chevron-left"></i> Back</ion-nav-back-button>
  </ion-nav-bar>

Animations within Ionic are exposed and used through the animation attribute, which is built atop the ngAnimate module. In this case, we are doing a simple animation that replicates the experience of a native mobile app:

  <ion-nav-view name="menuContent" animation="slide-left-right"></ion-nav-view>
 </ion-pane>

 <ion-side-menu side="left">
  <header class="bar bar-header bar-positive">
   <h1 class="title">Menu</h1>
  </header>
  <ion-content class="has-header">

A simple ion-list directive/element is used to display our navigation items in a vertical list. The ng-show attribute handles the display of menu items before and after a user has authenticated. Before a user logs in, they can access the navigation, but only the About and Log In views are available until after successful authentication.

   <ion-list>
    <ion-item nav-clear menu-close href= "#/app/chat" ng-show-auth="'login'">
     Chat
    </ion-item>

    <ion-item nav-clear menu-close href="#/app/about">
     About
    </ion-item>

    <ion-item nav-clear menu-close href= "#/app/login" ng-show-auth="['logout','error']">
     Log In
    </ion-item>

The Log Out navigation item is only displayed once logged in, and upon a click, it calls the logout() function in addition to navigating to the login view:

    <ion-item nav-clear menu-close href="#/app/login" ng-click= "logout()" ng-show-auth="'login'">
     Log Out
    </ion-item>
   </ion-list>
  </ion-content>
 </ion-side-menu>
</ion-side-menus>

The MenuCtrl controller is the simplest controller in this application, as all it contains is the toggleMenu() and logout() functions:

controller("MenuCtrl", ['$scope', 'loginService', '$location', '$ionicScrollDelegate', function($scope, loginService, $location, $ionicScrollDelegate) {
  $scope.toggleMenu = function() {
   $scope.sideMenuController.toggleLeft();
  };

  $scope.logout = function() {
    loginService.logout();
    $scope.toggleMenu();  };
 }])

The about view

The about view is 100 percent static, and its only real purpose is to present the credits for all the open source projects used in the application.

Global controller constants

All of krakn's controllers share only two dependencies: ionic and ngAnimate. Because Firebase's modules are defined within /app/scripts/app.js, they are available for consumption by all the controllers without the need to define them as dependencies. Therefore, the firebase service's syncData and loginService are available to ChatCtrl and LoginCtrl for use.

The syncData service is how krakn utilizes three-way data binding provided by www.krakn.firebaseio.com. For example, within the ChatCtrl controller, we use syncData( 'messages', 20 ) to bind the latest twenty messages within the messages collection to $scope for consumption by the chat view. Conversely, when a ng-click user clicks the submit button, we write the data to the messages collection by use of the syncData.$add() method inside the $scope.addMessage() function:

$scope.addMessage = function() {
  if(...) { $scope.messages.$add({ ... });
  }
};

Models and services

The model for krakn is www.krakn.firebaseio.com. The services that consume krakn's Firebase API are as follows:

  • The firebase service in krakn/app/scripts/service.firebase.js
  • The login service in krakn/app/scripts/service.login.js
  • The changeEmail service in krakn/app/scripts/changeEmail.firebase.js

The firebase service defines the syncData service that is responsible for routing data bidirectionally between krakn/app/bower_components/angularfire.js and our controllers. Please note that the reason I have not mentioned angularfire.js until this point is that it is basically an abstract data translation layer between firebaseio.com and Angular applications that intend on consuming data as a service.

Models and services
You have been reading a chapter from
AngularJS Deployment Essentials
Published in: Feb 2015
Publisher:
ISBN-13: 9781783983582
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime
Banner background image