Build an Angular 1.5 application with ES6 and Components

Introduction

  Tweet this course  —  we     you!

Angular 1.5 is a new version of Angular that introduces Angular2's core paradigms to existing Angular applications, namely ES6 classes and components. This will allow you to start writing code that will be much easier to port to Angular2 whilst still building features in your existing Angular codebases. This course will teach you everything you need to know about Angular 1.5 and best practices to ensure you're writing clean & modular code that can be upgraded to Angular2 with ease. In an upcoming course we will take this codebase and build it in Angular2, and we also have a corresponding guidebook for migrating Angular 1 apps to Angular2 that is based on our experience with this tutorial and the upcoming Angular2 tutorial.

What this course will cover

The new changes in Angular 1.5 and relevant portions of the Angular Style Guide will be the focus of our learning, including but not limited to:

  • ES6 class support for services and controllers
  • Angular's new component API
  • Using a task runner to transform and compile your code
  • Proper codebase structuring
  • Best practices for writing Angular2 compatible code

We feel that learning is best accomplished by "doing", and as such, throughout this course we will be creating a production ready Medium.com clone called "Conduit" to demonstrate & apply these learnings. You can view a live demo of the application here. Conduit is a fully featured social blogging site including:

  • Authentication with JWT
  • Profiles with images
  • Write/edit/read articles
  • Comments on articles
  • Ability to "favorite" articles
  • Ability to follow other users & have their articles show up in your feed

For your convenience, we have a hosted API that you can build your application against. We're also going to release courses for how to create the backend in either Node, Rails, or Django over the next two weeks.

Prerequisites

Use checkboxes to save your progress
Exposure to Angular and Javascript. If you haven't created an Angular application before, you might want to start by going through our super beginner friendly MEAN stack tutorial first.
Have Node.js and npm installed on your computer. If you don't, you can follow these instructions.
General understanding of ES6. If you haven't heard of ES6 yet, it's basically new syntax and features for Javascript. The most commonly used parts for this course (as well as day-to-day development) are import/export, class, let and const, and arrow functions. There are lots of other changes as well; here's a comprehensive list detailing them.
Knowledge of component-based development and web components. Components allow for a clear separation of concerns and allow you to build highly reusable code. Angular2 (and other frameworks like React and Ember) have adopted them, and this excerpt from our course on React does an excellent job of explaining what they're about.

Setting up a boilerplate project for Angular 1.5 & ES6

Go ahead and clone the seed branch of this repo by running git clone -b 00-seed git@github.com:gothinkster/angularjs-realworld-example-app.git and then run npm install.

Understanding the Gulp build system

Lets take a moment to see what's going on under the hood. We'll be using a Gulp and a handful of Gulp plugins to perform various tasks that will build our application for us. For this course, we chose Gulp instead of other alternatives (like Webpack, etc) because:

  • It's commonly used for Angular 1.x apps. This will make it easier to find answers to any questions you may have
  • Easily extensible. It's fairly straightforward to write your own build tasks
  • The Browserify tasks that gulp invokes (described below) can easily be ported over to Rails, Django, Node, etc projects if needed
  • Lightweight

It's worth noting that we considered choosing Webpack but decided against it because it's significantly more complicated and harder to customize. Gulp will likely be easier to integrate into your existing Angular applications as well considering its inherent flexibility.

If you look at our gulpfile, there are a few key tasks being run that are worth noting — we've outlined them below.

Bundling & modularizing all JS files

This allows us to use ES6's import/export functionality. It also dumps out a single JS file for the browser to download instead of having to include every file individually in your HTML file.

What we're using to do it: Browserify. It's very flexible compared to alternatives like Webpack (which is mostly popular with React developers, although Angular2 devs are starting to adopt it as well).

Transpiling ES6

Transpiling allows us to use ES6 features even if browsers don't support it yet.

What we're using to do it: Babel. It's by far the most popular tool for the job and highly configurable.

Gathering templates & injecting them into Angular's $templateCache

The browser will automatically get the entire application's templates on page load, omitting the need to request templates from the server. You can learn more about how $templateCache works here.

Run a local server to show us live changes while we develop our application

We decided to use BrowserSync. LiveReload would've also been a good choice, but BrowserSync has a handful of useful features that make it more attractive.

Exploring the base application

In the video above we explored the initial codebase and familiarized ourselves with its layout. Feel free to play around with the various files and get a feel for how things have been set up. The entry point for the application is app.js, which then includes all of the other javascript files.

Once you've had a chance to poke around a bit, lets go ahead and get started building our first bits of functionality!

  Tweet   “Just learned how to set up a boilerplate project for Angular 1.5 + ES6 from @GoThinkster 💪”

Building our first pages with ES6 controllers

When building a new application that involves users registering/logging in, you typically start off by building authentication functionality first. Welding on authentication after you've built out a lot of features can be tedious and lead to confusing code, as you'll have to restructure a lot of your existing code to accomodate it. Instead, lets build all of our authentication related functionality up front.

To do this, we'll need to:

  • Create login/register pages
  • Send login/register requests to our server & handle the response
  • Display errors
  • Store the user's auth info (JWT token) to ensure they stay logged in
  • Ensure users can/cannot access pages if they're logged in/out

Lets start by building our first two pages: login and register. Since they share a similar template we'll create a single HTML file that will be used for both pages.

Create a folder in /src/js/ called auth. This is where we'll be creating all of our functionality for the login & register pages. Inside of the auth folder, create a new file called auth.html with the template below.

auth/auth.html

<div class="auth-page">
  <div class="container page">
    <div class="row">

      <div class="col-md-6 offset-md-3 col-xs-12">
        <h1 class="text-xs-center">Register</h1>
        <p class="text-xs-center">
          <a href="">
            Have an account?
          </a>
        </p>

        <form>
          <fieldset>

            <fieldset class="form-group">
              <input class="form-control form-control-lg"
                type="text"
                placeholder="Username"
                ng-model="$ctrl.formData.username" />
            </fieldset>

            <fieldset class="form-group">
              <input class="form-control form-control-lg"
                type="email"
                placeholder="Email"
                ng-model="$ctrl.formData.email" />
            </fieldset>

            <fieldset class="form-group">
              <input class="form-control form-control-lg"
                type="password"
                placeholder="Password"
                ng-model="$ctrl.formData.password" />
            </fieldset>

            <button class="btn btn-lg btn-primary pull-xs-right"
              type="submit">
              Register
            </button>

          </fieldset>
        </form>
      </div>

    </div>
  </div>
</div>

Notice that we have three inputs (username, email, and password) that are bound via ng-model to an object called $ctrl.formData. If you've ever worked in Angular code before this shouldn't look unfamiliar except for the $ctrl variable, which normally would be $scope or some other name you define with controllerAs. We'll get to this in a second when we create the controller for this template!

We want the auth/auth.html template to show up when you go to either /#/login or /#/register, so we'll need to define new UI-Router states to do this.

Create a new config file in the auth folder called auth.config.js that defines a state for both the login page and the register page

auth/auth.config.js

function AuthConfig($stateProvider, $httpProvider) {
  'ngInject';

  // Define the routes
  $stateProvider

  .state('app.login', {
    url: '/login',
    templateUrl: 'auth/auth.html',
    title: 'Sign in'
  })

  .state('app.register', {
    url: '/register',
    templateUrl: 'auth/auth.html',
    title: 'Sign up'
  });

};

export default AuthConfig;
To make it easy to import all of our auth functionality in app.js, lets create an index.js file in the auth folder that includes our current functionality (just the config file) and attaches it to an Angular module called app.auth

auth/index.js

import angular from 'angular';

// Create the home module where our functionality can attach to
let authModule = angular.module('app.auth', []);


// Include our UI-Router config settings
import AuthConfig from './auth.config';
authModule.config(AuthConfig);


export default authModule;
The last thing we have to do is include the auth index file in app.js and declare app.auth in the requires variable

app.js

[...]
import './services';
+ import './auth';

const requires = [
  'ui.router',
  'templates',
  'app.layout',
  'app.components',
  'app.home',
  'app.profile',
  'app.article',
  'app.services',
+  'app.auth'
];

[...]

Now if you navigate to /#/login and /#/register you should see the HTML contained in the auth/auth.html file!

Lets create a controller for the login and register pages. Since the functionality between them is so similar, lets create a single controller. We'll need to determine if the current state is app.login or app.register.

Create a new controller using the ES6 class syntax in auth/auth.controller.js. Have the constructor function set a title and authType property that are retrieved from UI-Router's current state. We call replace('app.', '') on $state.current.name to strip out the "app." prefix of the UI-Router state; this is just for our own convenience when we access the authType in our template.

auth/auth.controller.js

class AuthCtrl {
  constructor($state) {
    'ngInject';

    this.title = $state.current.title;
    this.authType = $state.current.name.replace('app.', '');
  }

}


export default AuthCtrl;
We need to include this controller in auth/index.js

auth/index.js

[...]

+ // Controllers
+ import AuthCtrl from './auth.controller';
+ authModule.controller('AuthCtrl', AuthCtrl);

export default authModule;

Lets take a moment to talk about the $ctrl variable. With controllerAs, you were allowed to choose the scope's variable name. "vm" (short for "view model") is/was a popular choice. We've chosen $ctrl instead for two reasons:

  1. It's the default controllerAs name for the new component API (which we'll cover in a minute)
  2. It looks visually similar to $scope

While initially it may be tempting to use controller specific names (i.e. 'LoginVM', etc), there's no benefit in using different controllerAs variable names across your application. In fact, it will make it harder to upgrade to Angular2 as you'll likely want to do a simple find & remove for all controllerAs variable instances considering Angular2 templates don't require a reference variable.

Add controllerAs for AuthCtrl to the login & register states in auth/auth.config.js

auth/auth.config.js

function AuthConfig($stateProvider, $httpProvider) {
  'ngInject';

  // Define the routes
  $stateProvider

  .state('app.login', {
    url: '/login',
+    controller: 'AuthCtrl as $ctrl',
    templateUrl: 'auth/auth.html',
    title: 'Sign in'
  })

  .state('app.register', {
    url: '/register',
+    controller: 'AuthCtrl as $ctrl',
    templateUrl: 'auth/auth.html',
    title: 'Sign up'
  });

};

export default AuthConfig;

Now that our controller is wired up, we want to update our template to show the proper name of the page ("Sign in" or "Sign up"). We'll also include a link to /#/register on /#/login, and vice versa.

Update the H1 tag to be bound to title from our controller, then add in the two links that allow the user to navigate to /#/register from /#/login & vice versa.

auth/auth.html

[...]
-        <h1 class="text-xs-center">Register</h1>
+        <h1 class="text-xs-center" ng-bind="::$ctrl.title"></h1>
-        <p class="text-xs-center">
-          <a href="">
-            Have an account?
-          </a>
-        </p>
+        <p class="text-xs-center">
+          <a ui-sref="app.login"
+            ng-show="$ctrl.authType === 'register'">
+            Have an account?
+          </a>
+          <a ui-sref="app.register"
+            ng-show="$ctrl.authType === 'login'">
+            Need an account?
+          </a>
+        </p>
[...]

You may have noticed the :: in ng-bind="::$ctrl.title" — that's the syntax for a one time binding. If you update the title variable in the controller via user input, a timeout, etc it won't be reflected in the view. It's a lot of work for Angular to keep track of variable changes when they're bound two ways, so whenever you don't need two way data binding, use a one time binding. The title won't change at all after the page loads, so this is a good time to use one.

The other thing you may have noticed is the attribute ui-sref on the login and register <a> tags. Instead of having to type out /#/register or /#/login, we just pass along the name of the state and it automatically populates the link's href attribute with the correct URL for us. This is especially useful when you change your routes (i.e. changing /#/register to /#/create_account) - instead of having to go through your entire codebase to reflect this change, you simply update the relevant UI-Router configuration and ui-sref will change all links accordingly. Pretty cool!

We need this form to submit data through our controller, so lets modify the form to enable this.

Declare a method for submitting the form, and a variable that can disable the form inputs by using ng-disabled on the root fieldset. Also, hide the username field when on register page, as we only need it for signup (login only requires email/password). Finally, change the button at the bottom to be bound to the title name as well ("Sign in", "Sign up").

auth/auth.html

[...]
-        <form>
+        <form ng-submit="$ctrl.submitForm()">
-          <fieldset>
+          <fieldset ng-disabled="$ctrl.isSubmitting">

-            <fieldset class="form-group">
+            <fieldset class="form-group" ng-show="$ctrl.authType === 'register'">
              <input class="form-control form-control-lg"
                type="text"
                placeholder="Username"
                ng-model="$ctrl.formData.username" />
            </fieldset>

            [...]

            <button class="btn btn-lg btn-primary pull-xs-right"
              type="submit"
+              ng-bind="$ctrl.title">
-              Register
            </button>

          </fieldset>
        </form>
[...]

The page should now show the correct title & show the link to login or logout, respectively.

The last thing we need to do to wire the template's form submission to the controller. We'll do this by creating the submitForm method that the template is calling using ng-submit from <form ng-submit="$ctrl.submitForm()">.

Add a submitForm method that disables the form and outputs the contents of formData to console.log

auth/auth.controller.js

class AuthCtrl {
  constructor($state) {
    'ngInject';

    this.title = $state.current.title;
    this.authType = $state.current.name.replace('app.', '');
  }

+  submitForm () {
+    this.isSubmitting = true;
+
+    console.log(this.formData);
+  }

}

export default AuthCtrl;

Excellent! If you fill in the form and then click submit, your browser console should output an object containing the fields & data you entered.

  Tweet   “Just built an ES6 controller in Angular 🙌 ”

Interacting with a server using an ES6 service

Now that we're receiving the form's data, we need a way to actually send it up to the server. For this we'll create an Angular service that can perform this functionality and is available throughout our application.

Services in Angular are used to share data & functionality between controllers. If you've used Angular before, you've probably used "factories" more than you've used services. The difference between the two is pretty straightforward:

Factories: you create an object, add properties to it, then return that same object. When you pass this service into your controller, those properties on the object can now be accessed in that controller via your factory.

Services: it's instantiated with the ‘new’ keyword. Because of that, you'll add properties to ‘this’ (like we've been doing in this course) and the service will return ‘this’. When you pass the service into your controller, the properties on ‘this’ can now be accessed in that controller via your service.

Without the nice class syntax that ES6 provides, most developers opted to just create an object with factories instead of using services. However, with ES6 we can now create classes with ease! Lets create our first service using an ES6 class.

Creating a User service

For functionality that will be reused across the entire application it makes sense to have dedicated folders to hold those relevant files. As such, all of our services are stored in the services/ folder per the Angular styleguide.

Create a new file in the services folder called user.service.js and populate it with an empty class

services/user.service.js

export default class User {
  constructor() {
    'ngInject';

  }

}

The User service currently needs to do two things: make login/register requests via $http, and store the currently logged in user's info. We will need to access the API URL of our server from config/app.constants.js which are set to a constant named "AppConstants".

Inject AppConstants and the $http service into the User class. Then, attach an object to this that will hold our current user's info called current. When the service is first initialized there won't be a user logged in, so set the initial value to null
export default class User {
-  constructor() {
+  constructor(AppConstants, $http) {
    'ngInject';

+    // Object to store our user properties
+    this.current = null;
  }

}

The next thing we need to do is add a method for attempting authentication. There's just one "gotchya" - we can't access AppConstants or $http outside of the constructor function, as they are provided as arguments to the constructor function and are not saved anywhere for later use (which is what we need them for).

This is different from how you typically access injected services in factories, so don't worry if you're a bit confused! The answer is very straightforward - simply create a reference to the service object on this inside the constructor function.

Add references to AppConstants and $http inside the constructor function. While it's not required, it is considered best practice to add the _ before the variable name on this to ensure you don't get variable names mixed up - that way you know that anything starting with an _ is a service. This is also best practice for how you'll access services in Angular2.
export default class User {
  constructor(AppConstants, $http) {
    'ngInject';

+    this._AppConstants = AppConstants;
+    this._$http = $http;

    // Object to store our user properties
    this.current = null;
  }

}

To access an injected service outside of the constructor function, you simply call this._serviceName (i.e. this._$http()). With this, lets create a method called attemptAuth that will either login or register a user.

Note: If you want to use our public API to build against, make sure you update the app constants API url with https://conduit.productionready.io/api!

Create the attemptAuth method. It will accept two parameters: type (accepts 'login' or 'register') and credentials (an object containing the username, password, and email fields). Per the API docs, the route for registering is a POST request to /api/users and login is a POST request to /api/users/login (the /api/ is provided in the AppConstants variable). Both routes require an object with the key user that contains keys for email, password (and username when registering). If the attempt is successful, store the returned user object in this.current in the .then promise success callback.
export default class User {
  constructor(AppConstants, $http) {
    'ngInject';

    this._AppConstants = AppConstants;
    this._$http = $http;

    // Object to store our user properties
    this.current = null;
  }

+  // Try to authenticate by registering or logging in
+  attemptAuth(type, credentials) {
+    let route = (type === 'login') ? '/login' : '';
+    return this._$http({
+      url: this._AppConstants.api + '/users' + route,
+      method: 'POST',
+      data: {
+        user: credentials
+      }
+    }).then(
+      // On success...
+      (res) => {
+        // Store the user's info for easy lookup
+        this.current = res.data.user;
+
+        return res;
+      }
+    );
+  }

}

What is let? It's a new ES6 syntax for declaring variables - it's basically the same thing as var except it will do block scoping, which is super nice because the lack of block scoping with var often causes a lot of unexpected bugs.

What is this (res) => {} madness? It's a new feature of ES6 called arrow functions. In the past you would've been forced to write function(res) {} which would have created a new function scope, which would break this.current = res.data.user since this would no longer be tied to the User class. Further, it's a lot shorter & prettier to write out!

Include & declare the Angular User service

services/index.js

import angular from 'angular';

// Create the module where our functionality can attach to
let servicesModule = angular.module('app.services', []);

+ // Services
+ import UserService from './user.service';
+ servicesModule.service('User', UserService);

export default servicesModule;

Check to make sure your console isn't reporting any errors! If it isn't, that means that we should be ready to integrate the User attemptAuth method into our Auth controller.

Calling the service method in the controller

Add the User service & invoke User.attemptAuth within the submitForm method. User.attemptAuth returns a promise and the .then method accepts a success callback and an error callback. Lets just log the success data, and we'll log the errors object that the API always returns when something goes wrong.

auth/auth.controller.js

class AuthCtrl {
-  constructor($state) {
+  constructor(User, $state) {
    'ngInject';

+    this._User = User;

    this.title = $state.current.title;
    this.authType = $state.current.name.replace('app.', '');
  }

  submitForm () {
    this.isSubmitting = true;
-    console.log(this.formData);

+    this._User.attemptAuth(this.authType, this.formData).then(
+      // Callback for success
+      (res) => {
+        this.isSubmitting = false;
+        console.log(res);
+      },
+      // Callback for failure
+      (err) => {
+        this.isSubmitting = false;
+        console.log(err.data.errors);
+      }
+    );
  }

}

export default AuthCtrl;

If you try registering and/or logging in, you should see the success data or errors showing up in your console. We'll want to show errors to the user above the form so they know if anything went wrong, so lets go ahead and iterate over any error messages using ng-repeat.

We need to display the errors that come from the API's spec. To do this, add an ng-repeat that iterates over the fields that contained errors, and another ng-repeat that iterates over all of the errors that occurred with that specific field.

auth/auth.html

[...]
          <a ui-sref="app.register"
            ng-show="$ctrl.authType === 'login'">
            Need an account?
          </a>
        </p>

+        <ul class="error-messages" ng-show="$ctrl.errors">
+          <div ng-repeat="(field, errors) in $ctrl.errors">
+            <li ng-repeat="error in errors">
+              {{field}} {{error}}
+            </li>
+          </div>
+        </ul>

        <form ng-submit="$ctrl.submitForm()">
          <fieldset ng-disabled="$ctrl.isSubmitting">
[...]
Change the error callback to expose errors via the controllerAs variable ($ctrl)

auth/auth.controller.js

[...]
      // Callback for failure
      (err) => {
        this.isSubmitting = false;
-        console.log(err.data.errors);
+        this.errors = err.data.errors;
      }
[...]

When the server sends back errors they're now be displayed in a list in our view. Perfect!

  Tweet   “Boom! Got my Angular ES6 service wired up to the server 😎 ”

Using components & directives for reusable UI functionality

Listing out errors is something that we'll need to do in many different parts of our app, so wouldn't it be great if we could easily include this error listing functionality in our views without having to copy & paste the entire HTML snippet each time? Having a single HTML file would also make it easier to update in the future, as we could update all error handling functionality for the entire site by just changing one file.

This type of reusable UI functionality is exactly what components were made for. Lets create a reusable component that will list errors from an array.

Creating a reusable component for form errors

The simplest component you can make is just a template and a configuration object. When you want to control certain functionality in a component you'll typically need a controller as well, but considering we're just iterating over an array, we can get away with simply binding to a predefined array and expose that to the template.

Much like services, most components are meant to be used across many different parts of the app and thus should receive their own folder (in this case, that folder is /components).

In the /components folder, create a file called list-errors.component.js that defines a 2-way binding via "=" to an attribute called "errors". Also set the templateUrl to 'components/list-errors.html', which we'll be creating next.

components/list-errors.component.js

let ListErrors= {
  bindings: {
    errors: '='
  },
  templateUrl: 'components/list-errors.html'
};

export default ListErrors;
Create a new file called list-errors.html that is the exact same HTML we used in the auth.html file

components/list-errors.html

<ul class="error-messages" ng-show="$ctrl.errors">
  <div ng-repeat="(field, errors) in $ctrl.errors">
    <li ng-repeat="error in errors">
      {{field}} {{error}}
    </li>
  </div>
</ul>

Where is $ctrl being defined? When a controller isn't defined on a component, the default controller name is $ctrl. We've chosen to use this name for all other controllers as well because it looks similar to $scope and isn't easily confused with other variable names you'd typically use.

Add the list-errors component to the index.js file of the components folder. Note that the component name is defined as 'listErrors' and not 'list-errors'. This is because Angular directives and components require camelCase name definitions.

components/index.js

import angular from 'angular';

let componentsModule = angular.module('app.components', []);

+ // Components (and directives)
+ import ListErrors from './list-errors.component';
+ componentsModule.component('listErrors', ListErrors);

export default componentsModule;
The last thing we need to do is replace the ng-repeats in auth.html with our new component

auth/auth.html

[...]
          <a ui-sref="app.register"
            ng-show="$ctrl.authType === 'login'">
            Need an account?
          </a>
        </p>

-        <ul class="error-messages" ng-show="$ctrl.errors">
-          <div ng-repeat="(field, errors) in $ctrl.errors">
-            <li ng-repeat="error in errors">
-              {{field}} {{error}}
-            </li>
-          </div>
-        </ul>
+        <list-errors errors="$ctrl.errors"></list-errors>

        <form ng-submit="$ctrl.submitForm()">
          <fieldset ng-disabled="$ctrl.isSubmitting">
[...]

Awesome! We now have a reusable component that allows us to easily list out errors. The next thing we'll need to work on is allowing users to actually "log in" to our application.

Creating a directive to show/hide an element depending on auth state

Right now we don't have any way of showing if the request is successful - i.e. the user has no way of knowing if they're logged in or not. We'll need to show different parts of the UI when people are logged in (login/logout buttons, etc), so lets create a directive that allows us to programmatically show or hide HTML elements if the user is/isn't logged in.

Why a directive instead of a component? Directives are excellent when you want to invoke functionality on an existing HTML element via an attribute (i.e. <div hide-this-element="true">I'm going to be hidden programmatically</div>, whereas components are meant to be standalone HTML elements (i.e. <list-errors></list-errors>).

Since User.current is null when logged out, anything other than that means that they're logged in. Lets have a directive that shows contents when User.current is not null.

In the components folder, create a file called show-authed.directive.js that uses scope.$watch to observe changes to User.current in the link function. It also looks for the value of the show-authed="" attribute to determine if it should show or hide the element if they're logged in (true = show when authed, false = hide when authed).

components/show-authed.directive.js

function ShowAuthed(User) {
  'ngInject';

  return {
    restrict: 'A',
    link: function(scope, element, attrs) {
      scope.User = User;

      scope.$watch('User.current', function(val) {
          // If user detected
          if (val) {
            if (attrs.showAuthed === 'true') {
              element.css({ display: 'inherit'})
            } else {
              element.css({ display: 'none'})
            }

          // no user detected
          } else {
            if (attrs.showAuthed === 'true') {
              element.css({ display: 'none'})
            } else {
              element.css({ display: 'inherit'})
            }
          }
      });

    }
  };
}

export default ShowAuthed;
Include the show-authed directive in the index.js file (note that we're using camelCase again, since it's a directive)

components/index.js

[...]

// Components (and directives)
import ListErrors from './list-errors.component';
componentsModule.component('listErrors', ListErrors);

+ import ShowAuthed from './show-authed.directive';
+ componentsModule.directive('showAuthed', ShowAuthed);

export default componentsModule;

Sweet! The show-authed directive should now be available for use to use in any of our templates. Lets start by changing the header to show different options whether they're logged in or logged out.

Update header.html with a new UL that only displays for logged in users. Also, hide the other nav if the user is logged in and show links to the login and register pages instead of the dummy article and profile links.
<nav class="navbar navbar-light">
  <div class="container">

    <a class="navbar-brand"
      ui-sref="app.home"
      ng-bind="::$ctrl.appName | lowercase">
    </a>

    <!-- Show this for logged out users -->
    <ul class="nav navbar-nav pull-xs-right"
+      show-authed="false">

      <li class="nav-item">
        <a class="nav-link"
          ui-sref-active="active"
          ui-sref="app.home">
          Home
        </a>
      </li>

-      <li class="nav-item">
-        <a class="nav-link"
-          ui-sref-active="active"
-          ui-sref="app.article">
-          Article
-        </a>
-      </li>
-
-      <li class="nav-item">
-        <a class="nav-link"
-          ui-sref-active="active"
-          ui-sref="app.profile">
-          Profile
-        </a>
-      </li>

+      <li class="nav-item">
+        <a class="nav-link"
+          ui-sref-active="active"
+          ui-sref="app.login">
+          Sign in
+        </a>
+      </li>
+
+      <li class="nav-item">
+        <a class="nav-link"
+          ui-sref-active="active"
+          ui-sref="app.register">
+          Sign up
+        </a>
+      </li>

    </ul>

+    <!-- Show this for logged in users -->
+    <ul show-authed="true"
+      class="nav navbar-nav pull-xs-right">
+
+      <li class="nav-item">
+        <a class="nav-link"
+          ui-sref-active="active"
+          ui-sref="app.home">
+          Home
+        </a>
+      </li>
+
+      <li class="nav-item">
+        <a class="nav-link"
+          ui-sref-active="active"
+          ui-sref="app.article">
+          Article
+        </a>
+      </li>
+
+      <li class="nav-item">
+        <a class="nav-link"
+          ui-sref-active="active"
+          ui-sref="app.profile">
+          My Profile
+        </a>
+      </li>
+
+    </ul>


  </div>
</nav>

If you successfully log in or register, the header now shows "My Profile" in the header instead of "Profile". However, when you refresh the page the application forgets that the user was logged in. This makes sense, as the value of User.current (and every other variable in our app for that matter) is flushed whenever the page is closed or refreshed. To fix this, we'll need to somehow store the user's authentication credentials in a way that won't be deleted on page close.

  Tweet   “I'm now a ninja of Angular Components 💯 ”

Persisting JWT auth across page loads

The server API uses JWT tokens to authenticate users. If you haven't used JWTs before, a JWT token is basically a string that you pass along in requests that verifies the user is actually authorized. If the token is invalid the server will reject the request. It's pretty straightforward and this course about JWT authentication with Angular covers it in great detail.

To keep the user logged in, we'll save their JWT token that's returned from the server in localStorage. localStorage is a key->value store that allows us to persist data even when the page is reloaded.

Create a new service for saving, getting, and destroying a JWT token in localStorage. We'll access localStorage via Angular's $window service and the key we'll use for localStorage is the variable we set for AppConstants.jwtKey in config/app.constants.js (by default it's 'jwtToken')

services/jwt.service.js

export default class JWT {
  constructor(AppConstants, $window) {
    'ngInject';

    this._AppConstants = AppConstants;
    this._$window = $window;
  }

  save(token) {
    this._$window.localStorage[this._AppConstants.jwtKey] = token;
  }

  get() {
    return this._$window.localStorage[this._AppConstants.jwtKey];
  }

  destroy() {
    this._$window.localStorage.removeItem(this._AppConstants.jwtKey);
  }


}
Include the JWT service in services/index.js
import angular from 'angular';

// Create the module where our functionality can attach to
let servicesModule = angular.module('app.services', []);

// Services
import UserService from './user.service';
servicesModule.service('User', UserService);

+ import JwtService from './jwt.service';
+ servicesModule.service('JWT', JwtService);

export default servicesModule;

Now we need need to actually save the token when a user registers or logs in successfully.

Inject the JWT service and create a reference to it (otherwise attemptAuth won't be able to access it). The server sends back the JWT in the token key, which is nested under the user key in the return response - so just call JWT.save on it.

services/user.service.js

export default class User {
-  constructor(AppConstants, $http) {
+  constructor(JWT, AppConstants, $http) {
    'ngInject';

+    this._JWT = JWT;
    this._AppConstants = AppConstants;
    this._$http = $http;

    // Object to store our user properties
    this.current = null;
  }

  // Try to authenticate by registering or logging in
  attemptAuth(type, credentials) {
    let route = (type === 'login') ? '/login' : '';
    return this._$http({
      url: this._AppConstants.api + '/users' + route,
      method: 'POST',
      data: {
        user: credentials
      }
    }).then(
      // On success...
      (res) => {
+        // Set the JWT token
+        this._JWT.save(res.data.user.token);

        // Store the user's info for easy lookup
        this.current = res.data.user;

        return res;
      }
    );
  }

}

Now try logging in or registering. After the request returns successfully, type localStorage.getItem('jwtToken') in your console and you should see the token being stored. Huzzah!

Logging out

Lets add a logout method to the User service. To log out, we'll need to do three things:

  1. Set User.current to null
  2. Delete the JWT token in localStorage
  3. Do a hard refresh of the page to flush out old data
Create the logout method in the User service. Instead of having to do an actual page refresh, we can tell UI-Router to refresh all states by calling $state.go($state.current, {}, {reload: true}). We'll need to inject $state and expose it first though!

services/user.service.js

export default class User {
-  constructor(JWT, AppConstants, $http) {
+  constructor(JWT, AppConstants, $http, $state) {
    'ngInject';

    this._JWT = JWT;
    this._AppConstants = AppConstants;
    this._$http = $http;
+    this._$state = $state;

    // Object to store our user properties
    this.current = null;
  }

  // Try to authenticate by registering or logging in
  attemptAuth(type, credentials) {
    [...]
  }

+  logout() {
+    this.current = null;
+    this._JWT.destroy();
+    // Do a hard reload of current state to ensure all data is flushed
+    this._$state.go(this._$state.$current, null, { reload: true });
+  }

}

Lets test it out in the console! Make sure that you're logged in and seeing the "My Profile" link in the header. Then, get & hold a reference to the service object by running var User = angular.element(document).injector().get('User'). Then run User.logout() - the UI should change! If you check localStorage.getItem('jwtToken') it should yield no token.

But we still don't have the user's info when we refresh the page. Now that we're storing the JWT token, we just need to exchange that JWT token for the user's info on page load.

Getting the user's info on page load

To get the current user's info from the server we simply need to make a GET request to /api/users with an authentication header containing their JWT token. Before we can make that request we'll need to check if a JWT token is present. Therefore if there isn't a JWT token present, or if the server returns an error, we know that there isn't a user logged in.

Create a new method called verifyAuth that returns a promise. If there isn't a JWT token present it will return the promise with false. If User.current isn't null it will resolve with true. Otherwise the promise will resolve with true or false if the request to the server succeeds or fails, respectively. Also note that we've added the headers parameter to the request, with the Authorization key set as 'Token ' + this._JWT.get()

services/user.service.js

export default class User {
-  constructor(JWT, AppConstants, $http, $state) {
+  constructor(JWT, AppConstants, $http, $state, $q) {
    'ngInject';

    this._JWT = JWT;
    this._AppConstants = AppConstants;
    this._$http = $http;
    this._$state = $state;
+    this._$q = $q;

    // Object to store our user properties
    this.current = null;
  }

  // Try to authenticate by registering or logging in
  attemptAuth(type, credentials) {
    [...]
  }

  logout() {
    [...]
  }

+  verifyAuth() {
+    let deferred = this._$q.defer();
+
+    // Check for JWT token first
+    if (!this._JWT.get()) {
+      deferred.resolve(false);
+      return deferred.promise;
+    }
+
+    // If there's a JWT & user is already set
+    if (this.current) {
+      deferred.resolve(true);
+
+    // If current user isn't set, get it from the server.
+    // If server doesn't 401, set current user & resolve promise.
+    } else {
+      this._$http({
+        url: this._AppConstants.api + '/user',
+        method: 'GET',
+        headers: {
+          Authorization: 'Token ' + this._JWT.get()
+        }
+      }).then(
+        (res) => {
+          this.current = res.data.user;
+          deferred.resolve(true);
+        },
+        // If an error happens, that means the user's token was invalid.
+        (err) => {
+          this._JWT.destroy();
+          deferred.resolve(false);
+        }
+        // Reject automatically handled by auth interceptor
+        // Will boot them to homepage
+      );
+    }
+
+    return deferred.promise;
+  }

}

We need to check this on every page load otherwise we won't know if the user is logged in or not. This should be the first code that runs before our application fully boots up. We have an abstract UI-Router state called app that all of our other views inhereit from -- and by adding a resolve to it that invokes User.verifyAuth, it will ensure that the application won't render the child states until we know if the user is logged in or not.

Lets add a UI-Router resolve to our app state that will invoke User.verifyAuth and then wait for the promise to resolve before allowing the rest of the page to execute.

config/app.config.js

function AppConfig($httpProvider, $stateProvider, $locationProvider, $urlRouterProvider) {
  'ngInject';

  /*
    If you don't want hashbang routing, uncomment this line.
    Our tutorial will be using hashbang routing though :)
  */
  // $locationProvider.html5Mode(true);

  $stateProvider
  .state('app', {
    abstract: true,
    templateUrl: 'layout/app-view.html',
+    resolve:{
+      auth: function(User) {
+        return User.verifyAuth();
+      }
+    }
  });

  $urlRouterProvider.otherwise('/');

}

export default AppConfig;

Now when you reload the page the header should have the "My Profile" link in it which means we were automatically authenticated! There's one nice optimization we can do that will make sending authenticated requests much easier, which is to automatically add the JWT authentication header to all requests we make. Otherwise, we will have to include it with all $http requests we make which will be tedious and likely introduce accidental bugs.

Utilizing $http interceptors for JWT

We can easily add logic around all $http requests in our application by creating an $http interceptor. For our use case specifically, we want to automatically add the authorization header with our JWT token only if the $http request is being made to our own API. Additionally, if the server ever returns a 401 error (unauthorized), that means that the current user isn't logged in and we should delete the JWT token from localStorage.

Create a new file in the config folder called auth.interceptor.js that adds the authorization header for requests to our API. If the interceptor handles a 401 error, have it delete the JWT token in localStorage and do a hard page refresh.

config/auth.interceptor.js

function authInterceptor(JWT, AppConstants, $window, $q) {
  'ngInject';

  return {
    // automatically attach Authorization header
    request: function(config) {
      if(config.url.indexOf(AppConstants.api) === 0 && JWT.get()) {
        config.headers.Authorization = 'Token ' + JWT.get();
      }
      return config;
    },

    // Handle 401
    responseError: function(rejection) {
      if (rejection.status === 401) {
        // clear any JWT token being stored
        JWT.destroy();
        // do a hard page refresh
        $window.location.reload();
      }
      return $q.reject(rejection);
    }

  }
}

export default authInterceptor;
$http interceptors need to be added to the $httpProvider service within a config function. Include the interceptor and then add it in the AppConfig function

config/app.config.js

+ import authInterceptor from './auth.interceptor';

function AppConfig($httpProvider, $stateProvider, $locationProvider, $urlRouterProvider) {
  'ngInject';

+  // Push our interceptor for auth
+  $httpProvider.interceptors.push(authInterceptor);

  /*
    If you don't want hashbang routing, uncomment this line.
    Our tutorial will be using hashbang routing though :)
  */
  // $locationProvider.html5Mode(true);

  $stateProvider
  .state('app', {
    abstract: true,
    templateUrl: 'layout/app-view.html',
    resolve:{
      auth: function(User) {
        return User.verifyAuth();
      }
    }
  });

  $urlRouterProvider.otherwise('/');

}

export default AppConfig;
Delete the headers object from the $http request in User.verifyAuth
[...]
        url: this._AppConstants.api + '/user',
        method: 'GET'
-        headers: {
-          Authorization: 'Token ' + this._JWT.get()
-        }
[...]

Awesome, it still works! The final thing we'll do to finish authentication is creating a simple way to restrict access to pages whether you're logged in or logged out.

Restricting access to pages for logged in/out users

The most straightforward way to restrict access to a page is by adding a resolve to it's UI-Router state. A good example of this are our login & register pages -- we don't want logged in users to be able to access those. In the next chapter we'll be making a settings page, and we won't want logged out users to be able to access that page either.

Lets create a method called User.ensureAuthIs that takes a single boolean argument. If true, it will redirect anyone to the homepage who isn't logged in. If false, it will redirect anyone to the homepage that is logged in.

services/user.service.js

[...]
  // This method will be used by UI-Router resolves
  ensureAuthIs(bool) {
    let deferred = this._$q.defer();

    this.verifyAuth().then((authValid) => {
      // if it's the opposite, redirect home
      if (authValid !== bool) {
        this._$state.go('app.home');
        deferred.resolve(false);
      } else {
        deferred.resolve(true);
      }
    })

    return deferred.promise;
  }
[...]
And then add User.ensureAuthIs(false) to the resolves of both app.login and app.register

auth/auth.config.js

function AuthConfig($stateProvider, $httpProvider) {
  'ngInject';

  // Define the routes
  $stateProvider

  .state('app.login', {
    url: '/login',
    controller: 'AuthCtrl as $ctrl',
    templateUrl: 'auth/auth.html',
    title: 'Sign in',
+    resolve:{
+      auth: function(User) {
+        return User.ensureAuthIs(false);
+      }
+    }
  })

  .state('app.register', {
    url: '/register',
    controller: 'AuthCtrl as $ctrl',
    templateUrl: 'auth/auth.html',
    title: 'Sign up',
+    resolve:{
+      auth: function(User) {
+        return User.ensureAuthIs(false);
+      }
+    }
  });

};

export default AuthConfig;
Last but not least, when someone successfully registers/logs in, redirect them to the homepage

auth/auth.controller.js

class AuthCtrl {
  constructor(User, $state) {
    'ngInject';

    this._User = User;
+    this._$state = $state;

    this.title = $state.current.title;
    this.authType = $state.current.name.replace('app.', '');
  }

  submitForm () {
    this.isSubmitting = true;

    this._User.attemptAuth(this.authType, this.formData).then(
      // Callback for success
      (res) => {
-        this.isSubmitting = false;
-        console.log(res);
+        this._$state.go('app.home');
      },
      // Callback for failure
      (err) => {
        this.isSubmitting = false;
        this.errors = err.data.errors;
      }
    );
  }

}

export default AuthCtrl;

We're all done implementing authentication! Now lets give the user a way to edit their settings (username, picture, bio, etc) as well as view their own profile.

Updating user settings

Now that users can sign up & create an account, we should really let them edit/update their account information. Besides the username, email, and password they've already provided us with, they can also provide a profile image and a bio about themselves that will be displayed on their profile (which we'll create in the next chapter). Lets create a settings page where our users can update all of these fields!

Create a new folder called settings/ where we'll be creating our settings feature. Then create settings.html in it with the HTML below. You'll notice that it's very similar to our auth.html form -- there are just two more fields (profile picture and bio) and a logout button at the bottom of the page. Also notice we're using our list-errors component :)

settings/settings.html

<div class="settings-page">
  <div class="container page">
    <div class="row">
      <div class="col-md-6 offset-md-3 col-xs-12">

        <h1 class="text-xs-center">Your Settings</h1>

        <list-errors errors="$ctrl.errors"></list-errors>

        <form ng-submit="$ctrl.submitForm()">
          <fieldset ng-disabled="$ctrl.isSubmitting">

            <fieldset class="form-group">
              <input class="form-control"
                type="text"
                placeholder="URL of profile picture"
                ng-model="$ctrl.formData.image" />
            </fieldset>

            <fieldset class="form-group">
              <input class="form-control form-control-lg"
                type="text"
                placeholder="Username"
                ng-model="$ctrl.formData.username" />
            </fieldset>

            <fieldset class="form-group">
              <textarea class="form-control form-control-lg"
                rows="8"
                placeholder="Short bio about you"
                ng-model="$ctrl.formData.bio">
              </textarea>
            </fieldset>

            <fieldset class="form-group">
              <input class="form-control form-control-lg"
                type="email"
                placeholder="Email"
                ng-model="$ctrl.formData.email" />
            </fieldset>

            <fieldset class="form-group">
              <input class="form-control form-control-lg"
                type="password"
                placeholder="New Password"
                ng-model="$ctrl.formData.password" />
            </fieldset>

            <button class="btn btn-lg btn-primary pull-xs-right"
              type="submit">
              Update Settings
            </button>

          </fieldset>
        </form>

        <!-- Line break for logout button -->
        <hr />

        <button class="btn btn-outline-danger">
          Or click here to logout.
        </button>

      </div>
    </div>
  </div>
</div>
Create an empty (for now) controller for the settings page

settings/settings.controller.js

class SettingsCtrl {
  constructor() {
    'ngInject';


  }

}


export default SettingsCtrl;
Create the config function for settings that defines a state called app.settings and has a resolve for User.ensureAuthIs(true) (aka the user needs to be logged in to see this page). Note usage of controllerAs: '$ctrl' here instead of `controller: 'SettingsCtrl as $ctrl' - it's the same thing, just split into two lines. We'll use it from here on out as we think it's more explicit & easier to understand.

settings/settings.config.js

function SettingsConfig($stateProvider) {
  'ngInject';

  $stateProvider
  .state('app.settings', {
    url: '/settings',
    controller: 'SettingsCtrl',
    controllerAs: '$ctrl',
    templateUrl: 'settings/settings.html',
    title: 'Settings',
    resolve:{
      auth: function(User) {
        return User.ensureAuthIs(true);
      }
    }
  });

};

export default SettingsConfig;
Create the index.js file for settings and include our config & controller

settings/index.js

import angular from 'angular';

// Create the settings module where our functionality can attach to
let settingsModule = angular.module('app.settings', []);


// Include our UI-Router config settings
import SettingsConfig from './settings.config';
settingsModule.config(SettingsConfig);

// Controllers
import SettingsCtrl from './settings.controller';
settingsModule.controller('SettingsCtrl', SettingsCtrl);


export default settingsModule;
Import the settings index file and declare it in our requires variable
[...]
import './auth';
+ import './settings';


// Create and bootstrap application
const requires = [
  'ui.router',
  'templates',
  'app.layout',
  'app.components',
  'app.home',
  'app.profile',
  'app.article',
  'app.services',
  'app.auth',
+  'app.settings'
];

[...]

Now that the settings page is set up, lets include a handy link to the page for our users (and ourselves :)

Include a ui-sref link to the app.settings UI-Router state in the app header

layout/header.html

[...]
    <!-- Show this for logged in users -->
    <ul show-authed="true"
      class="nav navbar-nav pull-xs-right">

      <li class="nav-item">
        <a class="nav-link"
          ui-sref-active="active"
          ui-sref="app.home">
          Home
        </a>
      </li>

      <li class="nav-item">
        <a class="nav-link"
          ui-sref-active="active"
          ui-sref="app.article">
          Article
        </a>
      </li>

+      <li class="nav-item">
+        <a class="nav-link"
+          ui-sref-active="active"
+          ui-sref="app.settings">
+          <i class="ion-gear-a"></i>&nbsp;Settings
+        </a>
+      </li>

      <li class="nav-item">
        <a class="nav-link"
          ui-sref-active="active"
          ui-sref="app.profile">
          My Profile
        </a>
      </li>

    </ul>
[...]

What is <i class="ion-gear-a"></i>? It's an icon from Ionicons, a really dead-simple CSS icon set made by the folks over at Ionic. If you look at the index.html file in the src/ folder you'll see that we're including it in the header tag.

The settings page is now viewable and navigable from our header bar. However, the page doesn't show any of our user's current data in the form fields.

Inject the User service and prepopulate the formData object with the current user's information

settings/settings.controller.js

class SettingsCtrl {
+  constructor() {
+  constructor(User) {
    'ngInject';

+    this.formData = {
+       email: User.current.email,
+       bio: User.current.bio,
+       image: User.current.image,
+       username: User.current.username
+    };

  }

}


export default SettingsCtrl;

Excellent, our user's data is being prepopulated. Before we make the form work, lets make the logout button work.

In the constructor function, set this.logout to reference the User.logout method. We need to bind the User.logout method to the User object though, otherwise invoking this.logout() will try running the contents of User.logout but on the SettingsCtrl class (which would result in an error).

settings/settings.controller.js

class SettingsCtrl {
  constructor(User) {
    'ngInject';

    this.formData = {
       email: User.current.email,
       bio: User.current.bio,
       image: User.current.image,
       username: User.current.username
    };

+    // Bind is req'd because the logout method assumes
+    // the execution context is within the User object.
+    this.logout = User.logout.bind(User);

  }

}


export default SettingsCtrl;

One thing worth mentioning is that we could've had a class method called logout() { this._User.logout() } instead of this.logout = User.logout.bind(User);. This would've allowed us to not have to use .bind(User). These both yield the same desired result, but we find the former to be less verbose & we intentionally placed it in this course to ensure you have a solid understanding of scopes/contexts in Javascript.

Invoke the logout method when the logout button is clicked

settings/settings.html

[...]

        <button class="btn btn-outline-danger"
+          ng-click="$ctrl.logout()">
          Or click here to logout.
        </button>

[...]

The logout button should now work! Now lets finish up the form's functionality.

To make our form editable, we'll need to first create a method in our User service that allows us to update the user's information on the server.

Create an update method in the User service that allows us to make a request to the update user endpoint with an object containing the fields we want to update

services/user.service.js

[...]

+  // Update the current user's name, email, password, etc
+  update(fields) {
+    return this._$http({
+      url: this._AppConstants.api + '/user',
+      method: 'PUT',
+      data: { user: fields }
+    }).then(
+      (res) => {
+        this.current = res.data.user;
+        return res.data.user;
+      }
+    );
+  }

[...]
Create the method in the settings controller that our auth.html form will invoke on form submission (<form ng-submit="$ctrl.submitForm()">)`

settings/settings.controller.js

class SettingsCtrl {
-  constructor(User) {
+  constructor(User, $state) {
    'ngInject';

+    this._User = User;
+    this._$state = $state;

    this.formData = {
       email: User.current.email,
       bio: User.current.bio,
       image: User.current.image,
       username: User.current.username
    };

    // Bind is req'd because the logout method assumes
    // the execution context is within the User object.
    this.logout = User.logout.bind(User);
  }

+  submitForm() {
+    this.isSubmitting = true;
+    this._User.update(this.formData).then(
+      (user) => {
+        console.log('success!');
+        this.isSubmitting = false;
+      },
+      (err) => {
+        this.isSubmitting = false;
+        this.errors = err.data.errors;
+      }
+    )
+  }

}


export default SettingsCtrl;

Go ahead and test it out by updating some of the fields, submitting the form, and then refreshing the page. The data stays! When the user successfully updates their information, we want to redirect them to their profile (where their name, picture, and bio's changed would be reflected). Lets go ahead and build out the functionality on our profile pages.

Displaying a profile page from server data

The profile page will be used for all user profiles and not just our own. As such, the API has a different endpoint for retrieving public profile information than the /api/user endpoint we've previously used to retrieve the currently logged in user's info.

In general, all functionality in our User service is meant to only deal with the currently logged in User. As such, it makes sense for us to create a new service that will exclusively deal with retrieving & interacting with user data that is not (necessarily) the currently logged in user's.

Create a new service called Profile in the services folder called profile.service.js. Create a get method on it that accepts a username and passes it along to an $http request to the get profile endpoint

services/profile.service.js

export default class Profile {
  constructor(AppConstants, $http) {
    'ngInject';

    this._AppConstants = AppConstants;
    this._$http = $http;


  }

  // Retrieve a user's profile
  get(username) {
    return this._$http({
      url: this._AppConstants.api + '/profiles/' + username,
      method: 'GET'
    }).then((res) => res.data.profile);
  }


}
Include it in services/index.js
[...]

import JwtService from './jwt.service';
servicesModule.service('JWT', JwtService);

+ import ProfileService from './profile.service';
+ servicesModule.service('Profile', ProfileService);


export default servicesModule;

We now want to wire this up to the profile page. Specifically, we want to:

  1. Get the username from the URL of the page. We'll do this using UI-Router parameters.
  2. Pass that username to the Profile.get method and then display the resulting data on the profile page. There are a few ways we could go about doing this. We could use the $stateParams service in our profile controller and then invoke Profile.get - the only downside is that the profile page will partially render and look weird while Profile.get is waiting for the server response. The other option is to use a resolve on UI-Router to call Profile.get, which will ensure the profile is loaded before the state changes.

Using a resolve typically makes sense when the data you're requesting only needs to be loaded once for that page. When we load a profile page we don't need to request the profile information again, so this is a great place for us to use a UI-Router resolve.

In profile.config.js, update the URL to accept a :username parameter after any route that starts with an @ sign (i.e. '@eric', '@jason', etc). Then, add a resolve that will return the profile info associated with the username property from the $stateParams](https://github.com/angular-ui/ui-router/wiki/URL-Routing#stateparams-service) service

profile/profile.config.js

[...]

  $stateProvider
  .state('app.profile', {
-    url: '/profile',
+    url: '/@:username',
    controller: 'ProfileCtrl',
    controllerAs: '$ctrl',
    templateUrl: 'profile/profile.html'
+    resolve: {
+      profile: function(Profile, $state, $stateParams) {
+        return Profile.get($stateParams.username).then(
+          (profile) => profile,
+          (err) => $state.go('app.home')
+        );
+      }
+    }
  });

[...]

Why didn't you add a title property to this state? We're going to update the title with the user's name inside the controller instead of having a static name like "Profile". Don't worry, we'll get to this a little bit later!

In the profile controller, inject the profile object resolved by UI-Router and expose it to our view. Then, create a property called isUser that will let our view know whether or not to show an "Edit Profile" button or a "Follow" button.

profile/profile.controller.js

class ProfileCtrl {
-  constructor() {
+  constructor(profile, User) {
    'ngInject';

+    // The profile for this page, resolved by UI Router
+    this.profile = profile;

+    // Show edit profile is this profile is the current user's
+    if (User.current) {
+      this.isUser = (User.current.username === this.profile.username);
+    } else {
+      this.isUser = false;
+    }

  }
}


export default ProfileCtrl;
Update the profile template with user's info. Show a link to the settings page if this is our profile, otherwise show a follow button.

profile/profile.html

[...]

        <div class="col-xs-12 col-md-10 offset-md-1">

-          <img class="user-img" />
+          <img ng-src="{{::$ctrl.profile.image}}" class="user-img" />
-          <h4>BradGreen</h4>
+          <h4 ng-bind="::$ctrl.profile.username"></h4>
-          <p>I'm the dude running the Angular team at Google, yo.</p>
+          <p ng-bind="::$ctrl.profile.bio"></p>

+          <a ui-sref="app.settings"
+            class="btn btn-sm btn-outline-secondary action-btn"
+            ng-show="$ctrl.isUser">
+            <i class="ion-gear-a"></i> Edit Profile Settings
+          </a>

          <button class="btn btn-sm btn-outline-secondary action-btn"
+            ng-hide="$ctrl.isUser">
            <i class="ion-plus-round"></i>
            &nbsp;
-            Follow BradGreen
+            Follow
          </button>

        </div>

[...]

If you go to /#/@[your username] in your web browser you should now see your profile! You'll also be able to click through to the settings page. Lets make it easier to get to our profile page - first we need to update our settings controller to redirect back the the profile on success, and then we'll update our header's profile link to show our name & picture and link to our profile.

First, lets update our settings controller to redirect to our profile page when we successfully update our information.

Update the settings controller to redirect to the user's profile on success

settings/settings.controller.js

[...]
    this._User.update(this.formData).then(
      (user) => {
-        console.log('success!');
-        this.isSubmitting = false;
+        this._$state.go('app.profile', {username:user.username});
      },
      (err) => {
        this.isSubmitting = false;
        this.errors = err.data.errors;
      }
    )
[...]

Now we need to change the header bar link. Lets show the current user's prof pic and username.

In the header component's controller, inject the User service and attach the current user to a property called currentUser. We'll need to use $scope.$watch to ensure the currentUser property always reflects the data stored in User.current.

layout/header.component.js

class AppHeaderCtrl {
-  constructor(AppConstants) {
+  constructor(AppConstants, User, $scope) {
    'ngInject';

    this.appName = AppConstants.appName;
+    this.currentUser = User.current;

+    $scope.$watch('User.current', (newUser) => {
+      this.currentUser = newUser;
+    });
  }
}

[...]
Swap out the current profile link for a new link that includes our username. In the contents of the <a> tag, show our username and profile image.

layout/header.html

[...]
      <li class="nav-item">
        <a class="nav-link"
          ui-sref-active="active"
-          ui-sref="app.profile">
+          ui-sref="app.profile({ username: $ctrl.currentUser.username })">
-          My Profile
+          <img ng-src="{{$ctrl.currentUser.image}}" class="user-pic"/>
+          {{ $ctrl.currentUser.username }}
        </a>
      </li>
[...]

Nice! Our new header looks sexy.

Creating a follow button component with encapsulated functionality

The last thing we'll do on the profile (for now) is making the follow button actually work. The follow button will also be used on our articles page which we'll build next chapter, so we should create a reusable component that will encapsulate the button & it's functionality. It also makes the implementation very elegant as our profile controller doesn't need to be aware of the follow button's logic. The only thing that we need to give the follow button is a profile object, as it contains the two things we need: the user's username and whether or not we're currently following them.

First, lets add the html to our profile page.

profile/profile.html

<div class="profile-page">

  <!-- User's basic info & action buttons -->
  <div class="user-info">
    <div class="container">
      <div class="row">
        <div class="col-xs-12 col-md-10 offset-md-1">

          <img ng-src="{{::$ctrl.profile.image}}" class="user-img" />
          <h4 ng-bind="::$ctrl.profile.username"></h4>
          <p ng-bind="::$ctrl.profile.bio"></p>
+          <follow-btn user="$ctrl.profile" ng-hide="$ctrl.isUser"></follow-btn>

          <a ui-sref="app.settings"
            class="btn btn-sm btn-outline-secondary action-btn"
            ng-show="$ctrl.isUser">
            <i class="ion-gear-a"></i> Edit Profile Settings
          </a>

-          <button class="btn btn-sm btn-outline-secondary action-btn"
-            ng-hide="$ctrl.isUser">
-            <i class="ion-plus-round"></i>
-            &nbsp;
-            Follow
-          </button>

        </div>
      </div>
    </div>
  </div>

If you view the profile of another user besides the one you're currently logged in as, nothing will show up because Angular can't find the <follow-btn> component. Lets go ahead and create that.

Create a folder called buttons/ in the components/ folder where we'll store the various button components we'll make for this application. Then create a file with the HTML that will show the text "follow/unfollow username", depending if we're already following that user.

components/buttons/follow-btn.html

<button
  class="btn btn-sm btn-outline-secondary action-btn">
  <i class="ion-plus-round"></i>
  &nbsp;
  {{ $ctrl.user.following ? 'Unfollow' : 'Follow' }} {{ $ctrl.user.username }}
</button>
Next, create the base component for the follow button functionality. We'll pass along the target user to follow/unfollow via the user attribute, as we need to know their username and whether or not we're currently following them.

components/buttons/follow-btn.component.js

class FollowBtnCtrl {
  constructor() {
    'ngInject';

  }
}

let FollowBtn= {
  bindings: {
    user: '='
  },
  controller: FollowBtnCtrl,
  templateUrl: 'components/buttons/follow-btn.html'
};

export default FollowBtn;
Include the component in components/index.js
[...]

+ import FollowBtn from './buttons/follow-btn.component';
+ componentsModule.component('followBtn', FollowBtn);


export default componentsModule;

Cool, the follow button is showing now. We need to make the button actually make a request to the server when it's clicked though. It's inapproapriate to make $http requests in controllers, so lets extend our Profile service with follow & unfollow methods that wrap the $http requests.

Add follow and unfollow methods to our profile service that resolve the data of the $http request

services/profile.service.js

export default class Profile {
  constructor(AppConstants, $http) {
    'ngInject';

    this._AppConstants = AppConstants;
    this._$http = $http;

  }

  // Retrieve a user's profile
  get(username) {
    return this._$http({
      url: this._AppConstants.api + '/profiles/' + username,
      method: 'GET'
    }).then((res) => res.data.profile);
  }

+  // Follow a user
+  follow(username) {
+    return this._$http({
+      url: this._AppConstants.api + '/profiles/' + username + '/follow',
+      method: 'POST'
+    }).then((res) => res.data);
+  }

+  // Unfollow a user
+  unfollow(username) {
+    return this._$http({
+      url: this._AppConstants.api + '/profiles/' + username + '/follow',
+      method: 'DELETE'
+    }).then((res) => res.data);
+  }


}

Call the follow/unfollow Profile methods in a submit method on the component that will be invoked when the follow button is clicked

components/buttons/follow-btn.component.js

class FollowBtnCtrl {
-  constructor() {
+  constructor(Profile, User, $state) {
    'ngInject';

+    this._Profile = Profile;
+    this._User = User;

+    this._$state = $state;
  }

+  submit() {
+    this.isSubmitting = true;
+
+    if (!this._User.current) {
+      this._$state.go('app.register');
+      return;
+    }
+
+    // If following already, unfollow
+    if (this.user.following) {
+      this._Profile.unfollow(this.user.username).then(
+        () => {
+          this.isSubmitting = false;
+          this.user.following = false;
+        }
+      )
+
+    // Otherwise, follow them
+    } else {
+      this._Profile.follow(this.user.username).then(
+        () => {
+          this.isSubmitting = false;
+          this.user.following = true;
+        }
+      )
+    }
+  }

}

let FollowBtn= {
  bindings: {
    user: '='
  },
  controller: FollowBtnCtrl,
  templateUrl: 'components/buttons/follow-btn.html'
};

export default FollowBtn;
Update the button HTML to invoke the submit method when it's clicked

components/buttons/follow-btn.html

<button
-  class="btn btn-sm btn-outline-secondary action-btn">
+  class="btn btn-sm action-btn"
+  ng-class="{ 'disabled': $ctrl.isSubmitting,
+              'btn-outline-secondary': !$ctrl.user.following,
+              'btn-secondary': $ctrl.user.following }"
+  ng-click="$ctrl.submit()">
  <i class="ion-plus-round"></i>
  &nbsp;
  {{ $ctrl.user.following ? 'Unfollow' : 'Follow' }} {{ $ctrl.user.username }}
</button>

The follow button now works -- great job! Next, lets start working on the functionality for creating, updating, showing and deleting articles (this is a social blogging site after all :)

Creating the article editor

Lets create a page where we can create and update articles. For the most part it will just be a simple form, but we'll also need to create a UI for adding "tags". Further, we'll need to prepopulate the form when we're editing an article. Thankfully Angular and UI-Router makes both of these super easy to accomplish!

To start off, lets create the template and base controller.

Create a new folder called 'editor' and create the following HTML file in it

editor/editor.html

<div class="editor-page">
  <div class="container page">
    <div class="row">
      <div class="col-md-10 offset-md-1 col-xs-12">

        <list-errors errors="$ctrl.errors"></list-errors>

        <form>
          <fieldset ng-disabled="$ctrl.isSubmitting">

            <fieldset class="form-group">
              <input class="form-control form-control-lg"
                ng-model="$ctrl.article.title"
                type="text"
                placeholder="Article Title" />
            </fieldset>

            <fieldset class="form-group">
              <input class="form-control"
                ng-model="$ctrl.article.description"
                type="text"
                placeholder="What's this article about?" />
            </fieldset>

            <fieldset class="form-group">
              <textarea class="form-control"
                rows="8"
                ng-model="$ctrl.article.body"
                placeholder="Write your article (in markdown)">
              </textarea>
            </fieldset>

            <fieldset class="form-group">
              <input class="form-control"
                type="text"
                placeholder="Enter tags"
                ng-model="$ctrl.tagField" />

              <div class="tag-list">
                <span class="tag-default tag-pill">
                  <i class="ion-close-round"></i>
                  Example Tag
                </span>
                <span class="tag-default tag-pill">
                  <i class="ion-close-round"></i>
                  Another Tag
                </span>
              </div>
            </fieldset>

            <button class="btn btn-lg pull-xs-right btn-primary" type="button">
              Publish Article
            </button>

          </fieldset>
        </form>

      </div>
    </div>
  </div>
</div>
Create an empty controller for the editor that we'll add functionality into.

editor/editor.controller.js

class EditorCtrl {
  constructor() {
    'ngInject';


  }

}


export default EditorCtrl;
Set up a new route for the editor at /editor. Since only registered users will be able to create articles, ensure the user is logged in to see this page

editor/editor.config.js

function EditorConfig($stateProvider) {
  'ngInject';

  $stateProvider
  .state('app.editor', {
    url: '/editor',
    controller: 'EditorCtrl',
    controllerAs: '$ctrl',
    templateUrl: 'editor/editor.html',
    title: 'Editor',
    resolve:{
      auth: function(User) {
        return User.ensureAuthIs(true);
      }
    }
  });

};

export default EditorConfig;
Include the controller & config and attach it to an angular module named app.editor
import angular from 'angular';

// Create the module where our functionality can attach to
let editorModule = angular.module('app.editor', []);

// Include our UI-Router config settings
import EditorConfig from './editor.config';
editorModule.config(EditorConfig);


// Controllers
import EditorCtrl from './editor.controller';
editorModule.controller('EditorCtrl', EditorCtrl);


export default editorModule;
Finally, include the editor in app.js

app.js

[...]
import './auth';
import './settings';
+ import './editor';


// Create and bootstrap application
const requires = [
  'ui.router',
  'templates',
  'app.layout',
  'app.components',
  'app.home',
  'app.profile',
  'app.article',
  'app.services',
  'app.auth',
  'app.settings'
+  'app.editor'
];

[...]

If you navigate to /#/editor you should now see the HTML contained in editor.html. Lets make this a bit easier to navigate to for ourselves & our users!

Add a link to the editor in the header. While we're at it, remove the current link to the article page since we don't need it.

layout/header.html

[...]
-      <li class="nav-item">
-        <a class="nav-link"
-          ui-sref-active="active"
-          ui-sref="app.article">
-          Article
-        </a>
-      </li>

+      <li class="nav-item">
+        <a class="nav-link"
+          ui-sref-active="active"
+          ui-sref="app.editor">
+          <i class="ion-compose"></i>&nbsp;New Article
+        </a>
+      </li>
[...]

Awesome! Now lets work on getting the editor to actually behave the way we want it to. On all the other pages we've worked on we simply used ng-bind on the input fields. We'll be doing this here as well on the title, description and body, but we need to create a custom UI for creating/removing tags.

Initialize an object called article in the constructor function that will hold the current article's data. Then, create methods for the template to add and remove tags from the array of tags in the article object.

editor/editor.controller.js

class EditorCtrl {
  constructor() {
    'ngInject';

    this.article = {
      title: '',
      description: '',
      body: '',
      tagList: []
    }

  }

  addTag() {
    // Make sure this tag isn't already in the array
    if (!this.article.tagList.includes(this.tagField)) {
      this.article.tagList.push(this.tagField);
      this.tagField = '';
    }
  }

  removeTag(tagName) {
    this.article.tagList = this.article.tagList.filter((slug) => slug != tagName);
  }

}

export default EditorCtrl;
Update the template to hook into the functionality we added to the controller. We'll use ng-keyup to check if the user hit the enter key to add a tag, ng-repeat to list out the current tags, and ng-click to remove one of the listed tags.

editor/editor.html

-            <fieldset class="form-group">
-              <input class="form-control"
-                type="text"
-                placeholder="Enter tags"
-                ng-model="$ctrl.tagField" />
-
-              <div class="tag-list">
-                <span class="tag-default tag-pill">
-                  <i class="ion-close-round"></i>
-                  Example Tag
-                </span>
-                <span class="tag-default tag-pill">
-                  <i class="ion-close-round"></i>
-                  Another Tag
-                </span>
-              </div>
-            </fieldset>

+            <fieldset class="form-group">
+              <input class="form-control"
+                type="text"
+                placeholder="Enter tags"
+                ng-model="$ctrl.tagField"
+                ng-keyup="$event.keyCode == 13 && $ctrl.addTag()" />
+
+              <div class="tag-list">
+                <span ng-repeat="tag in $ctrl.article.tagList"
+                  class="tag-default tag-pill">
+                  <i class="ion-close-round" ng-click="$ctrl.removeTag(tag)"></i>
+                  {{ tag }}
+                </span>
+              </div>
+            </fieldset>

Adding & removing tags work!

Now lets create a service to actually save the article in our editor to the server.

Create a new service called Articles. Add a method called save that will take an article object and POST it to the /articles API endpoint.

services/articles.service.js

export default class Articles {
  constructor(AppConstants, $http) {
    'ngInject';

    this._AppConstants = AppConstants;
    this._$http = $http;


  }

  // Creates an article
  save(article) {
    let request = {
      url: `${this._AppConstants.api}/articles`,
      method: 'POST',
      data: { article: article }
    };

    return this._$http(request).then((res) => res.data.article);
  }


}

What's the deal with ${this._AppConstants.api}/articles?

It's the new template literals in ES6! Instead of having to split strings like you normally would with the plus sign 'hi' + userName, you can simply write a string and place variables in it with the ${} syntax (hi ${userName}). Note that you must have the string wrapped in backticks and not single or double quotes.

Before we can use the Articles service, we need to include it in the services index.js file

services/index.js

[...]
import ProfileService from './profile.service';
servicesModule.service('Profile', ProfileService);

+ import ArticlesService from './articles.service';
+ servicesModule.service('Articles', ArticlesService);


export default servicesModule;

Now lets hook into the save method on the Articles service.

Create a submit method in the editor controller that will be invoked by clicking the "Publish Article" button

editor/editor.html

            <button class="btn btn-lg pull-xs-right btn-primary" type="button" ng-click="$ctrl.submit()">
              Publish Article
            </button>

editor/editor.controller.js

class EditorCtrl {
-  constructor() {
+  constructor(Articles, $state) {
    'ngInject';

+    this._Articles = Articles;
+    this._$state = $state;

    [...]
  }

  addTag() {
    [...]
  }

  removeTag(tagName) {
    [...]
  }

+  submit() {
+    this.isSubmitting = true;
+
+    this._Articles.save(this.article).then(
+      (newArticle) => {
+        this._$state.go('app.article', { slug: newArticle.slug });
+      },
+      (err) => {
+        this.isSubmitting = false;
+        this.errors = err.data.errors;
+      }
+    );
+  }

}

export default EditorCtrl;

It should work! Before we actually show the article on the page we redirect to on success, lets add the ability to update existing articles in the editor.

Extending the editor to update articles

We want to have the form prefilled with an article's data when there is slug in the URL (i.e. /#/editor/article-slug-here). We currently don't have a way of retrieving articles from the server by slug, so lets extend the article service to allow this.

Add a get method to the article service for retrieving a single article by its slug

services/articles.service.js

  // Retrieve a single article
  get(slug) {
    return this._$http({
      url: this._AppConstants.api + '/articles/' + slug,
      method: 'GET'
    }).then((res) => res.data.article);
  }
Modify the editor config that will automatically prepopulate an article's data if it detects a slug in the url (i.e. /#/editor/name-of-article-here)

editor/editor.config.js

  $stateProvider
  .state('app.editor', {
    url: '/editor/:slug',
    controller: 'EditorCtrl',
    controllerAs: '$ctrl',
    templateUrl: 'editor/editor.html',
    title: 'Editor',
    resolve:{
      auth: function(User) {
        return User.ensureAuthIs(true);
      },
      article: function(Articles, User, $state, $stateParams) {
        // If we're trying to edit an article
        if ($stateParams.slug) {
          return Articles.get($stateParams.slug).then(
            (article) => {
              // If the current user is the author, resolve the article data
              if (User.current.username === article.author.username) {
                return article;

              // Otherwise, redirect them to home page
              } else {
                $state.go('app.home');
              }
            },
            // If there's an error (article doesn't exist, etc), redirect to home page
            (err) => $state.go('app.home')
          );

        // If this is a new article, then just return null
        } else {
          return null;
        }
      }

    }
  });
Update the editor's constructor function to check for a prepopulated article

editor/editor.controller.js

[...]
+  constructor(Articles, article, $state) {
    'ngInject';

    this._Articles = Articles;
    this._$state = $state;

+    if (!article) {
      this.article = {
        title: '',
        description: '',
        body: '',
        tagList: []
      }
+    } else {
+      this.article = article;
+    }

  }
[...]

Cool, the article data is now being prepopulated! But we need to have it actually update the article when we save, not try to create a new one. Lets update the save method to check for an existing article slug and update the article if one is found.

Check for an article slug in the save method. Updating an article should be done via PUT and creating new articles should be done via POST.

services/articles.service.js

  // Creates or updates an article
  save(article) {

    let request = {};

    // If there's a slug, perform an update via PUT w/ article's slug
    if (article.slug) {
      request.url = `${this._AppConstants.api}/articles/${article.slug}`;
      request.method = 'PUT';
      // Delete the slug from the article to ensure the server updates the slug,
      // which happens if the title of the article changed.
      delete article.slug;

    // Otherwise, this is a new article POST request
    } else {
      request.url = `${this._AppConstants.api}/articles`;
      request.method = 'POST';

    }

    // Set the article data in the data attribute of our request
    request.data = { article: article };

    return this._$http(request).then((res) => res.data.article);

  }

If you update an article and then go back to the editor page, you should now see the new article data being persisted in the form. Nice work!

Rendering articles from the server

Lets create the page where we can actually see articles! We want users to be able to view articles by visiting /#/article/slug-of-article. To do this, we need to retrieve the given article by its slug (similar to what we did with the article editor).

Add a resolve to the app.article state that retrieves the article corresponding with the slug in the url (i.e. /#/article/slug-of-article)

article/article.config.js

function ArticleConfig($stateProvider) {
  'ngInject';

  $stateProvider
  .state('app.article', {
    url: '/article/:slug',
    controller: 'ArticleCtrl',
    controllerAs: '$ctrl',
    templateUrl: 'article/article.html',
+    // When the controller loads, the title of the web page
+    // will be changed to the article's title
+    title: 'Article',
+    resolve: {
+      article: function(Articles, $state, $stateParams) {
+        return Articles.get($stateParams.slug).then(
+          (article) => article,
+          (err) => $state.go('app.home')
+        );
+      }
+    }
  });

};

export default ArticleConfig;
Inject the resolved article from UI-Router into ArticleCtrl. Make it available to the view by assigning it to this.article

article/article.controller.js

class ArticleCtrl {
-  constructor() {
+  constructor(article) {
    'ngInject';

+    this.article = article;

  }
}


export default ArticleCtrl;
Update the article template to show the article's title, markdown, and tags

article/article.html

<div class="article-page">

  <!-- Banner for article title, action buttons -->
  <div class="banner">
    <div class="container">

-      <h1>Example article title</h1>
+      <h1 ng-bind="::$ctrl.article.title"></h1>

      <div class="article-meta">
        <!-- Show author info + favorite & follow buttons -->
        <div class="article-meta">
          <a href=""><img /></a>
          <div class="info">
            <a href="" class="author">Brad Green</a>
            <span class="date">January 20th</span>
          </div>

          <button class="btn btn-sm btn-outline-secondary">
            <i class="ion-plus-round"></i>
            &nbsp;
            Follow Brad Green
          </button>
          &nbsp;
          <button class="btn btn-sm btn-outline-primary">
            <i class="ion-heart"></i>
            &nbsp;
            Favorite Article <span class="counter">(29)</span>
          </button>
        </div>
      </div>

    </div>
  </div>



  <!-- Main view. Contains article html and comments -->
  <div class="container page">

    <!-- Article's HTML & tags rendered here -->
    <div class="row article-content">
      <div class="col-xs-12">

        <div>
-          <p>This is the content of our article.</p>
+          <div ng-bind="::$ctrl.article.body"></div>
        </div>

        <ul class="tag-list">
-          <li class="tag-default tag-pill tag-outline">
-            Tag One
-          </li>
-          <li class="tag-default tag-pill tag-outline">
-            Tag Two
-          </li>
+          <li class="tag-default tag-pill tag-outline"
+            ng-repeat="tag in ::$ctrl.article.tagList">
+            {{ tag }}
+          </li>
        </ul>

      </div>
    </div>

[...]

You should now be able to see the title, body and tags show up on the article page! We need to turn the body into HTML from markdown though. We'll use an external package called marked to do this.

Run npm install marked --save
Import marked and invoke it on the article body data. We'll need to tell Angular that this HTML can be trusted. Also, update the title of this page for the browser bar.

article/article.controller.js

+ import marked from 'marked';

class ArticleCtrl {
-  constructor(article) {
+  constructor(article, $sce, $rootScope) {
    'ngInject';

    this.article = article;

+    // Update the title of this page
+    $rootScope.setPageTitle(this.article.title);

+    // Transform the markdown into HTML
+    this.article.body = $sce.trustAsHtml(marked(this.article.body, { sanitize: true }));

  }
}


export default ArticleCtrl;
Change the binding of the article body to ng-bind-html, since it now has HTML contained within it

article/article.html

[...]
    <div class="row article-content">
      <div class="col-xs-12">

        <div>
-          <div ng-bind="::$ctrl.article.body"></div>
+          <div ng-bind-html="::$ctrl.article.body"></div>
        </div>

        <ul class="tag-list">
          <li class="tag-default tag-pill tag-outline"
            ng-repeat="tag in ::$ctrl.article.tagList">
            {{ tag }}
          </li>
        </ul>

      </div>
    </div>
[...]

The markdown should now render properly! There is one bug though - if there's no slug in the URL, the server endpoint returns an array because it thinks we're querying for a list of the latest articles. The fix for this is simple — just update the service to reject the request if there is no slug present.

services/articles.service.js

export default class Articles {
-  constructor(AppConstants, $http) {
+  constructor(AppConstants, $http, $q) {
    'ngInject';

    this._AppConstants = AppConstants;
    this._$http = $http;
+    this._$q = $q;


  }

  // Creates or updates an article
  save(article) {
    [...]
  }

  // Retrieve a single article
  get(slug) {
+    let deferred = this._$q.defer();
+
+    // Check for blank title
+    if (!slug.replace(" ", "")) {
+      deferred.reject("Article slug is empty");
+      return deferred.promise;
+    }

-    return this._$http({
+    this._$http({
+      url: this._AppConstants.api + '/articles/' + slug,
+      method: 'GET'
-    }).then((res) => res.data.article);
+    }).then(
+      (res) => deferred.resolve(res.data.article),
+      (err) => deferred.reject(err)
+    );
+
+    return deferred.promise;

  }

With that bug fixed, our application can now display articles! The next thing we'll do is work on the functionality contained within the article page.

Compartmentalizing page functionality into components

Components are excellent for reusing functionality across your application, but they're also excellent for ensuring you have a solid separation of concerns. In other words, it allows you to break the functionality of your application down into small self contained pieces.

The article page is a great example of where we can take advantage of this aspect of components. At the top and bottom of the page we literally have the same thing showing up where we could reuse a component -- the author's info with follow & favorite buttons. We also have a comments section at the bottom of the page where we could compartmentalize functionality. Lets build out the functionality for these.

A component for showing article meta information

We'll need to show the author's name, image, and the date the article was posted in different places on our site besides the article page. As such, lets make a single component to handle this. Since it will be used elsewhere on the site, we'll create it within the components folder. However, we should create a new subfolder where we'll put all article related components in order to keep the components folder organized.

Create components/article-helpers/ folder, then create the following two files in it

components/article-helpers/article-meta.html

<div class="article-meta">
  <a ui-sref="app.profile({ username:$ctrl.article.author.username })">
    <img ng-src="{{$ctrl.article.author.image}}" />
  </a>

  <div class="info">
    <a class="author"
      ui-sref="app.profile({ username:$ctrl.article.author.username })"
      ng-bind="$ctrl.article.author.username">
    </a>
    <span class="date"
      ng-bind="$ctrl.article.createdAt | date: 'longDate' ">
    </span>
  </div>

  <ng-transclude></ng-transclude>
</div>

components/article-helpers/article-meta.component.js

let ArticleMeta= {
  bindings: {
    article: '='
  },
  transclude: true,
  templateUrl: 'components/article-helpers/article-meta.html'
};

export default ArticleMeta;
Include the ArticleMeta component in components/index.js
import FollowBtn from './buttons/follow-btn.component';
componentsModule.component('followBtn', FollowBtn);

+ import ArticleMeta from './article-helpers/article-meta.component';
+ componentsModule.component('articleMeta', ArticleMeta);

export default componentsModule;
Update the course page to use the ArticleMeta component

article/article.html

<div class="article-page">

  <!-- Banner for article title, action buttons -->
  <div class="banner">
    <div class="container">

      <h1 ng-bind="::$ctrl.article.title"></h1>

-      <div class="article-meta">
-        <!-- Show author info + favorite & follow buttons -->
-        <div class="article-meta">
-          <a href=""><img /></a>
-          <div class="info">
-            <a href="" class="author">Brad Green</a>
-            <span class="date">January 20th</span>
-          </div>
+        <article-meta article="$ctrl.article">
          <button class="btn btn-sm btn-outline-secondary">
            <i class="ion-plus-round"></i>
            &nbsp;
            Follow Brad Green
          </button>
          &nbsp;
          <button class="btn btn-sm btn-outline-primary">
            <i class="ion-heart"></i>
            &nbsp;
            Favorite Article <span class="counter">(29)</span>
          </button>
+        </article-meta>
-        </div>
-      </div>

    </div>
  </div>


  <!-- Main view. Contains article html and comments -->
  <div class="container page">

    <!-- Article's HTML & tags rendered here -->
    <div class="row article-content">
      [...]
    </div>

    <hr />

    <div class="article-actions">

      <!-- Show author info + favorite & follow buttons -->
-      <div class="article-meta">
-          <a href=""><img /></a>
-          <div class="info">
-            <a href="" class="author">Brad Green</a>
-            <span class="date">January 20th</span>
-          </div>
+        <article-meta article="$ctrl.article">
          <button class="btn btn-sm btn-outline-secondary">
            <i class="ion-plus-round"></i>
            &nbsp;
            Follow Brad Green
          </button>
          &nbsp;
          <button class="btn btn-sm btn-outline-primary">
            <i class="ion-heart"></i>
            &nbsp;
            Favorite Article <span class="counter">(29)</span>
          </button>
+        </article-meta>
-      </div>

    </div>

[...]

Excellent, we now have a single file that's powering the article's meta information!

Creating page specific components

You know, having the follow and favorite buttons hard coded in there seems redundant and they need their own logic anyways (actually following/favoriting the user/post on the server). Since this is functionality that is specific to the article page (and thus won't be reused elsewhere on the site), lets create a local component in the article/ folder to do this.

Create the following two files

article/article-actions.html

<article-meta article="$ctrl.article">
  <button class="btn btn-sm btn-outline-secondary">
    <i class="ion-plus-round"></i>
    &nbsp;
    Follow Brad Green
  </button>
  &nbsp;
  <button class="btn btn-sm btn-outline-primary">
    <i class="ion-heart"></i>
    &nbsp;
    Favorite Article <span class="counter">(29)</span>
  </button>
</article-meta>

article/article-actions.component.js

class ArticleActionsCtrl {
  constructor() {
    'ngInject';

  }
}

let ArticleActions = {
  bindings: {
    article: '='
  },
  controller: ArticleActionsCtrl,
  templateUrl: 'article/article-actions.html'
};

export default ArticleActions;
Include the ArticleActions component in article/index.js
[...]

// Controllers
import ArticleCtrl from './article.controller';
articleModule.controller('ArticleCtrl', ArticleCtrl);
+
+import ArticleActions from './article-actions.component';
+articleModule.component('articleActions', ArticleActions);


export default articleModule;
Finally, update article/article.html again and swap out the ArticleMeta component with the ArticleActions component
<div class="article-page">

  <!-- Banner for article title, action buttons -->
  <div class="banner">
    <div class="container">

      <h1 ng-bind="::$ctrl.article.title"></h1>

-      <article-meta article="$ctrl.article">
-        <button class="btn btn-sm btn-outline-secondary">
-          <i class="ion-plus-round"></i>
-          &nbsp;
-          Follow Brad Green
-        </button>
-        &nbsp;
-        <button class="btn btn-sm btn-outline-primary">
-          <i class="ion-heart"></i>
-          &nbsp;
-          Favorite Article <span class="counter">(29)</span>
-        </button>
-      </article-meta>
+      <article-actions article="$ctrl.article"></article-actions>

    </div>
  </div>


  <!-- Main view. Contains article html and comments -->
  <div class="container page">

    <!-- Article's HTML & tags rendered here -->
    <div class="row article-content">
      [...]
    </div>

    <hr />

    <div class="article-actions">

      <!-- Show author info + favorite & follow buttons -->
-      <article-meta article="$ctrl.article">
-        <button class="btn btn-sm btn-outline-secondary">
-          <i class="ion-plus-round"></i>
-          &nbsp;
-          Follow Brad Green
-        </button>
-        &nbsp;
-        <button class="btn btn-sm btn-outline-primary">
-          <i class="ion-heart"></i>
-          &nbsp;
-          Favorite Article <span class="counter">(29)</span>
-        </button>
-      </article-meta>
+      <article-actions article="$ctrl.article"></article-actions>

    </div>

[...]

Nice -- that looks super clean! We're not done yet with the article-actions component yet though; we want to show edit/delete buttons when logged in, and a favorite/follow button when we're not. Lets build the edit/delete buttons first.

Update article/article-actions.html with two new buttons for when this article is ours. Show the follow/favorite buttons when the article isn't ours
<article-meta article="$ctrl.article">

+  <!-- If current user is the author, show edit/delete buttons -->
+  <span ng-show="$ctrl.canModify">
+
+    <a class="btn btn-outline-secondary btn-sm"
+      ui-sref="app.editor({ slug: $ctrl.article.slug })">
+      <i class="ion-edit"></i> Edit Article
+    </a>
+
+    <button class="btn btn-outline-danger btn-sm">
+      <i class="ion-trash-a"></i> Delete Article
+    </button>
+
+  </span>

+  <!-- Otherwise, show favorite & follow buttons -->
+  <span ng-hide="$ctrl.canModify">
    <button class="btn btn-sm btn-outline-secondary">
      <i class="ion-plus-round"></i>
      &nbsp;
      Follow Brad Green
    </button>
    &nbsp;
    <button class="btn btn-sm btn-outline-primary">
      <i class="ion-heart"></i>
      &nbsp;
      Favorite Article <span class="counter">(29)</span>
    </button>
+  </span>
</article-meta>
Now lets create a variable that tells us if they're logged in

article/article-actions.component.js

class ArticleActionsCtrl {
-  constructor() {
+  constructor(User) {
    'ngInject';

+    // The user can only edit/delete this comment if they are the author
+    if (User.current) {
+      this.canModify = (User.current.username === this.article.author.username);
+    } else {
+      this.canModify = false;
+    }

  }
}

[...]

The edit button works! Now lets make that delete button work.

First, add a destroy method to article service
  // Retrieve a single article
  get(slug) {
    [...]
  }

+  // Delete an article
+  destroy(slug) {
+    return this._$http({
+      url: this._AppConstants.api + '/articles/' + slug,
+      method: 'DELETE'
+    });
+  }
In ArticleActionsCtrl, create a delete method that invokes Articles.delete and redirects them to the homepage when successful

article/article-actions.component.js

class ArticleActionsCtrl {
-  constructor(User) {
+  constructor(Articles, User, $state) {
    'ngInject';

+    this._Articles = Articles;
+    this._$state = $state;

    // The user can only edit/delete this comment if they are the author
    if (User.current) {
      this.canModify = (User.current.username === this.article.author.username);
    } else {
      this.canModify = false;
    }

  }

+  deleteArticle() {
+    this.isDeleting = true;
+    this._Articles.destroy(this.article.slug).then(
+      (success) => this._$state.go('app.home'),
+      (err) => this._$state.go('app.home')
+    );
+  }

}

[...]
In the html, add the delete button class & action

article/article-actions.component.html

<article-meta article="$ctrl.article">

  <!-- If current user is the author, show edit/delete buttons -->
  <span ng-show="$ctrl.canModify">

    <a class="btn btn-outline-secondary btn-sm"
      ui-sref="app.editor({ slug: $ctrl.article.slug })">
      <i class="ion-edit"></i> Edit Article
    </a>

-    <button class="btn btn-outline-danger btn-sm">
+    <button class="btn btn-outline-danger btn-sm"
+      ng-class="{disabled: $ctrl.isDeleting}"
+      ng-click="$ctrl.deleteArticle()">
      <i class="ion-trash-a"></i> Delete Article
    </button>

  </span>

  <!-- Otherwise, show favorite & follow buttons -->
  <span ng-hide="$ctrl.canModify">
    <button class="btn btn-sm btn-outline-secondary">
      <i class="ion-plus-round"></i>
      &nbsp;
      Follow Brad Green
    </button>
    &nbsp;
    <button class="btn btn-sm btn-outline-primary">
      <i class="ion-heart"></i>
      &nbsp;
      Favorite Article <span class="counter">(29)</span>
    </button>
  </span>
</article-meta>

Delete works! Finally, lets add the following and favorite buttons.

Implementing the following button is easy - we already made it! Just drop it into the HTML and pass along the author's information

article/article-actions.html

[...]

  <!-- Otherwise, show favorite & follow buttons -->
-  <span ng-hide="$ctrl.canModify">
-    <button class="btn btn-sm btn-outline-secondary">
-      <i class="ion-plus-round"></i>
-      &nbsp;
-      Follow Brad Green
-    </button>
+    <follow-btn user="$ctrl.article.author"></follow-btn>
    &nbsp;
    <button class="btn btn-sm btn-outline-primary">
      <i class="ion-heart"></i>
      &nbsp;
      Favorite Article <span class="counter">(29)</span>
    </button>
  </span>
</article-meta>

Boom! That was super easy, right? Now lets make the favorite button. It will be used on other parts of the site (like the follow button), so lets put it in components/buttons/ as well.

Create components/buttons/favorite-btn.html with the following html
<button class="btn btn-sm btn-outline-primary">

  <i class="ion-heart"></i> <ng-transclude></ng-transclude>

</button>
Create the base component class & object in components/buttons/favorite-btn.component.js
class FavoriteBtnCtrl {
  constructor() {
    'ngInject';


  }
}

let FavoriteBtn= {
  bindings: {
    article: '='
  },
  transclude: true,
  controller: FavoriteBtnCtrl,
  templateUrl: 'components/buttons/favorite-btn.html'
};

export default FavoriteBtn;
Include it in components/index.js
[...]

import ArticleMeta from './article-helpers/article-meta.component';
componentsModule.component('articleMeta', ArticleMeta);

+ import FavoriteBtn from './buttons/favorite-btn.component';
+ componentsModule.component('favoriteBtn', FavoriteBtn);


export default componentsModule;
Add the favorite button component to article/article-actions.html
[...]

  <!-- Otherwise, show favorite & follow buttons -->
  <span ng-hide="$ctrl.canModify">
    <follow-btn user="$ctrl.article.author"></follow-btn>
-    &nbsp;
-    <button class="btn btn-sm btn-outline-primary">
-      <i class="ion-heart"></i>
-      &nbsp;
-      Favorite Article <span class="counter">(29)</span>
-    </button>
+    <favorite-btn article="$ctrl.article">
+      {{ $ctrl.article.favorited ? 'Unfavorite' : 'Favorite' }} Article <span class="counter">({{$ctrl.article.favoritesCount}})</span>
+    </favorite-btn>
  </span>
</article-meta>

And the favorite button now shows up! To wire the favoriting functionality up to the server, we'll need favorite/unfavorite methods on the article service for the favorite button controller to invoke.

Add a favorite and unfavorite methode to services/articles.service.js
[...]

  // Delete an article
  destroy(slug) {
    return this._$http({
      url: this._AppConstants.api + '/articles/' + slug,
      method: 'DELETE'
    });
  }

+  // Favorite an article
+  favorite(slug) {
+    return this._$http({
+      url: this._AppConstants.api + '/articles/' + slug + '/favorite',
+      method: 'POST'
+    });
+  }

+  // Unfavorite an article
+  unfavorite(slug) {
+    return this._$http({
+      url: this._AppConstants.api + '/articles/' + slug + '/favorite',
+      method: 'DELETE'
+    });
+  }

}
Update the favorite button to favorite or unfavorite the post, depending whether we've already favorited it!

components/buttons/favorite-btn.component.js

class FavoriteBtnCtrl {
-  constructor() {
+  constructor(User, Articles, $state) {
    'ngInject';

+    this._Articles = Articles;
+    this._User = User;
+    this._$state = $state;

  }

+  submit() {
+    this.isSubmitting = true;
+
+    if (!this._User.current) {
+      this._$state.go('app.register');
+      return;
+    }
+
+    // If fav'd already, unfavorite it
+    if (this.article.favorited) {
+      this._Articles.unfavorite(this.article.slug).then(
+        () => {
+          this.isSubmitting = false;
+          this.article.favorited = false;
+          this.article.favoritesCount--;
+        }
+      )
+
+    // Otherwise, favorite it
+    } else {
+      this._Articles.favorite(this.article.slug).then(
+        () => {
+          this.isSubmitting = false;
+          this.article.favorited = true;
+          this.article.favoritesCount++;
+        }
+      )
+    }
+  }

}
Hook into the controller's submit method on ng-click and show different CSS styles based on whether the article has/hasn't been favorited, or is currently submitting to the server

components/buttons/favorite-btn.component.js

- <button class="btn btn-sm btn-outline-primary">
+ <button class="btn btn-sm"
+  ng-class="{ 'disabled': $ctrl.isSubmitting,
+              'btn-outline-primary': !$ctrl.article.favorited,
+              'btn-primary': $ctrl.article.favorited }"
+  ng-click="$ctrl.submit()">

  <i class="ion-heart"></i> <ng-transclude></ng-transclude>

</button>

Woot! The favorite button is complete :) Notice the data binding here -- when you favorite at the top of the page, it also is reflected at the bottom of the page automatically. Having two way data binding can make the UX of your applications amazing.

Creating commenting functionality

Lets start off by wiring up the comment form at the bottom of the page to the article controller.

Modify the comment form on article/article.html
[...]

    <!-- Comments section -->
    <div class="row">
      <div class="col-xs-12 col-md-8 offset-md-2">

-        <div>
+        <div show-authed="true">
+          <list-errors from="$ctrl.commentForm.errors"></list-errors>
-          <form class="card comment-form">
+          <form class="card comment-form" ng-submit="$ctrl.addComment()">
+            <fieldset ng-disabled="$ctrl.commentForm.isSubmitting">
              <div class="card-block">
                <textarea class="form-control"
                  placeholder="Write a comment..."
                  rows="3"
+                  ng-model="$ctrl.commentForm.body">
                </textarea>
              </div>
              <div class="card-footer">
-                <img class="comment-author-img" />
+                <img ng-src="{{::$ctrl.currentUser.image}}" class="comment-author-img" />
                <button class="btn btn-sm btn-primary" type="submit">
                  Post Comment
                </button>
              </div>
+           </fieldset>
          </form>
        </div>

+        <p show-authed="false">
+          <a ui-sref="app.login">Sign in</a> or <a ui-sref="app.register">sign up</a> to add comments on this article.
+        </p>


        <div class="card">
          <div class="card-block">
            <p class="card-text">This is an example comment.</p>
          </div>
          <div class="card-footer">
            <a class="comment-author" href="">
              <img class="comment-author-img" />
            </a>
            &nbsp;
            <a class="comment-author" href="">
              BradGreen
            </a>
            <span class="date-posted">
              Jan 20, 2016
            </span>
          </div>
        </div>


      </div>
    </div>

[...]
In ArticleCtrl, expose the currently logged in user's information to the view (for showing their profile picture). Also, add a method for submitting comments that we'll wire up to the server in a minute.

article/article.controller.js

import marked from 'marked';

class ArticleCtrl {
-  constructor(article, $sce, $rootScope) {
+  constructor(article, User, $sce, $rootScope) {
    'ngInject';

    this.article = article;

+    this.currentUser = User.current;

    // Update the title of this page
    $rootScope.setPageTitle(this.article.title);

    // Transform the markdown into HTML
    this.article.body = $sce.trustAsHtml(marked(this.article.body));

+    // Initialize blank comment form
+    this.resetCommentForm();
  }

+  resetCommentForm() {
+    this.commentForm = {
+      isSubmitting: false,
+      body: '',
+      errors: []
+    }
+  }

+  addComment() {
+    this.commentForm.isSubmitting = true;
+
+    // Need to make request to server here
+  }

}


export default ArticleCtrl;

When you submit the form it should be disabled, which means it's wired up properly! We need a way to create, get, and delete comments on the server. Since commenting is a separate piece of functionality from articles, lets create a comments service that will handle all of the server interactions.

Create a Comments service that POSTs new comments to our server

services/comments.service.js

export default class Comments {
  constructor(AppConstants, $http) {
    'ngInject';

    this._AppConstants = AppConstants;
    this._$http = $http;
  }


  // Add a comment to an article
  add(slug, payload) {
    return this._$http({
      url: `${this._AppConstants.api}/articles/${slug}/comments`,
      method: 'POST',
      data: { comment: { body: payload } }
    }).then((res) => res.data.comment);

  }

}
Add to services/index.js
[...]

import ArticlesService from './articles.service';
servicesModule.service('Articles', ArticlesService);

+ import CommentsService from './comments.service';
+ servicesModule.service('Comments', CommentsService);


export default servicesModule;
Update the addComment method in ArticleCtrl to invoke Comments.add with the form data

article/article.controller.js

import marked from 'marked';

class ArticleCtrl {
-  constructor(article, User, $sce, $rootScope) {
+  constructor(article, User, Comments, $sce, $rootScope) {
    'ngInject';

    this.article = article;
+    this._Comments = Comments;

    this.currentUser = User.current;

    [...]
  }

  resetCommentForm() {
    [...]
  }

  addComment() {
    this.commentForm.isSubmitting = true;

-    // Need to make request to server here
+    this._Comments.add(this.article.slug, this.commentForm.body).then(
+      (comment) => {
+        console.log(comment);
+        this.resetCommentForm();
+      },
+      (err) => {
+        this.commentForm.isSubmitting = false;
+        this.commentForm.errors = err.data.errors;
+      }
+    );
  }

[...]

It works! Now lets create a method to get all the comments for a given article and then display them underneath the form.

Create a getAll method in the comments service

services/comments.service.js

export default class Comments {
  constructor(AppConstants, $http) {
    'ngInject';

    this._AppConstants = AppConstants;
    this._$http = $http;
  }


  // Add a comment to an article
  add(slug, payload) {
    return this._$http({
      url: `${this._AppConstants.api}/articles/${slug}/comments`,
      method: 'POST',
      data: { comment: { body: payload } }
    }).then((res) => res.data.comment);

  }

+  // Retrieve the comments for an article
+  getAll(slug) {
+    return this._$http({
+      url: `${this._AppConstants.api}/articles/${slug}/comments`,
+      method: 'GET'
+    }).then((res) => res.data.comments);
+  }

}
In the contructor function of ArticleCtrl, invoke Comments.getAll and expose the result to the view. When a new comment is created successfully from the addComment method, unshift it onto the this.comments array.

article/article.controller.js

import marked from 'marked';

class ArticleCtrl {
  constructor(article, User, Comments, $sce, $rootScope) {
    'ngInject';

    this.article = article;
    this._Comments = Comments;

    this.currentUser = User.current;

    // Update the title of this page
    $rootScope.setPageTitle(this.article.title);

    // Transform the markdown into HTML
    this.article.body = $sce.trustAsHtml(marked(this.article.body));

+    // Get comments for this article
+    Comments.getAll(this.article.slug).then(
+      (comments) => this.comments = comments
+    );

    // Initialize blank comment form
    this.resetCommentForm();
  }

  resetCommentForm() {
    [...]
  }

  addComment() {
    this.commentForm.isSubmitting = true;

    // Need to make request to server here
    this._Comments.add(this.article.slug, this.commentForm.body).then(
      (comment) => {
-        console.log(comment);
+        this.comments.unshift(comment);
        this.resetCommentForm();
      },
      (err) => {
        this.commentForm.isSubmitting = false;
        this.commentForm.errors = err.data.errors;
      }
    );
  }

}


export default ArticleCtrl;
Update article/article.html to show comment data
-        <div class="card">
+        <div class="card" ng-repeat="data in $ctrl.comments">
          <div class="card-block">
-            <p class="card-text">This is an example comment.</p>
+            <p class="card-text" ng-bind="::data.body"></p>
          </div>
          <div class="card-footer">
-            <a class="comment-author" href="">
+            <a class="comment-author"
+              ui-sref="app.profile({ username: data.author.username })">
-              <img class="comment-author-img" />
+              <img ng-src="{{::data.author.image}}" class="comment-author-img" />
            </a>
            &nbsp;
-            <a class="comment-author" href="">
-              BradGreen
-            </a>
+            <a class="comment-author"
+              ui-sref="app.profile({ username: data.author.username })"
+              ng-bind="::data.author.username">
+            </a>
-            <span class="date-posted">
-              Jan 20, 2016
-            </span>
+            <span class="date-posted"
+              ng-bind="::data.createdAt | date: 'longDate' ">
+            </span>
          </div>
        </div>

It works! We need to add a delete button if this is the author though. We've added a lot of stuff to the article controller and template, so we should really modularize this a bit. Lets create a component for comments that will handle this logic.

Add the HTML for a new component called 'comment' in place of our old comment HTML.

article/article.html

[...]

    <!-- Comments section -->
    <div class="row">
      <div class="col-xs-12 col-md-8 offset-md-2">

        [...]

        <p show-authed="false">
          <a ui-sref="app.login">Sign in</a> or <a ui-sref="app.register">sign up</a> to add comments on this article.
        </p>

-        <div class="card" ng-repeat="data in $ctrl.comments">
-          <div class="card-block">
-            <p class="card-text" ng-bind="::data.body"></p>
-          </div>
-          <div class="card-footer">
-            <a class="comment-author"
-              ui-sref="app.profile({ username: data.author.username })">
-              <img ng-src="{{::data.author.image}}" class="comment-author-img" />
-            </a>
-            &nbsp;
-            <a class="comment-author"
-              ui-sref="app.profile({ username: data.author.username })"
-              ng-bind="::data.author.username">
-            </a>
-            <span class="date-posted"
-              ng-bind="::data.createdAt | date: 'longDate' ">
-            </span>
-          </div>
-        </div>
+        <comment ng-repeat="cmt in $ctrl.comments"
+          data="cmt">
+        </comment>


      </div>
    </div>

[...]
Create article/comment.html with the HTML we deleted above
<div class="card" ng-repeat="data in $ctrl.comments">
  <div class="card-block">
    <p class="card-text" ng-bind="::data.body"></p>
  </div>
  <div class="card-footer">
    <a class="comment-author"
      ui-sref="app.profile({ username: data.author.username })">
      <img ng-src="{{::data.author.image}}" class="comment-author-img" />
    </a>
    &nbsp;
    <a class="comment-author"
      ui-sref="app.profile({ username: data.author.username })"
      ng-bind="::data.author.username">
    </a>
    <span class="date-posted"
      ng-bind="::data.createdAt | date: 'longDate' ">
    </span>
  </div>
</div>
Create our base comment component javascript in article/comment.component.js
class CommentCtrl {
  constructor() {
    'ngInject';

  }
}

let Comment = {
  bindings: {
    data: '='
  },
  controller: CommentCtrl,
  templateUrl: 'article/comment.html'
};

export default Comment;
Include it in article/index.js
[...]

import ArticleActions from './article-actions.component';
articleModule.component('articleActions', ArticleActions);

+ import Comment from './comment.component';
+ articleModule.component('comment', Comment);


export default articleModule;

Hmm, it doesn't seem to work... Oh! We need to update the comment template to simply use the $ctrl variable and remove its ng-repeat, since ng-repeat is being called upon it in article.html

article/comment.html

- <div class="card" ng-repeat="data in $ctrl.comments">
+ <div class="card">
  <div class="card-block">
-    <p class="card-text" ng-bind="::data.body"></p>
+    <p class="card-text" ng-bind="::$ctrl.data.body"></p>
  </div>
  <div class="card-footer">
    <a class="comment-author"
-      ui-sref="app.profile({ username: data.author.username })">
+      ui-sref="app.profile({ username: $ctrl.data.author.username })">
-      <img ng-src="{{::data.author.image}}" class="comment-author-img" />
+      <img ng-src="{{::$ctrl.data.author.image}}" class="comment-author-img" />
    </a>
    &nbsp;
    <a class="comment-author"
-      ui-sref="app.profile({ username: data.author.username })"
+      ui-sref="app.profile({ username: $ctrl.data.author.username })"
-      ng-bind="::data.author.username">
+      ng-bind="::$ctrl.data.author.username">
    </a>
    <span class="date-posted"
-      ng-bind="::data.createdAt | date: 'longDate' ">
+      ng-bind="::$ctrl.data.createdAt | date: 'longDate' ">
    </span>
  </div>
</div>

Now it works! Lets add a delete button that only shows up when the current user is the comment owner.

Create a canModify variable for the comment component. This will tell our view whether it should show the delete button.

article/comment.component.js

class CommentCtrl {
-  constructor() {
+  constructor(User) {
    'ngInject';

+    // The user can only delete this comment if they are the author
+    if (User.current) {
+      this.canModify = (User.current.username === this.data.author.username);
+    } else {
+      this.canModify = false;
+    }

  }

}

[...]
Add a delete button in article/comment.html
<div class="card">
  <div class="card-block">
    <p class="card-text" ng-bind="::$ctrl.data.body"></p>
  </div>
  <div class="card-footer">
    <a class="comment-author"
      ui-sref="app.profile({ username: $ctrl.data.author.username })">
      <img ng-src="{{::$ctrl.data.author.image}}" class="comment-author-img" />
    </a>
    &nbsp;
    <a class="comment-author"
      ui-sref="app.profile({ username: $ctrl.data.author.username })"
      ng-bind="::$ctrl.data.author.username">
    </a>
    <span class="date-posted"
      ng-bind="::$ctrl.data.createdAt | date: 'longDate' ">
    </span>
+    <span class="mod-options" ng-show="$ctrl.canModify">
+      <i class="ion-trash-a"></i>
+    </span>
  </div>
</div>
Create a delete method in the comments service

services/comments.service.js

export default class Comments {
  constructor(AppConstants, $http) {
    'ngInject';

    this._AppConstants = AppConstants;
    this._$http = $http;
  }


  // Add a comment to an article
  add(slug, payload) {
    [...]
  }

+  // Delete a comment from an article
+  destroy(commentId, articleSlug) {
+    return this._$http({
+      url: `${this._AppConstants.api}/articles/${articleSlug}/comments/${commentId}`,
+      method: 'DELETE'
+    });
+  }

  // Retrieve the comments for an article
  getAll(slug) {
    [...]
  }

}

In the future, we might want to be able to delete comments from the article controller itself. So lets create the method there and then pass it down for the component to use.

Create a deleteComment method in ArticleCtrl
[...]

  addComment() {
    [...]
  }

+  deleteComment(commentId, index) {
+    this._Comments.destroy(commentId, this.article.slug).then(
+      (success) => {
+        this.comments.splice(index, 1);
+      }
+    );
+  }

}


export default ArticleCtrl;
Pass the function in through a new attribute on the comment component

article/article.html

[...]

        <comment ng-repeat="cmt in $ctrl.comments"
-          data="cmt">
+          data="cmt"
+          delete-cb="$ctrl.deleteComment(cmt.id, $index)">
        </comment>

[...]
Accept the new attribute in the comment component

article/comment.component.js

[...]

let Comment = {
  bindings: {
-    data: '='
+    data: '=',
+    deleteCb: '&'
  },
  controller: CommentCtrl,
  templateUrl: 'article/comment.html'
};

export default Comment;
And then invoke the delete callback with ng-click

article/comment.html

<div class="card">
  <div class="card-block">
    <p class="card-text" ng-bind="::$ctrl.data.body"></p>
  </div>
  <div class="card-footer">
    <a class="comment-author"
      ui-sref="app.profile({ username: $ctrl.data.author.username })">
      <img ng-src="{{::$ctrl.data.author.image}}" class="comment-author-img" />
    </a>
    &nbsp;
    <a class="comment-author"
      ui-sref="app.profile({ username: $ctrl.data.author.username })"
      ng-bind="::$ctrl.data.author.username">
    </a>
    <span class="date-posted"
      ng-bind="::$ctrl.data.createdAt | date: 'longDate' ">
    </span>
    <span class="mod-options" ng-show="$ctrl.canModify">
-      <i class="ion-trash-a"></i>
+      <i class="ion-trash-a" ng-click="$ctrl.deleteCb()"></i>
    </span>
  </div>
</div>

And it works! Bad ass. Our article page is totally complete now. However, we still need to add lists of articles to profiles and our home page. Lets start with profiles first.

Creating abstract states and child states in UI-Router

We want our profile page to show articles that the user created, and articles that the user favorited. Ideally these two pages would have different routes (/#/@username and /#/@username/favorites), so lets make that happen.

Set app.profile as an abstract state, and create two new states app.profile.main and app.profile.favorites. Both of these states will use ProfileArticlesCtrl as the controller and profile/profile-articles.html as the templateUrl

profile/profile.config.js

function ProfileConfig($stateProvider) {
  'ngInject';

  $stateProvider
  .state('app.profile', {
+    abstract: true,
    url: '/@:username',
    controller: 'ProfileCtrl',
    controllerAs: '$ctrl',
    templateUrl: 'profile/profile.html',
    // When the controller loads, the title of the web page
    // will be changed to the username of this profile
    resolve: {
      profile: function(Profile, $state, $stateParams) {
        return Profile.get($stateParams.username).then(
          (profile) => profile,
          (err) => $state.go('app.home')
        );
      }
    }
  })

+  .state('app.profile.main', {
+    url:'',
+    controller: 'ProfileArticlesCtrl',
+    controllerAs: '$ctrl',
+    templateUrl: 'profile/profile-articles.html',
+    title: 'Profile'
+  })

+  .state('app.profile.favorites', {
+    url:'/favorites',
+    controller: 'ProfileArticlesCtrl',
+    controllerAs: '$ctrl',
+    templateUrl: 'profile/profile-articles.html',
+    title: 'Favorites'
+  });

};

export default ProfileConfig;
Replace the placeholder list of articles with a ui-view

profile/profile.html

[...]

-        <!-- List of articles -->
-        <div class="article-preview">
-          <div class="article-meta">
-            <a href=""><img /></a>
-            <div class="info">
-              <a href="" class="author">BradGreen</a>
-              <span class="date">January 20th</span>
-            </div>
-            <button class="btn btn-outline-primary btn-sm pull-xs-right">
-              <i class="ion-heart"></i> 29
-            </button>
-          </div>
-          <a href="" class="preview-link">
-            <h1>How to build Angular apps that scale</h1>
-            <p>Building web applications is not an easy task. It's even hard to make ones that scale.</p>
-            <span>Read more...</span>
-            <ul class="tag-list">
-              <li class="tag-default tag-pill tag-outline">programming</li>
-              <li class="tag-default tag-pill tag-outline">web</li>
-            </ul>
-          </a>
-        </div>
+        <!-- View where profile-articles renders to -->
+        <div ui-view></div>

[...]
Create a new controller named ProfileArticlesCtrl. We'll be injecting profile which gets resolved from ui-router, and $state. We're also calling replace on the current state name to get the child state name and setting it to profileState

profile/profile-articles.controller.js

class ProfileArticlesCtrl {
  constructor(profile, $state) {
    'ngInject';

    // The profile for this page, resolved by UI Router
    this.profile = profile;

    this.profileState = $state.current.name.replace('app.profile.', '');


  }
}


export default ProfileArticlesCtrl;
Create a simple template to test this out before adding more functionality

profile/profile-articles.html

<p>Current state is {{ $ctrl.profileState }}</p>
Import ProfileArticlesCtrl in the profile index file and register it with the profile module

profile/index.js

[...]

import ProfileCtrl from './profile.controller';
profileModule.controller('ProfileCtrl', ProfileCtrl);

+ import ProfileArticlesCtrl from './profile-articles.controller';
+ profileModule.controller('ProfileArticlesCtrl', ProfileArticlesCtrl);


export default profileModule;

Nice! You can now navigate to /#/@username/favorites in the URL bar. How about we fix the tabs to allow us to click between the main and favorites states?

Add the ui-sref and ui-sref-active directives to the tabs on the profile page to route to the main and favorite states we just created.

profile/profile.html

        <!-- Tabs for switching between author articles & favorites -->
        <div class="articles-toggle">
          <ul class="nav nav-pills outline-active">

            <li class="nav-item">
-              <a class="nav-link active">
+              <a class="nav-link"
+                ui-sref-active="active"
+                ui-sref="app.profile.main({username: $ctrl.profile.username})">
                My Articles
              </a>
            </li>

            <li class="nav-item">
-              <a class="nav-link">
+              <a class="nav-link"
+                ui-sref-active="active"
+                ui-sref="app.profile.favorites({username: $ctrl.profile.username})">
                Favorited Articles
              </a>
            </li>

          </ul>
        </div>

Cool, you can toggle between tabs now! However, all of the links we made to profiles on the site are busted now because they have to be profile.main and not just profile.

Update the profile link in our header to link to the new app.profile.main state

layout/header.html

      <li class="nav-item">
        <a class="nav-link"
          ui-sref-active="active"
-          ui-sref="app.profile({ username: $ctrl.currentUser.username })">
+          ui-sref="app.profile.main({ username: $ctrl.currentUser.username })">
          <img ng-src="{{$ctrl.currentUser.image}}" class="user-pic"/>
          {{ $ctrl.currentUser.username }}
        </a>
      </li>
Update the profile link for our articles metadata

components/article-helpers/article-meta.html

<div class="article-meta">
-  <a ui-sref="app.profile({ username:$ctrl.article.author.username })">
+  <a ui-sref="app.profile.main({ username:$ctrl.article.author.username })">
    <img ng-src="{{$ctrl.article.author.image}}" />
  </a>

  <div class="info">
    <a class="author"
-      ui-sref="app.profile({ username:$ctrl.article.author.username })"
+      ui-sref="app.profile.main({ username:$ctrl.article.author.username })"
      ng-bind="$ctrl.article.author.username">
    </a>
    <span class="date"
      ng-bind="$ctrl.article.createdAt | date: 'longDate' ">
    </span>
  </div>

  <ng-transclude></ng-transclude>
</div>
Update the profile links for comments

article/comment.html

<div class="card">
  <div class="card-block">
    <p class="card-text" ng-bind="::$ctrl.data.body"></p>
  </div>
  <div class="card-footer">
    <a class="comment-author"
-      ui-sref="app.profile({ username: $ctrl.data.author.username })">
+      ui-sref="app.profile.main({ username: $ctrl.data.author.username })">
      <img ng-src="{{::$ctrl.data.author.image}}" class="comment-author-img" />
    </a>
    &nbsp;
    <a class="comment-author"
-      ui-sref="app.profile({ username: $ctrl.data.author.username })"
+      ui-sref="app.profile.main({ username: $ctrl.data.author.username })"
      ng-bind="::$ctrl.data.author.username">
    </a>
    <span class="date-posted"
      ng-bind="::$ctrl.data.createdAt | date: 'longDate' ">
    </span>
    <span class="mod-options" ng-show="$ctrl.canModify">
      <i class="ion-trash-a" ng-click="$ctrl.deleteCb()"></i>
    </span>
  </div>
</div>
Update the state transition on successful profile update to the new app.profile.main state

settings/settings.controller.js

[...]

  submitForm() {
    this.isSubmitting = true;
    this._User.update(this.formData).then(
      (user) => {
-        this._$state.go('app.profile', {username:user.username});
+        this._$state.go('app.profile.main', {username:user.username});
      },
      (err) => {
        this.isSubmitting = false;
        this.errors = err.data.errors;
      }
    )
  }

[...]

Sweet! We're now ready to start showing lists of articles :)

Displaying lists of articles

We'll want to display lists of articles on both the home page and the profile page. To get started, lets build this out on the profile page. The first thing we'll need to do is create a wrapper for querying the server's API for lists of articles.

Create a method named query for preparing requests to fetch articles. This method will accept an configuration object which will specify if we're retrieving all articles or feed articles. If we've provided all as the option, we have the ability to set query parameters, allowing us to filter by author, a user's favorites, or by tag.

services/articles.service.js

[...]

  // Unfavorite an article
  unfavorite(slug) {
    return this._$http({
      url: this._AppConstants.api + '/articles/' + slug + '/favorite',
      method: 'DELETE'
    });
  }

+  /*
+    Config object spec:
+
+    {
+      type: String [REQUIRED] - Accepts "all", "feed"
+      filters: Object that serves as a key => value of URL params (i.e. {author:"ericsimons"} )
+    }
+  */
+  query(config) {
+
+    // Create the $http object for this request
+    let request = {
+      url: this._AppConstants.api + '/articles' + ((config.type === 'feed') ? '/feed' : ''),
+      method: 'GET',
+      params: config.filters ? config.filters : null
+    };
+    return this._$http(request).then((res) => res.data);
+  }


}

We'll be calling this method from our ArticleListCtrl later for retrieving the appropriate list of articles. Next up, let's create a template for previewing a list of articles. We'll make this a component so we can use it in multiple places.

Create the template for article previews

components/article-helpers/article-preview.html

<div class="article-preview">
  <article-meta article="$ctrl.article">
    <favorite-btn
      article="$ctrl.article"
      class="pull-xs-right">
      {{$ctrl.article.favoritesCount}}
    </favorite-btn>
  </article-meta>

  <a ui-sref="app.article({ slug: $ctrl.article.slug })" class="preview-link">
    <h1 ng-bind="$ctrl.article.title"></h1>
    <p ng-bind="$ctrl.article.description"></p>
    <span>Read more...</span>
    <ul class="tag-list">
      <li class="tag-default tag-pill tag-outline"
        ng-repeat="tag in $ctrl.article.tagList">
        {{tag}}
      </li>
    </ul>
  </a>
</div>
Create the component for article previews

components/article-helpers/article-preview.component.js

let ArticlePreview = {
  bindings: {
    article: '='
  },
  templateUrl: 'components/article-helpers/article-preview.html'
};

export default ArticlePreview;
Import the ArticlePreview component in our components index file, and register it with componentsModule

components/index.js

[...]

+ import ArticlePreview from './article-helpers/article-preview.component';
+ componentsModule.component('articlePreview', ArticlePreview);


export default componentsModule;

Like we mentioned earlier, we also need to show lists of articles on the home page. So lets create a component that can be dropped in and "just works" for retrieving, iterating, and paginating lists of articles.

Create a new HTML file for the article-list component. We'll need to ng-repeat over the articles from the controller and pass the articles down to the article-preview component. We'll also include a couple loading messages for displaying the loading state to the user.

components/article-helpers/article-list.html

<article-preview
  article="article"
  ng-repeat="article in $ctrl.list">
</article-preview>

<div class="article-preview"
  ng-hide="!$ctrl.loading">
  Loading articles...
</div>

<div class="article-preview"
  ng-show="!$ctrl.loading && !$ctrl.list.length">
  No articles are here... yet.
</div>
Our component requires a limit for pagination, as well as the configuration for the query. Let's pass those values down to our component.

profile/profile-articles.html

- <p>Current state is {{ $ctrl.profileState }}</p>
+ <article-list limit="5" list-config="$ctrl.listConfig"></article-list>
Next we need to make a few updates to ProfileArticlesCtrl. Here we're preparing the query for the component, as well as setting the page title based off of what the user has selected.

profile/profile-articles.component.js

class ProfileArticlesCtrl {
+  constructor(profile, $state, $rootScope) {
    'ngInject';

    // The profile for this page, resolved by UI Router
    this.profile = profile;

    this.profileState = $state.current.name.replace('app.profile.', '');

+    // Both favorites and author articles require the 'all' type
+    this.listConfig = { type: 'all' };
+
+    // `main` state's filter should be by author
+    if (this.profileState === 'main') {
+      this.listConfig.filters = {author: this.profile.username};
+      // Set page title
+      $rootScope.setPageTitle('@' + this.profile.username);
+
+    } else if (this.profileState === 'favorites') {
+      this.listConfig.filters = {favorited: this.profile.username};
+      // Set page title
+      $rootScope.setPageTitle(`Articles favorited by ${this.profile.username}`);
+    }

  }
}

export default ProfileArticlesCtrl;
Now let's create the component's controller

components/article-helpers/article-list.component.js

class ArticleListCtrl {
  constructor(Articles) {
    'ngInject';

    this._Articles = Articles;

    this.setListTo(this.listConfig);

  }

  setListTo(newList) {
    // Set the current list to an empty array
    this.list = [];

    // Set listConfig to the new list's config
    this.listConfig = newList;

    this.runQuery();
  }

  runQuery() {
    // Show the loading indicator
    this.loading = true;

    // Create an object for this query
    let queryConfig = {
      type: this.listConfig.type,
      filters: this.listConfig.filters || {}
    };

    // Set the limit filter from the component's attribute
    queryConfig.filters.limit = this.limit;

    // Run the query
    this._Articles
      .query(queryConfig)
      .then(
        (res) => {
          this.loading = false;

          // Update list and total pages
          this.list = res.articles;
        }
      );
  }

}

let ArticleList = {
  bindings: {
    limit: '=',
    listConfig: '='
  },
  controller: ArticleListCtrl,
  templateUrl: 'components/article-helpers/article-list.html'
};

export default ArticleList;
With the component ready to be used, let's import it in our components index, and register it with our application

components/index.js

[...]

import ArticlePreview from './article-helpers/article-preview.component';
componentsModule.component('articlePreview', ArticlePreview);

+ import ArticleList from './article-helpers/article-list.component';
+ componentsModule.component('articleList', ArticleList);


export default componentsModule;

Now we should be able to see our ArticleList component on our profile pages!

Adding pagination to lists

If you think about it, pagination functionality just needs to know 2 things: the current number of pages, and which page we're currently on. When a different page is selected, the pagination needs to relay that information to the article-list component.

Pass down the current page we're on and the total number of pages there are to our listConfig

components/article-helpers/article-list.component.js

[...]

  runQuery() {
    // Show the loading indicator
    this.loading = true;

    // Create an object for this query
    let queryConfig = {
      type: this.listConfig.type,
      filters: this.listConfig.filters || {}
    };

    // Set the limit filter from the component's attribute
    queryConfig.filters.limit = this.limit;

+    // If there is no page set, set page as 1
+    if (!this.listConfig.currentPage) {
+      this.listConfig.currentPage = 1;
+    }

+    // Add the offset filter
+    queryConfig.filters.offset = (this.limit * (this.listConfig.currentPage - 1));

    // Run the query
    this._Articles
      .query(queryConfig)
      .then(
        (res) => {
          this.loading = false;

          // Update list and total pages
          this.list = res.articles;

+          this.listConfig.totalPages = Math.ceil(res.articlesCount / this.limit);
        }
      );
  }

[...]
Create a list-pagination component in our article list template. This is where we'll have buttons for the user to navigate between pages of articles.

components/article-helpers/article-list.html

<article-preview
  article="article"
  ng-repeat="article in $ctrl.list">
</article-preview>

<div class="article-preview"
  ng-hide="!$ctrl.loading">
  Loading articles...
</div>

<div class="article-preview"
  ng-show="!$ctrl.loading && !$ctrl.list.length">
  No articles are here... yet.
</div>

+ <list-pagination
+  total-pages="$ctrl.listConfig.totalPages"
+  current-page="$ctrl.listConfig.currentPage"
+  ng-hide="$ctrl.listConfig.totalPages <= 1">
+ </list-pagination>
Create the pagination component.

components/article-helpers/list-pagination.component.js

class ListPaginationCtrl {
  constructor() {
    'ngInject';

  }

  pageRange(total) {
    let pages = [];

    for (var i = 0; i < total; i++) {
      pages.push(i + 1)
    }

    return pages;
  }

}

let ListPagination= {
  bindings: {
    totalPages: '=',
    currentPage: '='
  },
  controller: ListPaginationCtrl,
  templateUrl: 'components/article-helpers/list-pagination.html'
};

export default ListPagination;
Create the template for the pagination component

components/article-helpers/list-pagination.html

<nav>
  <ul class="pagination">

    <li class="page-item"
      ng-class="{active: pageNumber === $ctrl.currentPage }"
      ng-repeat="pageNumber in $ctrl.pageRange($ctrl.totalPages)">

      <a class="page-link" href="">{{ pageNumber }}</a>

    </li>

  </ul>
</nav>
Import the ListPagination component in our index and register it with our application.

components/index.js

[...]

import ArticleList from './article-helpers/article-list.component';
componentsModule.component('articleList', ArticleList);

+ import ListPagination from './article-helpers/list-pagination.component';
+ componentsModule.component('listPagination', ListPagination);


export default componentsModule;

Pagination should now work! However, we need to enable changing pages from the pagination component. We'll do this by using $scope.$emit and $scope.broadcast to send information between controllers.

Add a method on ArticleListCtrl to listen & respond to page changes.

components/article-helpers/article-list.component.js

class ArticleListCtrl {
-  constructor(Articles) {
+  constructor(Articles, $scope) {
    'ngInject';

    this._Articles = Articles;

    this.setListTo(this.listConfig);

+    $scope.$on('setPageTo', (ev, pageNumber) => {
+      this.setPageTo(pageNumber);
+    });

  }

  setListTo(newList) {
    // Set the current list to an empty array
    this.list = [];

    // Set listConfig to the new list's config
    this.listConfig = newList;

    this.runQuery();
  }

+  setPageTo(pageNumber) {
+    this.listConfig.currentPage = pageNumber;
+
+    this.runQuery();
+  }


[...]

ArticleListCtrl will listen to the setPageTo event, which the pagination component will fire when the user wants to view a different page.

Add a changePage method to ListPaginationCtrl to emit the setPageTo event. We'll also need to inject $scope to our controller.

components/article-helpers/list-pagination.component.js

class ListPaginationCtrl {
-  constructor() {
+  constructor($scope) {
    'ngInject';

+    this._$scope = $scope;
  }

  pageRange(total) {
    [...]
  }

+  changePage(number) {
+    this._$scope.$emit('setPageTo', number);
+  }
}

[...]
Add a ng-click for changing the page number to our pagination component

components/article-helpers/list-pagination.html

<nav>
  <ul class="pagination">

    <li class="page-item"
      ng-class="{active: pageNumber === $ctrl.currentPage }"
      ng-repeat="pageNumber in $ctrl.pageRange($ctrl.totalPages)"
+      ng-click="$ctrl.changePage(pageNumber)">

      <a class="page-link" href="">{{ pageNumber }}</a>

    </li>

  </ul>
</nav>

Cool - it all works! Last but not least, lets drop the article-list component into our home page :)

Reusing the article-list component on the home page

There are three article-list configurations that we want to implement on the home page: feed articles (articles that were authored by users you follow), global articles (all articles that have been posted to the site), and articles that contain a certain tag. We already have the functionality for feed articles and global articles, so lets work on showing articles with specific tags.

The first thing we need to do is populate the tag list on the homepage from the server.

Create a Tags service that retrieves all of tags on the site

services/tags.service.js

export default class Tags {
  constructor(AppConstants, $http) {
    'ngInject';

    this._AppConstants = AppConstants;
    this._$http = $http;


  }

  getAll() {

    return this._$http({
      url: this._AppConstants.api + '/tags',
      method: 'GET',
    }).then((res) => res.data.tags);

  }


}
Import the tags service we created and register it with our application.

services/index.js

[...]

import CommentsService from './comments.service';
servicesModule.service('Comments', CommentsService);

+ import TagsService from './tags.service';
+ servicesModule.service('Tags', TagsService);


export default servicesModule;
Create a controller for the home page

home/home.controller.js

class HomeCtrl {
  constructor(AppConstants) {
    'ngInject';

    this.appName = AppConstants.appName;

  }
}

export default HomeCtrl;
Update the markup in our homepage template to iterate over a list of tags as well as show loading indicators

home/home.html

      <!-- Sidebar where popular tags are listed -->
      <div class="col-md-3">
        <div class="sidebar">

          <p>Popular Tags</p>

-          <div class="tag-list">
-            <a href="" class="tag-default tag-pill">
-              Tag One
-            </a>
-            <a href="" class="tag-default tag-pill">
-              Tag Two
-            </a>
-          </div>
+          <div class="tag-list" ng-show="$ctrl.tags">
+            <a href="" class="tag-default tag-pill"
+              ng-repeat="tagName in $ctrl.tags"
+              ng-bind="tagName">
+            </a>
+          </div>

+          <div ng-show="!$ctrl.tagsLoaded">
+            Loading tags...
+          </div>

+          <div class="post-preview"
+            ng-show="$ctrl.tagsLoaded && !$ctrl.tags.length">
+            No tags are here... yet.
+          </div>

        </div>
      </div>
To make the tags show up, call Tags.getAll and set the result to this.tags. Let's get the article list component on this page as well; create a config object in controller that we'll pass to the component.

home/home.controller.js

class HomeCtrl {
-  constructor(Tags, AppConstants) {
+  constructor(User, Tags, AppConstants) {
    'ngInject';

    this.appName = AppConstants.appName;

+    // Get list of all tags
+    Tags
+      .getAll()
+      .then(
+        (tags) => {
+          this.tagsLoaded = true;
+          this.tags = tags
+        }
+      );


+    // Set current list to either feed or all, depending on auth status.
+    this.listConfig = {
+      type: User.current ? 'feed' : 'all'
+    };

  }


}

export default HomeCtrl;
Replace the placeholder markup for articles on the home page with the article-list component we've made.

home/home.html

[...]

        <!-- List the current articles -->
-        <div class="article-preview">
-          <div class="article-meta">
-            <a href=""><img /></a>
-            <div class="info">
-              <a href="" class="author">BradGreen</a>
-              <span class="date">January 20th</span>
-            </div>
-            <button class="btn btn-outline-primary btn-sm pull-xs-right">
-              <i class="ion-heart"></i> 29
-            </button>
-          </div>
-          <a href="" class="preview-link">
-            <h1>How to build Angular apps that scale</h1>
-            <p>Building web applications is not an easy task. It's even hard to make ones that scale.</p>
-            <span>Read more...</span>
-            <ul class="tag-list">
-              <li class="tag-default tag-pill tag-outline">programming</li>
-              <li class="tag-default tag-pill tag-outline">web</li>
-            </ul>
-          </a>
-        </div>
+        <article-list limit="10" list-config="$ctrl.listConfig"></article-list>

[...]

We can see the feed now, but we need to have a way to toggle between the different feeds. We'll need to add a listener in the ArticleList component for this.

Have the articlelist component listen for the setListTo event to toggle between different types of lists.

components/article-helpers/article-list.component.js

class ArticleListCtrl {
  constructor(Articles, $scope) {
    'ngInject';

    this._Articles = Articles;

    this.setListTo(this.listConfig);


+    $scope.$on('setListTo', (ev, newList) => {
+      this.setListTo(newList);
+    });

    $scope.$on('setPageTo', (ev, pageNumber) => {
      this.setPageTo(pageNumber);
    });

  }

Add a method to HomeCtrl for broadcasting the setListTo event. We'll also need to inject $scope to our controller.

home/home.controller.js

class HomeCtrl {
-  constructor(User, Tags, AppConstants) {
+  constructor(User, Tags, AppConstants, $scope) {
    'ngInject';

    this.appName = AppConstants.appName;
+    this._$scope = $scope;

    // Get list of all tags
    Tags
      .getAll()
      .then(
        (tags) => {
          this.tagsLoaded = true;
          this.tags = tags
        }
      );

    // Set current list to either feed or all, depending on auth status.
    this.listConfig = {
      type: User.current ? 'feed' : 'all'
    };

  }

+  changeList(newList) {
+    this._$scope.$broadcast('setListTo', newList);
+  }


}

export default HomeCtrl;
Update the markup on the home page to toggle between global, following, and tagged articles
[...]

      <!-- Main view - contains tabs & article list -->
      <div class="col-md-9">
        <!-- Tabs for toggling between feed, article lists -->
        <div class="feed-toggle">
          <ul class="nav nav-pills outline-active">

            <li class="nav-item" show-authed="true">
-              <a href="" class="nav-link active">
+              <a href="" class="nav-link"
+                ng-class="{ active: $ctrl.listConfig.type === 'feed' }"
+                ng-click="$ctrl.changeList({ type: 'feed' })">
                Your Feed
              </a>
            </li>

            <li class="nav-item">
-              <a href="" class="nav-link">
+              <a href="" class="nav-link"
+                ng-class="{ active: $ctrl.listConfig.type === 'all' && !$ctrl.listConfig.filters }"
+                ng-click="$ctrl.changeList({ type: 'all' })">
                Global Feed
              </a>
            </li>

+            <li class="nav-item" ng-show="$ctrl.listConfig.filters.tag">
+              <a href="" class="nav-link active">
+                <i class="ion-pound"></i> {{$ctrl.listConfig.filters.tag}}
+              </a>
+            </li>

          </ul>
        </div>

        <!-- List the current articles -->
        <article-list limit="10" list-config="$ctrl.listConfig"></article-list>

      </div>

      <!-- Sidebar where popular tags are listed -->
      <div class="col-md-3">
        <div class="sidebar">

          <p>Popular Tags</p>

          <div class="tag-list" ng-show="$ctrl.tags">
            <a href="" class="tag-default tag-pill"
+              ng-click="$ctrl.changeList({ type: 'all', filters: { tag: tagName } })"
              ng-repeat="tagName in $ctrl.tags"
              ng-bind="tagName">
            </a>
          </div>

          <div ng-show="!$ctrl.tagsLoaded">
            Loading tags...
          </div>

          <div class="post-preview"
            ng-show="$ctrl.tagsLoaded && !$ctrl.tags.length">
            No tags are here... yet.
          </div>

        </div>
      </div>

[...]

And with that, our application is complete! Nice work!!

Building it in Angular2

Stay tuned! We have an upcoming course that will show you how to recreate Conduit in Angular2 by reusing the code you wrote in Angular 1.5. To stay in the loop, make sure you're subscribed to our newsletter (right below this section) and follow us on Twitter @GoThinkster and/or myself @EricSimons40!