In Part 1 of our tutorial on How to Build an Etsy Clone, we got done with configuring Stamplay as the backend API for our entire application. We also integrated Facebook logins. And the best part about it, we did all of that without a single line of code!

Let's pick up where we left off and move onto the front-end side of our application. This is where we get our hands dirty with HTML/CSS/JS. This is also where Angular comes heavily into play. The end of this chapter will look like:

Getting Your Project Ready

If you go look at your new Stamplay application's home page, you'll see that Stamplay has laid out clear instructions to get started.

The steps are pretty much to grab the Stamplay CLI tools and then use those to push our project to Stamplay.

Installing the Stamplay Tools

Here are the steps that we'll follow along with:

Note: You need Node.js installed to have npm available to you.

Install Stamplay CLI tools: npm install -g stamplay-cli Update the Stamplay tools: npm update -g stamplay-cli

Once we have the Stamplay tools, we can push our project up so that it is visible at <angularetsy.stamplayapp.com>.

Deploying Our Project

Let's initialize our Stamplay project with the following commands:

Choose a directory and start a Stamplay project:

$ cd angular-etsy-stamplay $ stamplay init

The stamplay init command will ask you for your App ID and Api key. Remember that these should be kept secret since the Api key provides god-like powers (full access to create, update, and delete content).

Your App ID will be the name of your application and the Api key can be found in your app's Stamplay dashboard:

Now we can try to deploy our application using:

$ stamplay deploy

By default, Stamplay sets the index.html file as the start for our application. This is set in the stamplay.json file that was created when we ran stamplay init .

Since we haven't our index.html file, Stamplay won't let us deploy. Let's start our application now and lay out the foundation of our Angular app.

Starting Our Angular Application

Our Angular app will be very straightforward. We'll use ui-router for routing and have the following pages:

Home Page/Product Listings Page

Shop Page

Shop Admin Page

Single Product Page

Checkout Page

User Profile Page

Login/Signup Page

With those pages in mind, let's get started.

Directory Structure

Here is our directory structure for our Angular application. We'll be using Angular 1.x right now since 2.0 isn't fully production ready yet, but we'll be keeping our application structure similar to how you'd see an Angular 2.0 application to be handled.

|- index.html |- style.css |- app |- components |- admin |- admin.html |- admin.js |- authenticate |- authenticate.html |- authenticate.js |- home |- home.html |- home.js |- shop |- shop.html |- shop.js |- product |- product.html |- product.js |- checkout |- checkout.html |- checkout.js |- profile |- profile.html |- profile.js |- shared |- ProductService.js |- UserService.js |- OrderService.js |- ShopService.js |- app.routes.js |- app.js |- bower.json

As you can see, we're keeping our structure close to the idea of components and WebComponents. This creates a solid separation for each part of our application.

We're also creating the shared folder that will hold all of our Angular services that will talk to the Stamplay API.

Front-End Dependencies with Bower

We're going to use Bower to grab the front-end resources we need. To install Bower, run the following:

$ npm install -g bower

For more info on Bower, read our Bower Getting Started tuorial . Now that we have bower installed, we just need to create a bower.json file by using the following command:

$ bower init

Just use the defaults here and then we can finally install our dependencies that we need. We will be using the following:

Bootswatch: (grabbing the Cerulean theme since that has an Etsy type of vibe)

Stamplay SDK: This is the JavaScript SDK provided by Stamplay

Angular

ui-router: Angular routing

Angular Stamplay: A wrapper for the Stamplay SDK so that we can work with the proper commands in Angular

To install all those in one command, run the following:

$ bower install --save bootswatch stamplay-js-sdk angular angular-stamplay ui-router

These will be installed into a new bower_components folder in the root of our project and we'll be able to reference those moving forward.

The --save flag tells Bower to save these as dependencies in the bower.json file. This way, we have a centralized place to look when we need to know what front-end resources this application requires and what versions.

Related Reading: Only Grab the Files you Need with Bower

With all that ready to go, let's create our index.html file which will be the base for our entire application.

Starting Our Main View File (index.html)

Let's create all those files now and take a look at our index.html file.

index.html

In this file, the main things we have to do are load all of our dependencies ( app.js and style.css ) and make sure we configure our Stamplay application.

We're also going to lay out our overall application with a header, footer, and the main section where Angular and ui-router will inject our views.

Here is the start of our index.html file:

<!-- index.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Angular Etsy Stamplay</title> <!-- required by ui-router --> <base href="/"> <!-- CSS --> <!-- load up bootswatch-cerulean and our custom styles --> <!-- JS --> <!-- load the stamplay sdk and configure it --> </head> <body> </body> </html>

Let's go piece by piece and fill in the blanks.

Adding CSS Files

We're loading the Cerulean Bootswatch theme and our own style.css file that we created earlier.

<!-- index.html --> <!-- CSS --> <!-- load up bootswatch-cerulean and our custom styles --> <link rel="stylesheet" href="./bower_components/bootswatch/cerulean/bootstrap.min.css"> <link rel="stylesheet" href="./style.css">

Adding JS Files

This will seem the most daunting at first. But we're going to take it piece by piece. And remember, in a production level application, you'd want to use a task runner like Grunt or Gulp to minify and combine/concatenate all these files together into one singular file.

<!-- index.html --> <!-- JS --> <!-- load the stamplay sdk and configure it --> <script src="./bower_components/stamplay-js-sdk/dist/stamplay.min.js"></script> <script> Stamplay.init('angularetsy'); // initiate stamplay app with app name </script> <!-- load our other JS dependencies --> <script src="./bower_components/angular/angular.min.js"></script> <script src="./bower_components/angular-stamplay/angular-stamplay.js"></script> <script src="./bower_components/angular-ui-router/release/angular-ui-router.min.js"></script> <!-- load all the parts for our custom angular app --> <!-- youd probably want to compile all this with a build system like gulp --> <!-- load our angular components --> <script src="./app/components/home/home.js"></script> <script src="./app/components/shop/shop.js"></script> <script src="./app/components/product/product.js"></script> <script src="./app/components/checkout/checkout.js"></script> <script src="./app/components/profile/profile.js"></script> <script src="./app/components/admin/admin.js"></script> <script src="./app/components/authenticate/authenticate.js"></script> <!-- load our angular services --> <script src="./app/shared/ProductService.js"></script> <script src="./app/shared/UserService.js"></script> <script src="./app/shared/OrderService.js"></script> <script src="./app/shared/ShopService.js"></script> <!-- load our core angular app --> <script src="./app/app.routes.js"></script> <script src="./app/app.js"></script>

Wow what a doozy. That may seem like a lot of loading things, but again, you'd want a way to automate all this together in the future.

We're just loading the Stamplay JS SDK and configuring that with our application name. Then we're loading the different pieces of our Angular application. We'll bootstrap all that together soon.

Let's keep moving with this index.html file and move to the <body> .

The Main Body

We're going to need a <header> , <main> , and <footer> so let's create those:

<!-- index.html --> <!-- HEADER AND NAV --> <header> <nav id="main-nav" class="navbar"> <div class="container"> <!-- site logo --> <div class="navbar-header"> <a href="#" class="navbar-brand"> <span class="glyphicon glyphicon-heart-empty"></span> Shop City </a> </div> <!-- search form --> <form class="navbar-form navbar-left"> <div class="form-group"> <input type="text" class="form-control" placeholder="Search"> </div> <button type="submit" class="btn btn-primary">Search</button> </form> <!-- logged in navigation --> <!-- todo: check for logged in user --> <ul class="nav navbar-nav navbar-right"> <li><a href="#">Admin</a></li> <li><a href="#">Purchases</a></li> <li><a href="#">Profile</a></li> <li><a href="#">Logout</a></li> </ul> <!-- not logged in navigation --> <ul class="nav navbar-nav navbar-right"> <li><a href="#">Register</a></li> <li><a href="#">Sign in</a></li> </ul> </div> </nav> </header> <!-- ui-router will inject our component views here --> <main class="container"> <div ui-view></div> </main> <!-- FOOTER --> <footer class="text-center"> © 2015 Shop City </footer>

Most of this is Bootstrap stylings and we'll be filling in the link hrefs (or ui-sref s in our case since we're using ui-router) once we have our Angular routes configured.

We'll also be using Angular to show/hide those logged in links if a user is not logged in via Stamplay.

Some Quick Styling

Now let's add a little styling so that we sort of look like Etsy. The Cerulean theme has this crazy blue navbar that we're going to get rid of. Etsy also uses a serif font for their headers so we'll do the same.

In our style.css file, add the following:

/* style.css */ /* main styles ------------------------------------------*/ body { background:#EFEFEF; } h1, h2, h3, h4, h5, h6 { font-family:Georgia, serif; } /* footer ---------------------------------------------- */ footer { padding-top:50px; } /* header ---------------------------------------------- */ #main-nav { background:#FFF; background-image:none; border:none; border-bottom:1px solid #E1E3DF; box-shadow:none; } /* logo */ #main-nav .navbar-brand { color:#f45800; font-size:30px; padding-top:20px; padding-bottom:20px; font-family:Georgia, serif; } /* nav links */ #main-nav a { font-size:18px; padding-bottom:30px; padding-top:30px; } #main-nav a:hover { background:none; } /* header search form */ #main-nav .navbar-form { padding-top:16px; padding-bottom:10px; }

Testing Our Main View

Now that we have all that wired up, let's see how it looks! We're going to need a local server to view our application (or you can just stamplay deploy ) and see it live.

Luckily, Stamplay also has a command to get a local server up for us to test. All we have to do is run:

$ stamplay start

Then my app is viewable at http://localhost:8080.

Deploying to Stamplay

Great! Our foundation is ready! Let's move on to wiring up all our views and components together so we can click around our application and see the pages changing.

Go ahead and stamplay deploy and we can also see our application live at http://angularetsy.stamplayapp.com! If you want to share it with people, feel free. It doesn't do much yet but it will very soon.

image

Angular Routing for Our Application

Our application doesn't really do much yet, but let's start working on the Angular side of things to get all the functionality that we want. We're going to be using ui-router so if you need a primer on that, be sure to read our Angular Routing with ui-router article.

We've already grabbed Angular and ui-router using Bower and have already loaded it into our application in index.html . The next step is to start up our app.js file.

Starting Our Angular App (app.js)

Here we will create our angular.module and create a MainController to encompass our entire application.

// app.js angular .module('etsyApp', [ 'ngStamplay', 'ui.router', 'app.routes' ]) .controller('MainController', MainController); /** * The main controller for our application */ function MainController() { var main = this; }

We've started our etsyApp and injected ngStamplay , ui.router and the app.routes module (we haven't created it just yet). We've also created a MainController that we can use to house things that will be usable all around our application.

Things like the logout functionality can go here. Now we have to apply our Angular application to our view. We will do that in index.html on the <body> tag:

<!-- index.html --> ... <body ng-app="etsyApp" ng-controller="MainController as main"> ...

Our Angular application is now applied to our view. If we try to view this in our browser, we'll throw an error however since we haven't yet defined the app.routes module in the app.routes.js file.

Creating Our Routing File

The routing file may be one of the most important (if not the most important) file in our entire Angular application. It is the backbone of our application and is a top-level view of how things will work together. It will define the pages of our application, grab all the necessary views, and assign the proper controllers.

This is going to be a lot, but let's parse through it and see what's happening in app.routes.js :

// app.routes.js angular .module('app.routes', []) .config(['$stateProvider', '$urlRouterProvider', '$locationProvider', AppRoutes]); /** * Create all the application routes */ function AppRoutes($stateProvider, $urlRouterProvider, $locationProvider) { // pretty Angular URLs $locationProvider.html5Mode(true); // the route people are sent to when they are lost // the home page in this case $urlRouterProvider.otherwise('/'); // create our routes, set the view, pull in the controller $stateProvider // home page .state('home', { url : '/', templateUrl : '/app/components/home/home.html', controller : 'HomeController as home' }) // shop page .state('shop', { url : '/shop/{name}', templateUrl : '/app/components/shop/shop.html', controller : 'ShopController as shop' }) // product page (a child of shop) .state('product', { url : '/listing/{id}/{name}', templateUrl : '/app/components/product/product.html', controller : 'ProductController as product' }) // login/signup page .state('authenticate', { url : '/authenticate', templateUrl : '/app/components/authenticate/authenticate.html', controller : 'AuthenticateController as authenticate' }) // profile page .state('profile', { url : '/profile/{user_name}', templateUrl : '/app/components/profile/profile.html', controller : 'ProfileController as profile' }) // checkout page .state('checkout', { url : '/checkout/{id}', templateUrl : '/app/components/checkout/checkout.html', controller : 'CheckoutController as checkout' }) // checkout page .state('admin', { url : '/admin', templateUrl : '/app/components/admin/admin.html', controller : 'AdminController as admin' }); }

It really just feels like a lot because it's the longest file we've made so far, but it's very straightforward.

Create a route

Pick the view file associated with it

Pick the Angular controller associated with that view

This keeps to the idea that each part of our site is a component and gets us in the mindset for Angular 2 when that happens in the future.

Note: If you're wondering why I define the .config() like that, it's for minification purposes. Read more: Declaring AngularJS Modules for Minification

Then we set the $urlRouterProvider.otherwise() to let our application know where to route people if they are trying to access a route that doesn't exist.

We're setting two URL parameters on product because we don't want to limit product name to be unique (multiple users should be able to create the same product name) and we're going to use the id since that will always be unique.

Now that we have these routes, we have to tell our application's links to use them. Earlier, we just defined our <a> tags with href="#" so they won't do anything.

Linking Together Our Application

In ui-router, the way we define links is to use ui-sref and then point to the name of the state.

Here is our updated index.html file (just the <header> part).

<!-- index.html --> <!-- HEADER AND NAV --> <header> <nav id="main-nav" class="navbar"> <div class="container"> <!-- site logo --> <div class="navbar-header"> <a ui-sref="home" class="navbar-brand"> <span class="glyphicon glyphicon-heart-empty"></span> Shop City </a> </div> <!-- search form --> <form class="navbar-form navbar-left"> <div class="form-group"> <input type="text" class="form-control" placeholder="Search"> </div> <button type="submit" class="btn btn-primary">Search</button> </form> <!-- logged in navigation --> <!-- todo: check for logged in user --> <ul class="nav navbar-nav navbar-right"> <li><a ui-sref="admin">Admin</a></li> <li><a ui-sref="profile">Profile</a></li> <li><a href="#">Logout</a></li> </ul> <!-- not logged in navigation --> <ul class="nav navbar-nav navbar-right"> <li><a ui-sref="authenticate">Register</a></li> <li><a ui-sref="authenticate">Sign in</a></li> </ul> </div> </nav> </header>

Now you may ask why did we remove the href="#" from our links? Well when having an href="#" , this will cause the browser to move to the top when clicked (an anchor to the top of the page). We'll just use the ui-sref and add some CSS so that our users will see the cursor on our a tags.

Add this to your style.css file:

/* style.css */ /* make sure that our a tags have the cursor */ a[ui-sref] { cursor:pointer; }

We also haven't added a ui-sref to the Logout button because that will be handled with an ng-click from our MainController . We'll deal with that after we have login functionality working in a few sections.

Creating The Components

We've used our routing to point to the specific components like app/components/home/home.js but we haven't defined that Angular controller yet. Let's do that for each of our files. Just go in and drop the following into each file. This will be the base template for each.

app/components/home/home.js

// home.js angular .module('app.home', []) .controller('HomeController', HomeController); function HomeController() { var home = this; }

app/components/admin/admin.js

// admin.js angular .module('app.admin', []) .controller('AdminController', AdminController); function AdminController() { var admin = this; }

app/components/authenticate/authenticate.js

// authenticate.js angular .module('app.authenticate', []) .controller('AuthenticateController', AuthenticateController); function AuthenticateController() { var authenticate = this; }

app/components/checkout/checkout.js

// checkout.js angular .module('app.checkout', []) .controller('CheckoutController', CheckoutController); function CheckoutController() { var checkout = this; }

app/components/product/product.js

// product.js angular .module('app.product', []) .controller('ProductController', ProductController); function ProductController() { var product = this; }

app/components/profile/profile.js

// profile.js angular .module('app.profile', []) .controller('ProfileController', ProfileController); function ProfileController() { var profile = this; }

With all those defined as our controllers, ui-router will know which to pull for a certain view now. All we have to do is inject them into our main application now.

Injecting into the Main Application

Go into the app.js file and inject the following into the dependencies:

// app.js angular .module('etsyApp', [ 'ngStamplay', 'ui.router', 'app.routes', 'app.admin', 'app.authenticate', 'app.checkout', 'app.home', 'app.product', 'app.profile', 'app.shop' ])

Finally our entire application is set up and ready to rock. We can now go piece by piece and start working on each module as we fill out the functionality in our application.

Let's test to make sure that everything is working and go to your application in browser. You should be able to click around and see the URL changing.

We can fill in each view now so that we can show things to our users. The next step is to create Angular services to talk to our Stamplay backend.

Angular Services to Interact with Our Stamplay API

If routing is the backbone of our application, then the services are our communication network. These are how we will talk to the Stamplay API that was created in Part 1.

We'll use a UserService to authenticate and register users via their email.

We'll also use a ProductService to show all products and let users create products. Then we'll create a ShopService to let users create their own shops if they choose to do so.

Let's get started with our UserService . A lot of the functionality in this has been provided to us by the Stamplay JS SDK and the angular-stamplay module.

User Service for Authentication

The main functions in our UserService will be:

Get the current logged in user

Register a user

Log a user in

Log a user out

Here is the app/shared/UserService.js file:

// UserService.js angular .module('UserService', []) .factory('User', ['$stamplay', '$q', UserService]); function UserService($stamplay, $q) { // return an object with all our functions return { getCurrent: getCurrent, signup: signup, login: login, logout: logout }; /** * Get the current logged in user */ function getCurrent() { var def = $q.defer(); // instantiate a new user model from the stamplay js sdk var user = $stamplay.User().Model; user.currentUser() .then(function() { // send the entire user model back def.resolve(user); }); return def.promise; } /** * Register a user with their name, email, and password */ function signup(data) { var def = $q.defer(); // instantiate a new user model from the stamplay js sdk var user = $stamplay.User().Model; user.signup(data) .then(function() { // send the entire user model back def.resolve(user); }) return def.promise; } /** * Log a user in with their email and password */ function login(data) { var def = $q.defer(); var user = $stamplay.User().Model; user.login(data.email, data.password) .then(function() { // send the entire user model back def.resolve(user); }, function() { def.reject({ 'error': 'Unable to login user.' }); }); return def.promise; } /** * Log the current user out * Will also redirect the browser to the logout url (home) */ function logout() { var user = $stamplay.User().Model; user.logout(); } }

We have four main functions here: getCurrent , signup , login , and logout . The format for these are very similar. We are going to use the Stamplay JS SDK to grab what we need. Stamplay provides a convenient wrapper so that we don't have to worry about the internals of what's going on.

For instance, for the signup function, we just need to pass in an email , a password , and a displayName and that user will be created. We will then return the user object in a promise object so that we will be able to use it in our controllers.

With this UserService ready to go, we can go and inject it into our main app.js file and then use it in our authenticate component. Let's inject it and move on:

// app.js angular .module('etsyApp', [ ... 'UserService' ... ])

Now that we have it, we can inject the User factory into our MainController like so:

// app.js .controller('MainController', ['User', '$rootScope', MainController]); function MainController(User, $rootScope) { ... }

We are also grabbing $rootScope so that we can bind the logged in user to that and have it usable across our application. Next step is to actually use this new User factory.

Grabbing The Current User

When a user lands on our application, we will want to show that they are logged in if they are. To do this, we will need to grab the current user's logged in information. We have already created the function to do so in our UserService so let's use it in the MainController .

// app.js function MainController(User) { var main = this; $rootScope.currentUser = {}; // creating this object to hold our current users info // get the current user and bind their data to $rootScope.currentUser object User.getCurrent() .then(function(data) { if (data.get('_id')) { $rootScope.currentUser.id = data.get('_id'); $rootScope.currentUser.name = data.get('displayName'); $rootScope.currentUser.image = data.get('profileImg'); } else { // clear the current user just to be sure $rootScope.currentUser = {}; } }); }

Now that we are grabbing a logged in user (if there is one) and binding it to the $rootScope.currentUser object, we can use that object in our view to hide/show certain elements of our UI.

Note: If you are testing your application locally, then you'll see that there is an error running User.getCurrent() . This is because Stamplays CORS security only allows access from specified domains. View the Stamplay CORS Policy to see how you can add your local domain to your application and have the ability to test locally.

Hiding UI Elements Based on Logged In Status

Let's hide all the things that require a login in our index.html file.

We will use ngShow and ngHide on our <ul> tags.

<!-- index.html --> <!-- logged in navigation --> <!-- todo: check for logged in user --> <ul class="nav navbar-nav navbar-right" ng-show="currentUser.id"> <li><a ui-sref="admin">Admin</a></li> <li><a ui-sref="profile">Profile</a></li> <li><a href="#">Logout</a></li> </ul> <!-- not logged in navigation --> <ul class="nav navbar-nav navbar-right" ng-show="!currentUser.id"> <li><a ui-sref="authenticate">Register</a></li> <li><a ui-sref="authenticate">Sign in</a></li> </ul>

Very straightforward here. We are going to show the logged in links if there is a user bound to currentUser (which looks at $rootScope ) and show the logged out links if one doesn't exist.

Logging a User Out

Let's wire up that Logout button now that we have the functionality from our UserService .

We just need to add a function to our MainController :

// app.js function MainController(User, $rootScope) { var main = this; main.logout = logout; // bind the logout function to our controller ... /** * Use the User factory's logout functionality */ function logout() { User.logout(); $rootScope.currentUser = {}; } }

Let's add that to our Logout button in index.html now:

<!-- index.html --> <li><a ng-click="main.logout()">Logout</a></li>

And a little CSS to make sure that a user has their cursor when they hover ng-click :

/* style.css */ /* make sure that our a tags have the cursor */ a[ui-sref], a[ng-click] { cursor:pointer; }

Great! Now we have everything needed for when a user is logged in. But how will they login if they don't have an account yet? Let's move onto our authenticate page where a user will be able to sign up with their email address.

Handling Email Login and Signup

We already have Facebook login and signup thanks to Stamplay. Now we will use their JS SDK tools to let our users sign up and login with their email address.

This will all be handled in our UserService.js file. We'll then pull that service into the authenticate.js component of our application and let people login and signup there.

Login/Signup View

In our authenticate.html file is where we will create the view for our login/signup page. We'll be splitting the page in half, left side for signup and right side for login.

Here is the code in app/components/authenticate/authenticate.html :

<!-- authenticate.html --> <div class="row"> <!-- SIGNUP =============================== --> <div class="col-sm-6"> <div class="page-header text-center"> <h2>Sign Up</h2> </div> <!-- signup button to use facebook --> <p> <a class="btn btn-block btn-primary" href="/auth/v1/facebook/connect">Sign Up with Facebook</a> </p> <!-- form will use authenticate.signup on submit --> <form id="signupForm" ng-submit="authenticate.signup()"> <div class="form-group"> <label>Name</label> <input type="text" class="form-control" ng-model="authenticate.signupData.displayName" required> </div> <div class="form-group"> <label>Email</label> <input type="email" class="form-control" ng-model="authenticate.signupData.email" required> </div> <div class="form-group"> <label>Password</label> <input type="password" class="form-control" ng-model="authenticate.signupData.password" required> </div> <button type="submit" class="btn btn-info">Sign Up</button> </form> </div> <!-- LOGIN ================================ --> <div class="col-sm-6"> <div class="page-header text-center"> <h2>Login</h2> </div> <!-- signup button to use facebook --> <p> <a class="btn btn-block btn-primary" href="/auth/v1/facebook/connect">Login with Facebook</a> </p> <!-- form will use authenticate.login on submit --> <form id="loginForm" ng-submit="authenticate.login()"> <div class="form-group"> <label>Email</label> <input type="email" class="form-control" ng-model="authenticate.loginData.email" required> </div> <div class="form-group"> <label>Password</label> <input type="password" class="form-control" ng-model="authenticate.loginData.password" required> </div> <button type="submit" class="btn btn-info">Login</button> </form> </div> </div>

This is very Bootstrap heavy syntax which is totally fine since it helps us get to our end goal faster. We're going to create a signupForm and a loginForm here with each one bound to a separate function in the AuthenticateController . We'll be defining both of those soon.

Notice we also created links to the Login with Facebook. This link is the same as the Sign Up with Facebook as the flow for that is the same. If a Facebook user exists in our database, Stamplay will return them, if not, Stamplay will create them.

Notice we also bind each form's data to a specific object on our AuthenticateController ( signupData and loginData ).

Let's move forward and create the controller that will handle submitting both of these forms.

Authenticate Controller

First thing we need to do is inject the User factory into this controller since that is what we will use for signup and login.

// authenticate.js angular .module('app.authenticate', []) .controller('AuthenticateController', ['User', '$rootScope', '$state', AuthenticateController]); function AuthenticateController(User, $rootScope, $state) { ...

$rootScope is also injected here so that we can bind the user to it after signing up or logging in. We are grabbing $state from ui-router as well so that we can redirect a user after successfully signing up or logging in. The next step is to create the signup and login functions:

// authenticate.js function AuthenticateController(User, $rootScope, $state) { var authenticate = this; // create the objects for our forms authenticate.signupData = {}; authenticate.loginData = {}; // bind the functions to our controller authenticate.signup = signup; authenticate.login = login; /** * Sign a user up and bind their info to $rootScope */ function signup() { User.signup(authenticate.signupData) .then(function(data) { if (data.get('_id')) { $rootScope.currentUser.id = data.get('_id'); $rootScope.currentUser.name = data.get('displayName'); $rootScope.currentUser.image = data.get('profileImg'); // redirect the user $state.go('home'); } }); } /** * Use the User factory to log a user in * Bind the user's information to $rootScope */ function login() { User.login(authenticate.loginData) .then(function(data) { if (data.get('_id')) { $rootScope.currentUser.id = data.get('_id'); $rootScope.currentUser.name = data.get('displayName'); $rootScope.currentUser.image = data.get('profileImg'); // redirect the user $state.go('home'); } }); } }

Now we have the functionality to sign a user up or log a user in! We are also

Testing Signup and Login

Let's check on all our work up to this point and test everything. Run a stamplay deploy and go visit your application in browser.

Everything should be routing correctly and you should see your login and signup forms when you click Sign in or Register.

Let's try a user sign up and see if it works!

We have signed a user up and been redirected to the home page now! Let's go to the Stamplay dashboard and see our user in our database.

Go ahead and test that you can logout and log back in also. We now have all of our authentication ready to go!

The last thing we'll do in Part 2 is to allow users to create products and to show all products on the home page.

The Product Service

Just like when we interacted with the Stamplay User API, we will need a service to interact with the API for products.

Let's go ahead and create our ProductService now and we'll only need the basic CRUD functionality.

The basics of the Stamplay JS SDK is that there are Model s and Collection s. Models are for grabbing on singular object and Collections are for multiple. We'll use each in the appropriate functions.

The functions we are going to have in the ProductService are:

all() : Grab all products

: Grab all products get(id) : Get a single product

: Get a single product create(data) : Create a product

: Create a product update(id, data) : Update a specific product with new information

: Update a specific product with new information destroy(id) : Delete a certain product

: Delete a certain product getComments(id) : Get the comments for a certain product

: Get the comments for a certain product comment(id, data) : Add a comment to a product

: Add a comment to a product getCategories() : Get all the categories so we can use them when we create products.

These are the six functions we are going to have on our Product. The CRUD stuff is pretty standard. We'll use a Stamplay Collection to grab all the products and a Stamplay Model when we work with a singular product.

The interesting thing to note here is that on all custom objects, Stamplay provides an actions object on each and every custom object. This means that we have three really useful attributes available:

comments

ratings

votes

We can see if we go into the Stamplay Dashboard and view all products in the API Console, we have the actions object available to us.

Be sure to look at the Stamplay JS SDK Docs for more information on what we can do with Models and Collections.

Let's define our ProductService.js file now so we can start letting users create and see products.

// app.js angular .module('ProductService', []) .factory('Product', ['$stamplay', '$q', '$http', ProductService]); function ProductService($stamplay, $q, $http) { return { all: all, get: get, create: create, update: update, destroy: destroy, getComments: getComments, comment: comment, createPicture: createPicture, getCategories: getCategories }; /** * Get all the products */ function all() { var def = $q.defer(); // instanticate a new product collection from the stamplay js sdk var products = new Stamplay.Cobject('products').Collection; products.populate().fetch() .then(function() { def.resolve(products); }); return def.promise; } /** * Get a single product */ function get(id) { var def = $q.defer(); // instanticate a new product model from the stamplay js sdk var product = new Stamplay.Cobject('products').Model; // get the product in question and return it product.fetch(id) .then(function() { def.resolve(product); }); return def.promise; } /** * Create a product */ function create(data) { var def = $q.defer(); // instanticate a new product model from the stamplay js sdk var product = new Stamplay.Cobject('products').Model; // loop over the fields in data and update the product angular.forEach(data, function(value, key) { product.set(key, value); }); product.save() .then(function() { def.resolve(product); }); return def.promise; } /** * Update an existing product */ function update(id, data) { var def = $q.defer(); // instanticate a new product model from the stamplay js sdk var product = new Stamplay.Cobject('products').Model; product.fetch(id) .then(function() { // loop over the fields in data and update the product angular.forEach(data, function(value, key) { product.set(key, value); }); return product.save(); }) .then(function() { // return the product def.resolve(product); }); return def.promise; } /** * DESTROY a product */ function destroy(id) { var def = $q.defer(); // instanticate a new product model from the stamplay js sdk var product = new Stamplay.Cobject('products').Model; product.fetch(id) .then(function() { return product.destroy(); }) .then(function() { // return true that the product was deleted def.resolve({ 'success': true }); }); return def.promise; } /** * Get all the comments for a specific product */ function getComments(id) { var def = $q.defer(); // instanticate a new product model from the stamplay js sdk var product = new Stamplay.Cobject('products').Model; product.fetch(id) .then(function() { // a user will comment on the found product def.resolve(product.getComments()); }); return def.promise; } /** * Comment on a product */ function comment(id, data) { var def = $q.defer(); // instanticate a new product model from the stamplay js sdk var product = new Stamplay.Cobject('products').Model; product.fetch(id) .then(function() { // a user will comment on the found product return product.comment(data.text); }) .then(function() { // return the product def.resolve(product); }); return def.promise; } /** * Create a picture */ function createPicture(files) { var def = $q.defer(); // create an object for the ids var pictureIDs = []; // loop over the files and upload them via the Stamplay API angular.forEach(files, function(file) { // create a new formdata to store our image var fd = new FormData(); fd.append('photo', file); // process the upload $http({ method: 'POST', url: 'https://angularetsy.stamplayapp.com/api/cobject/v1/pictures', data: fd, headers: { 'Content-Type': undefined }, photo: file }) .then(function(response) { // push the given id into the pictureIDs array pictureIDs.push(response.data.id); def.resolve({ pictures: pictureIDs }); }); }); return def.promise; } /** * Get all the product categories */ function getCategories() { var def = $q.defer(); // instanticate a new product collection from the stamplay js sdk var products = new Stamplay.Cobject('categories').Collection; products.populate().fetch() .then(function() { def.resolve(products); }); return def.promise; } }

Applying to Our Application

Next step is to add this ProductService to our app.js dependencies:

// app.js angular .module('etsyApp', [ ... 'ProductService' ])

Viewing All Products on the Home Page

Let's use this ProductService to grab all the products for viewing on the home page since our home page is pretty sparse right now.

We're going to handle all this in the app/components/home/ directory. Open up app/components/home/home.js and we'll start here. First we need to inject the Product factory now that we have access to it:

// home.js angular .module('app.home', []) .controller('HomeController', ['Product', HomeController]); function HomeController(Product) { var home = this; // get all the products here }

The next step is to use the Product.all() call to grab all of our products.

// home.js function HomeController(Product) { var home = this; // get all the products and bind them to home.products Product.all() .then(function(data) { home.products = data.instance; }); }

With all the products bound to home.products, we can ng-repeat over them in our home.html file. That simple!

Let's go do that now in home.html :

<!-- home.html --> <div class="page-header text-center"> <h1>Top Products</h1> </div> <div class="row"> <!-- loop over home.products here --> <div class="col-sm-4" ng-repeat="product in home.products"> <div class="product-box"> <!-- the product image --> <a class="product-img" ui-sref="product({ id: product.instance.id, name: product.instance.name })"> <img ng-src="{{ product.instance.pictures[0].photo }}" ng-show="product.instance.pictures"> <img src="http://placebear.com/g/250/170" ng-show="!product.instance.pictures"> </a> <!-- the product content like title --> <div class="product-content"> <a ui-sref="product({ id: product.instance.id, name: product.instance.name })"> {{ product.instance.name }} </a> <div class="listing-price"> {{ product.instance.price | currency }} </div> </div> </div> </div> </div>

That is all we need to loop over the products that Stamplay gives us back from the API. Right now it's pretty sparse since the demo product we created in Part 1 only had a name (no price, colors, sizes, pictures, category), but we'll fix that soon by letting users create their own products.

We're attaching ui-sref s to the image and to the title so users can click through to see the product. ui-router will then route us to the appropriate page. We are passing the parameters into ui-sref as an object also. Read more about that on our ui-router tips article.

We're going to be showing the product picture if it exists (we're checking with ng-show ) and if there is no product picture, then we'll just use a picture of a majestic bear.

Adding Some Styling

Here is some styling to match our products to Etsy stylings:

// style.css /* products ------------------------------------ */ .product-box { background:#FFF; border-color:#CACACA; border-bottom-color:#DADADA; box-shadow:0 0 2px 0 rgba(0, 0, 0, 0.15); border-radius:3px; padding:8px; } .product-img img { display:block; margin:0 auto 10px; max-width:100%; } .product-content a { text-align:center; font-size:14px; color:#696969; display:block; } .product-content .listing-price { text-align:right; color:#78C042; font-size:12px; }

Alright now we're getting closer to an Etsy look!

Showing a Single Product

We've wired up the img and the product title to link to the product page. This is where we'll show off the product.

Note: ui-router by default will urlencode your URLs when using ui-sref . To get rid of this behavior, follow the instructions outlined on this stack overflow answer.

We're going to follow the same steps as before to connect with the Stamplay API through our Angular service and then show that in our view.

All of the following work will be done in app/components/product/ . Let's open up app/components/product/product.js and use the Product factory to grab the single product based on the name in the URL route.

// product.js angular .module('app.product', []) .controller('ProductController', ['Product', '$stateParams', ProductController]); function ProductController(Product, $stateParams) { var product = this; // get the product for this page and bind it to the product.listing object Product.get($stateParams.id) .then(function(data) { // since this is a singular Stamplay model that was returned, we can bind instance directly product.listing = data.instance; }); }

We are using the Product.get(id) function that we created earlier. $stateParams , provided by ui-router will allow us to grab the url parameters. In this case, we need the id .

The Stamplay API and SDK will return a single instance since we are only looking for one product. With this, we will bind it to the controller as the listing object.

Now that we've grabbed this product, we can display it in our view, app/components/product/product.html .

This is going to be a lot of code, but we're just laying out the entire product page. There are the images, comments, and content to show so just bear with us.

<!-- product.html --> <div class="listing-wrap"> <div class="row"> <!-- main content (images, description, comments) --> <div class="col-sm-7"> <div class="listing-main"> <!-- the images (show the bears if there are no images --> <div class="listing-images"> <img ng-repeat="picture in product.pictures" ng-show="picture.photo" ng-src="{{ picture.photo }}" class="img-responsive"> <img ng-show="!product.listing.pictures" src="http://placebear.com/g/600/400" class="img-responsive"> </div> <!-- the description --> <div class="listing-description"> <h3>Description</h3> {{ product.listing.description }} </div> <!-- the comments (ng-repeat over the comments) --> <h3>Comments</h3> <div class="listing-comments"> <div class="comment" ng-repeat="comment in product.listing.actions.comments"> {{ comment.text }} </div> </div> </div> </div> <!-- the extra content (title, price, options, add to cart) --> <div class="col-sm-5"> <div class="listing-extra"> <!-- the extras info --> <div class="listing-info"> <h1>{{ product.listing.name }}</h1> <!-- the price --> <div class="listing-price"> {{ product.listing.price | currency }} </div> <!-- loop over the colors --> <ul class="listing-colors"> <li ng-repeat="color in product.listing.color">{{ color }}</li> </ul> <!-- loop over the sizes --> <ul class="listing-sizes"> <li ng-repeat="size in product.listing.size">{{ size }}</li> </ul> </div> <!-- buy now button links to checkout route --> <a ui-sref="checkout({ id: product.listing.id })" class="listing-buy btn btn-success btn-block"> Buy Now </a> </div> </div> </div> </div>

Now to add a bit of styling to style.css :

/* style.css */ /* listing ------------------------------------- */ .listing-wrap { background:#FFF; border:1px solid #ECECEC; border-radius:3px; padding:30px; } .listing-images img { border-radius:3px; margin-bottom:20px; } .listing-description { font-size:16px; margin-bottom:20px; } .listing-comments { background:#FEFEFE; border-radius:3px; padding:20px; } .listing-comments .comment { margin-bottom:15px; } .listing-extra { border:10px solid #E4F3D9; border-radius:3px; padding:20px; } .listing-extra h1 { font-size:20px; line-height:1.2; color:#333; margin:0 0 15px; }

Now this page is a little scarce too since our demo product didnt' have all the much information.

Let's move onto creating products now so we can get the full experience of our product pages. The last thing we'll do here is handle the form for letting logged in users create products.

Before we can create a product, we'll need to create some categories for our products. Let's log into the Stamplay dashboard and create those real quick.

Creating Categories

In the API Console, select Create object and select categories in the dropdown. We'll have product category types universal across our entire site and we'll generate them here.

Let's create five different categories.

Shirts

Shorts

Electronics

Woodwork

Handcrafted

Now if we Get all object, we should be able to see our categories in the API Console.

Let's move onto creating a new product now.

Creating a New Product

We'll lump a lot of admin type functionality into the admin component. The first feature we'll add into the admin component is to create a product.

As

Like this article? Follow @chrisoncode on Twitter