TL;DR: In this article, you'll develop a simple application in AngularJS 1.5. This application will show you how to use some of the features discussed in Part 1 of this tutorial to build component-based AngularJS applications. If you want to skip the tutorial and dive straight into the code, the repo is publicly available.


Before AngularJS 1.5, developers relied solely on directives to be able to build reusable components in their applications. Currently, AngularJS 1.5+ offers the ability to use the .component() method to build isolated and reusable components like React and Angular applications.

Introducing SpeakerHang

I admire several developers in the community, and one of them is Nadia Odunayo. She's a Ruby developer and regular conference speaker. She built a pretty popular side project called Speakerline. Speakerline is an open source project to help demystify the tech conference CFP process for new speakers. The project is built in Rails.

SpeakerHang, the project we'll build in this tutorial is largely inspired by Speakerline. It's a simpler version. SpeakerHang displays a list of conference speakers, their details and allows you to add a speaker.

Worthy of note here is that there is no database or external REST API. The speakers are added to an array. The crux of this guide is to teach you how to build a component-based AngularJS application easily.

Visualizing SpeakerHang

The best way to build apps is to visualize how the end product will look. Let's think about SpeakerHang in components. Check out the visualization below:

SpeakerHang - Initial component visualization SpeakerHang - Component Visualization

SpeakerHang - Component Visualization SpeakerHang - Component Visualization

From the diagram above, our visualization produced four components.

  • First component for navigation.
  • Second component for Add Speaker form.
  • Third component for Add Speaker container.
  • Fourth component for a list of speakers.

NOTE: You can break up your app into as many components as you want, but be careful not to complicate the process.

Build SpeakerHang

I have a starter pack already configured for building AngularJS 1.5+ apps. The starter pack ships with webpack, Sass compilation, autoreload, ES6 transpiling, component generation, Angularjs UI-Router, and test files generator. It's a forked and enhanced version of NG6-Starter.

Clone the AngularJS starter pack, cd into the directory and run npm install to install all the dependencies.

Run gulp serve to start the app.

Your app should look like the diagram below:

AngularJS Starter Pack AngularJS Starter Pack Index

Before proceeding, I'll like you to delete the following:

  • The app/common directory.
  • This line in app/app.js: import Common from './common/common';
  • Remove Common from the required dependencies in angular.module in app.js.
  • The about, home components folder and components.js file in app/components directory.
  • This line in app/app.js: import Components from './components/components';
  • Remove Components from the required dependencies in angular.module in app.js.

Now, your app should show a blank page. If there are no errors, then you are on track.

Set Up Routes

We'll make use of the efficient UI-Router. Open up app.js and replace the content with the code below:

import angular from 'angular';
import uiRouter from '@uirouter/angularjs';
import AppComponent from './app.component';
import 'normalize.css';
import 'skeleton.css/skeleton.css';
import './app.scss';

angular.module('app', [
    uiRouter
  ])
  .config(($locationProvider, $stateProvider, $urlRouterProvider) => {
    "ngInject";

    $stateProvider
      .state('app', {
        url: '/app',
        abstract: true,
        template: '<app></app>'
      })

      .state('home', {
        url: '/home',
        template: '<h4>The Home of Speakers in the Developer Community</h4>'
      });

   // Default page for the router
   $urlRouterProvider.otherwise('/home');
  })

  .component('app', AppComponent);

In the code above, we defined the home and app state. We also configured the default page for the app. The default page that will be rendered when a user visits the app is the /home route which renders the template.

Note: The template can be a component or simply a string.

Your app should render like this below:

SpeakerHang - Home Index SpeakerHang - Home Index

Craft Components - NavBar

It's time to build our components. Earlier, we visualized the SpeakerHang app and came up with four components.

  • NavBar component
  • Add Speaker container component
  • Speaker form component
  • Speaker list component

Let's start with the navbar component.

Run the following command in your terminal to generate a component:

gulp component --name navbar

This command will generate a new component, navbar, inside the components folder with the following files:

  • navar.component.js - The navbar component itself
  • navbar.html - The navbar template
  • navbar.scss - The style for the navbar template
  • navbar.js - The navbar module that ties everything together
  • navbar.controller.js - The navbar controller that defines the business logic
  • navbar.spec.js - The navbar test file

The first step is to add meat to the navbar template. Replace the content with this:

<div class="navigation">
  <ul>
    <li><a href="#" class="heading">SpeakerHang</a></li>
    <div class="inline align-right">
      <li><a ui-sref="speakers">Speakers</a></li>
      <li><a ui-sref="addspeaker">Add A Speaker</a></li>
    </div>
  </ul>
</div>
<hr>

Next, head over to app.js to import the navbar component.

app/app.js

...
import NavBarComponent from './components/navbar/navbar';
...

angular.module('app', [
    uiRouter,
    NavBarComponent
  ])
  .config(.....)
  .component('app', AppComponent);

We have imported the navbar component and registered it with our app's module. However, we need to take one more step to make sure it reflects on the page.

Call the navbar component in app/app.html.

app/app.html

<!-- Place all UI elements intended to be present across all routes in this file -->
<div class="container">
  <navbar></navbar>
  <div ui-view></div>
</div>

Your app should show the navbar now. Awesome!!!

Speaker List Component

Let's create the speaker list component. Run the component creation command in the terminal like so:

gulp component --name speakerlist

Now, we have the speakerlist components folder. Take a brief pause here.

The speaker list component should list all the speakers on the platform. The question now is, Where are the speakers?, Where will that data come from?

In a production app, the data will be made available to our app from an API. In the absence of an API, we'll simply make use of an array and use a service.

Let's create the speaker service. Create a new folder, services, inside the app directory. And create a speakerservice.js file inside the services folder.

Add code to the file like so:

services/speakerservice.js

function SpeakerService() {
  "ngInject";

  const speakers = [
    {
        id: 99,
        name: "Prosper Otemuyiwa",
        favConf: "Ruby Conf 2016",
        noOfConf: 13,
        favProgrammingQuote: "A C program is like a fast dance on a newly waxed dance floor by people carrying razors."
    },
    {
        id: 100,
        name: "Funsho Okubanjo",
        favConf: "Laracon 2016",
        noOfConf: 7,
        favProgrammingQuote: "Don’t worry if it doesn’t work right. If everything did, you’d be out of a job."
    },
    {
        id: 101,
        name: "Chilezie Unachukwu",
        favConf: "ReactRally 2017",
        noOfConf: 19,
        favProgrammingQuote: "Good design adds value faster than it adds cost"
    },
    {
        id: 102,
        name: "Damilola Adekoya",
        favConf: "Codefest Russia 2012",
        noOfConf: 25,
        favProgrammingQuote: "Talk is cheap. Show me the code."
    },
  ];

  return {

    // Will retrieve our speakers list for displaying
    getAllSpeakers() {
      return speakers;
    },

    // Creating a Speaker entry based on user input.
    addASpeaker(speaker) {
      const {name, favConf, noOfConf, favProgrammingQuote } = speaker;

      const newSpeaker = { name, favConf, noOfConf, favProgrammingQuote };

      speakers.push(tempSpeaker);
    }
  }
}

export default SpeakerService;

In the service above, we have a factory with two functions, getAllSpeakers responsible for returning a list of speakers and addASpeaker, responsible for adding a speaker.

Now, let's get back to the speakerlist component. Open up app/components/speakerlist/speakerlist.component.js file.

In the speakerlistComponent object, we have bindings, template, controller, and restrict.

...
let speakerlistComponent = {
  restrict: 'E',
  bindings: {},
  template,
  controller
};
...
  • restrict - This means the component should be restricted to an element.
  • bindings - This helps specify the binding option whether it's a one-way or two-way data binding. It's represented by a symbol. E.g < represents a one-way data binding.
  • template - This is where we specify the view for the component.
  • controller - This is where we specify the controller which holds the logic for the component. By default, it ships with $ctrl as an alias that can be used in the view to invoke controller methods.
  • controllerAs- This allows developers to specify their alias, e.g vm as commonly used in a lot of apps.

Update the speakerlistComponent object to have a controllerAs key and value like so:

...
let speakerlistComponent = {
  restrict: 'E',
  bindings: {},
  template,
  controller,
  controllerAs: 'vm'
};
...

Open up app/components/speakerlist/speakerlist.controller.js and modify it like so:

class SpeakerlistController {
  constructor(SpeakerService) {
    "ngInject";

    // This will keep the service instance across our class
    this.SpeakerService = SpeakerService;


    this.speakers = [];
  }

    // This method will be called each time the component will be initialised,
    // In our case, it will be called for every page route change.
  $onInit() {
    this.speakers = this.SpeakerService.getAllSpeakers();
  }
}

export default SpeakerlistController;

In the code above, we injected the SpeakerService into the controller and assigned it to an instance variable.

The $onInit() lifecycle hook was invoked to initialize the speakers' array with the list of speakers from the speaker service. The $onInit() hook is good for a controller's initialization code.

Update the speaker view, speakerlist.html, to have the necessary code to display the list of speakers.

speakerlist.html

<section>
  <div class="row">
    <div class="row" ng-repeat="speaker in vm.speakers">
      <div class="four columns">
        <label for="control-label col-sm-2">Speaker Name:</label>
        <input class="u-full-width" type="text" placeholder="{{ speaker.name }}" readonly>
      </div>
      <div class="four columns">
        <label for="control-label col-sm-2">No. Of Conferences Attended: </label>
        <input class="u-full-width" type="text" placeholder="{{ speaker.noOfConf }}" readonly>
      </div>
      <div class="four columns">
        <label for="favConference">Favorite Conference So Far:</label>
        <input class="u-full-width" type="number" placeholder="{{ speaker.favConf }}" readonly>
      </div>
      <label for="FavoriteProgrammingQuote">Favorite Programming Quote:</label>
      <textarea class="u-full-width" placeholder="{{ speaker.favProgrammingQuote }}" readonly></textarea>
      <hr/>
    </div>
  </div>
</section>

Now, we need to import the speaker list component in the app.js file and define a route for /speakers.

Open up app/app.js, import the speakerlist component and define the /speakers route.

...
import SpeakerListComponent from './components/speakerlist/speakerlist';
...
angular.module('app', [
    uiRouter,
    NavBarComponent,
    SpeakerListComponent
  ])
  .config(...
    ...
    // Speaker page to contain list of speakers
    .state('speakers', {
      url: '/speakers',
      template: '<speaker-list></speaker-list>'
    })
    ...

    )
  .component('app', AppComponent);

There is one more step before we can see the list of speakers on the page. Open up app/app.js and import the speaker service.

...
import SpeakerService from './services/SpeakerService';
...

Now, call the .factory method on the angular module and reference the speaker service like so:

app/app.js

...
angular.module('app', [
    uiRouter,
    NavBarComponent,
    SpeakerListComponent,
    AddSpeakerComponent,
  ])
  .config(...) => {
    ...
    })
  .component('app', AppComponent)
  .factory('SpeakerService', SpeakerService);

Note: Make sure it is speakerList, not speakerlist in .component('speakerList', speakerlistComponent) section of the app/components/speakerlist/speakerlist.js file.

Your app should look like this now:

SpeakerHang - List of speakers SpeakerHang - List of speakers

Add Speaker Component

Let's create the add speaker component. Run the component creation command in the terminal like so:

gulp component --name addspeaker

The addspeaker component is the container for the forthcoming speakerform component.

Head over to app/app.js and import the addspeaker component. We'll also add the route for /add-speaker.

...
import AddSpeakerComponent from './components/addspeaker/addspeaker';
...
angular.module('app', [
    uiRouter,
    NavBarComponent,
    SpeakerListComponent,
    AddSpeakerComponent,
  ])
  .config(($locationProvider, $stateProvider, $urlRouterProvider) => {
    ...
     //Create route for our goat listings creator
    .state('addspeaker', {
        url: '/add-speaker',
        template: '<add-speaker></add-speaker>'
    });

   // Default page for the router
   $urlRouterProvider.otherwise('/home');
  })
  .component('app', AppComponent)
  .factory('SpeakerService', SpeakerService);

Click on the Add A Speaker link on the navbar. You'll be directed to the /addspeaker route. Right now, it's empty but everything is working perfectly.

Note: We need to add a speaker form to the template of the addspeaker component. We'll come back to it once we are done with creating the speaker form component.

Speaker Form Component

Let's create the speaker form component. Run the component creation command in the terminal like so:

gulp component --name speakerform

Open up app/components/speakerform/speakerform.html and add the form code to it like so:

<form role="form" ng-submit="vm.addSpeaker()">
  <div class="row">
    <div class="six columns">
        <label for="control-label col-sm-2">Speaker Name:</label>
        <input class="u-full-width" type="text" placeholder="John Doe" ng-model="vm.speaker.name">
    </div>
    <div class="six columns">
        <label for="control-label col-sm-2">No. Of Conferences Attended:</label>
        <input class="u-full-width" type="number" placeholder="25" ng-model="vm.speaker.noOfConf">
    </div>
  </div>

  <label for="favConference">Favorite Conference So Far:</label>
  <input class="u-full-width" type="text" placeholder="All Superpower Dev Conf 2050" ng-model="vm.speaker.favConf">

  <label for="FavoriteProgrammingQuote">Favorite Programming Quote</label>
  <textarea class="u-full-width" placeholder="Organizational Skills Beat Algorithmic Wizadry - James Hague" ng-model="vm.speaker.favProgrammingQuote"></textarea>
  
  <input class="button-primary" type="submit" value="Submit">
</form>

In the code above, we have the addSpeaker() function that will be invoked when the form is submitted. The data of each input type will be submitted to the speaker object, made possible via ng-model.

Let's add the controller code to app/components/speakerform/speakerform.component.js file.

speakerform.component.js

class SpeakerformController {
  constructor($state, SpeakerService) {
    "ngInject";

    this.$state       = $state;
    this.SpeakerService = SpeakerService;

    this.speaker = {};
  }

  // will handle the form submission,
  // validates the required field and then adds the goat to the service.
  // once added, we'll be directed to the next page.
  addSpeaker() {
    if(!this.speaker.name) return alert('Speaker Name is Required');
    if(!this.speaker.noOfConf) return alert('No. Of Conferences Attended is required');
    if(!this.speaker.favConf) return alert('Favorite Conference So Far is required');
    if(!this.speaker.favProgrammingQuote) return alert('Favorite Programming Quote is required');

    this.SpeakerService.addASpeaker(this.speaker);

    // reset the form
    this.speaker = {};

    // go to home page, to see our entry
    this.$state.go('speakers');
  }
}

export default SpeakerformController;

In the code above, we injected the state and SpeakerService into the controller. The addSpeaker() function collects the values of the form and sends them to the addSpeaker function.

Quickly add the controllerAs key with the value of vm to the speakerform component.

app/components/speakerform/speakerform.component.js

import template from './speakerform.html';
import controller from './speakerform.controller';
import './speakerform.scss';

let speakerformComponent = {
  restrict: 'E',
  bindings: {},
  template,
  controller,
  controllerAs: 'vm'
};

export default speakerformComponent;

Now, go ahead and import the speakerform component in addspeaker.js like so:

import angular from 'angular';
import addspeakerComponent from './addspeaker.component';
import speakerForm from '../speakerform/speakerform';

let addspeakerModule = angular.module('addspeaker', [
  speakerForm
])

.component('addSpeaker', addspeakerComponent)

.name;

export default addspeakerModule;

Note: Make sure it is speakerForm, not speakerform in .component('speakerForm', speakerformComponent) section of the app/components/speakerform/speakerform.js file.

One more thing, remember the app/components/addspeaker/addspeaker.html file? It's time to add the <speaker-form> component to it.

app/components/addspeaker/addspeaker.html

<div>
  <speaker-form></speaker-form>
</div>

Try to add a speaker now. The speaker form works and it adds a new speaker to the list of speakers. Yaay!!!

SpeakerHang - Add Speaker SpeakerHang - Add Speaker

Adding Authentication to Your AngularJS App

Aside: Authenticate an AngularJS App with Auth0

We can protect our applications and APIs so that only authenticated users can access them. Let's explore how to do this with an Angular application using Auth0. You can clone this sample app from the repo on GitHub.

Auth0 login screen

Sign Up for Auth0

You'll need an Auth0 account to manage authentication. You can sign up for a free account here. Next, set up an Auth0 application and API so Auth0 can interface with an Angular app and Node API.

Set Up an Auth0 Application

  1. Go to your Auth0 Dashboard and click the "create a new application" button.
  2. Name your new app and select "Single Page Web Applications".
  3. In the Settings for your new Auth0 app, add http://localhost:3000/callback to the Allowed Callback URLs. Click the "Save Changes" button.
  4. If you'd like, you can set up some social connections. You can then enable them for your app in the Application options under the Connections tab. The example shown in the screenshot above utilizes username/password database, Facebook, Google, and Twitter. For production, make sure you set up your own social keys and do not leave social connections set to use Auth0 dev keys.

Note: Under the OAuth tab of Advanced Settings (at the bottom of the Settings section) you should see that the JsonWebToken Signature Algorithm is set to RS256. This is the default for new applications. If it is set to HS256, please change it to RS256. You can read more about RS256 vs. HS256 JWT signing algorithms here.

Dependencies and Setup

Once you've cloned the project, install the dependencies for both the AngularJS app and the Node server by running the following commands in the root of your project folder:

$ npm install

Start the app via the express server with:

$ npm start

Find the auth0-variables.js.example file and remove the .example extension from the filename. Then open the file:

var AUTH0_CLIENT_ID='{CLIENT_ID}'; 
var AUTH0_DOMAIN='{DOMAIN}'; 
var AUTH0_CALLBACK_URL='http://localhost:3000/callback';

Change the AUTH0_DOMAIN identifier to your Auth0 application domain.

Note: To learn more about RS256 and JSON Web Key Set, read Navigating RS256 and JWKS.

Change the AUTH0_CLIENT_ID to your Auth0 information.

let's take a look at how authentication is implemented.

Authentication Service

Authentication logic on the front end is handled with an AuthService authentication service: src/app/auth/auth.service.js file.

(function () {

  'use strict';

  angular
    .module('app')
    .service('authService', authService);

  authService.$inject = ['$state', 'angularAuth0', '$timeout'];

  function authService($state, angularAuth0, $timeout) {

    function login() {
      angularAuth0.authorize();
    }

    function handleAuthentication() {
      angularAuth0.parseHash(function(err, authResult) {
        if (authResult && authResult.accessToken && authResult.idToken) {
          setSession(authResult);
          $state.go('home');
        } else if (err) {
          $timeout(function() {
            $state.go('home');
          });
          console.log(err);
          alert('Error: ' + err.error + '. Check the console for further details.');
        }
      });
    }

    function setSession(authResult) {
      // Set the time that the access token will expire at
      let expiresAt = JSON.stringify((authResult.expiresIn * 1000) + new Date().getTime());
      localStorage.setItem('access_token', authResult.accessToken);
      localStorage.setItem('id_token', authResult.idToken);
      localStorage.setItem('expires_at', expiresAt);
    }

    function logout() {
      // Remove tokens and expiry time from localStorage
      localStorage.removeItem('access_token');
      localStorage.removeItem('id_token');
      localStorage.removeItem('expires_at');
      $state.go('home');
    }

    function isAuthenticated() {
      // Check whether the current time is past the 
      // access token's expiry time
      let expiresAt = JSON.parse(localStorage.getItem('expires_at'));
      return new Date().getTime() < expiresAt;
    }

    return {
      login: login,
      handleAuthentication: handleAuthentication,
      logout: logout,
      isAuthenticated: isAuthenticated
    }
  }
})();

The login() method authorizes the authentication request with Auth0 using your config variables. A login page will be shown to the user and they can then log in.

  • handleAuthentication: looks for the result of authentication in the URL hash. Then, the result is processed with the parseHash method from auth0.js
  • setSession: sets the user's Access Token and ID Token, and the Access Token's expiry time
  • logout: removes the user's tokens and expiry time from browser storage
  • isAuthenticated: checks whether the expiry time for the user's Access Token has passed

Note: If it's the user's first visit to our app and our callback is on localhost, they'll also be presented with a consent screen where they can grant access to our API. A first party client on a non-localhost domain would be highly trusted, so the consent dialog would not be presented in this case. You can modify this by editing your Auth0 Dashboard API Settings. Look for the "Allow Skipping User Consent" toggle.

Login Control

Provide a component with controls for the user to log in and log out.

app/navbar/navbar.html

<nav class="navbar navbar-default">
  <div class="container-fluid">
    <div class="navbar-header">
      <a class="navbar-brand" href="#">Auth0 - AngularJS</a>
      <button
        class="btn btn-primary btn-margin"
        ui-sref="home">
          Home
      </button>
      <button
        class="btn btn-primary btn-margin"
        ng-if="!vm.auth.isAuthenticated()"
        ng-click="vm.auth.login()">
          Log In
      </button>
      <button
        class="btn btn-primary btn-margin"
        ng-if="vm.auth.isAuthenticated()"
        ng-click="vm.auth.logout()">
          Log Out
      </button>
    </div>
  </div>
</nav>

Directive

// app/navbar/navbar.directive.js

(function() {

  'use strict';

  angular
    .module('app')
    .directive('navbar', navbar);

  function navbar() {
    return {
      templateUrl: 'app/navbar/navbar.html',
      controller: navbarController,
      controllerAs: 'vm'
    }
  }

  navbarController.$inject = ['authService'];

  function navbarController(authService) {
    var vm = this;
    vm.auth = authService;
  }

})();

Depending on whether the user is authenticated or not, they see the Log Out or Log In button. The ng-click events on the buttons make calls to the authService service to let the user log in or out. When the user clicks Log In, they are redirected to the login page.

Callback Component

The callback component is where the app is redirected after authentication. This component simply shows a loading message until the login process is completed. After the session is set up, the users are redirected to the /home route.

// app/callback/callback.controller.js

(function () {

  'use strict';

  angular
    .module('app')
    .controller('CallbackController', callbackController);

  function callbackController() {}

})();

app/callback/callback.html

<div class="loading">
  <img src="assets/loading.svg" alt="loading">
</div>

Process Authentication Result

When a user authenticates at the login page, they are redirected to your application. Their URL contains a hash fragment with their authentication information. The handleAuthentication method in the authService service processes the hash.

Call the handleAuthentication method in your app's run block. The method processess the authentication hash while your app loads.

// app/app.run.js

(function () {

  'use strict';

  angular
    .module('app')
    .run(run);

  run.$inject = ['authService'];

  function run(authService) {
    // Handle the authentication
    // result in the hash
    authService.handleAuthentication();
  }

})();

More Resources

That's it! We have an authenticated AngularJS application with login, logout, and protected routes. To learn more, check out the following resources:

Conclusion

AngularJS 1.5+ provides a component-based architecture to build apps similar to React and Angular. In this tutorial, you've learned how to build and secure an app using the .component method and the new APIs included in the framework.

In addition, Auth0 can help secure your AngularJS apps with more than just username-password authentication. It provides features like multifactor auth, anomaly detection, enterprise federation, single sign on (SSO), and more. Sign up today so you can focus on building features unique to your app.

Auth0 provides simple and easy to use interfaces to help administrators manage user identities including password resets, as well as creating, provisioning, blocking and deleting users.