MEAN App: Users Management CRM - MEAN Machine - A beginner's practical guide to the JavaScript stack (2015)

MEAN Machine - A beginner's practical guide to the JavaScript stack (2015)

MEAN App: Users Management CRM

Up to this point, we have created a Node API and used JSON web tokens to authenticate it. This has all been server-side code and now we will integrate it with the frontend using the information we just learned about Angular services.

The goal here is to start writing an Angular application and not have to edit the backend. Treating your applications like this can simulate similar situations where the backend API is built and maintained by someone other than yourself (like Facebook, Twitter, or GitHub APIs). We only have access to the frontend code.

Setting Up the Application

We will be using the foundation and application structure we created in Chapter 14. Since that was our Node API (w/ authentication), this will act as the perfect setup for building a full MEAN stack application.

Folder Structure

We will only be working with the frontend and Angular parts of the MEAN stack, so we will only need to work within the public/ folder. That separation of frontend and backend allows us to narrow our focus and keep our development clean because we already know exactly where our code needs to go.

Here is the application structure.

1 - app/

2 ----- models/

3 ---------- user.js

4 ----- routes/

5 ---------- api.js

6 - public/

7 ----- assets/

8 ---------- css/

9 --------------- style.css

10 ----- app/

11 ---------- controllers/

12 --------------- mainCtrl.js

13 --------------- userCtrl.js

14 ---------- services/

15 --------------- authService.js

16 --------------- userService.js

17 ---------- app.routes.js

18 ---------- app.js

19 ---------- views/

20 --------------- pages/

21 -------------------- users/

22 ------------------------- all.html

23 ------------------------- single.html

24 --------------- login.html

25 --------------- home.html

26 ---------- index.html

27 - package.json

28 - config.js- server.js

Most of these files were already created in Chapter 14.

The authService.js will be the same file we created in the last chapter (Chapter 16) while userService.js will be the file created in Chapter 15.

The rest of the work will be focused on writing controllers, routes, and views.

Bringing In the Services

We’ll be adding the services that we created last chapter. Go ahead and take the files you created and add them to:

· public/app/services/authService.js

· public/app/services/userService.js

Now that our services are part of our application, let’s get the Angular groundwork ready and then we’ll start using them to link the frontend to the backend.

Main Application

The foundation of our Angular application will require 5 files:

· public/app/controllers/mainCtrl.js

· public/app/app.js

· public/app/app.routes.js

· public/app/views/index.html

· public/app/views/pages/home.html

All of these files will work together to produce the home page of our application. Since the home page doesn’t require authentication, we won’t need to use the authService.js just yet.

This is what our home page will look like:

Home Page

Home Page

Let’s get started creating our files.

app/app.js

1 angular.module('userApp', [

2 'ngAnimate',

3 'app.routes',

4 'authService',

5 'mainCtrl',

6 'userCtrl',

7 'userService'

8 ]);

This is a high level overview of our entire application. We are bringing in:

· ngAnimate to add animations to all of our Angular directives (specifically ngShow/ngHide)

· app.routes will be the routing for our application

· authService is the service file created in chapter 16

· mainCtrl will be a brand new controller we create that will encompass our main view

· userCtrl will have the controllers for all our user management pages

· userService is the service file created in chapter 15

Routes

We will start with our route file. For now, we are only adding the first route for our home page.

public/app.routes.js

1 angular.module('app.routes', ['ngRoute'])

2

3 .config(function($routeProvider, $locationProvider) {

4

5 $routeProvider

6

7 // home page route

8 .when('/', {

9 templateUrl : 'app/views/pages/home.html'

10 });

11

12 // get rid of the hash in the URL

13 $locationProvider.html5Mode(true);

14

15 });

This is enough to produce our homepage. Now let’s move on to the main controller.

Controller

public/app/controllers/mainCtrl.js

1 angular.module('mainCtrl', [])

2

3 .controller('mainController', function($rootScope, $location, Auth) {

4

5 var vm = this;

6

7 // get info if a person is logged in

8 vm.loggedIn = Auth.isLoggedIn();

9

10 // check to see if a user is logged in on every request

11 $rootScope.$on('$routeChangeStart', function() {

12 vm.loggedIn = Auth.isLoggedIn();

13

14 // get user information on route change

15 Auth.getUser()

16 .success(function(data) {

17 vm.user = data;

18 });

19 });

20

21 // function to handle login form

22 vm.doLogin = function() {

23

24 // call the Auth.login() function

25 Auth.login(vm.loginData.username, vm.loginData.password)

26 .success(function(data) {

27

28 // if a user successfully logs in, redirect to users page

29 $location.path('/users');

30 });

31 };

32

33 // function to handle logging out

34 vm.doLogout = function() {

35 Auth.logout();

36 // reset all user info

37 vm.user = {};

38 $location.path('/login');

39 };

40

41 });

The mainController will contain some major functions for our application. Since this controller is applied to the overall layout of our application, it will be responsible for holding the logged in and logged out user information.

There are four main tasks that the mainController has that will be accomplished by accessing the Auth service:

Checking if a user is logged in We will check if a user is logged in using the Auth.isLoggedIn() function. This will check to see if there is a token in localStorage. We are also using a module we haven’t used before called $rootScope to detect a route change and check if our user is still logged in. This means that every time we visit a different page, we will check our user’s login state.

Getting user data Whenever the page route is changed, we will go and grab information for the current user. This way, we can display a message like Hello Holly!. This call uses the Auth.getUser() function, which hits the API endpoint http://localhost:8080/api/me.


Tip

Tip

Caching Service Calls

On every route change, we are going to grab the user data. This is to ensure that our user information is fresh, especially right after login. We won’t want to make a call to the API however on every call, so there is a way to cache that information. This can be used for any $http calls and can be very useful in ensuring speed and efficiency in our application.

To cache the getUser() call, we will need to open up our AuthService.js file.

All we have to do here is add to the $http.get() call like so:

1 // get the logged in user

2 authFactory.getUser = function() {

3 if (AuthToken.getToken())

4 return $http.get('/api/me', { cache: true });

5 else

6 return $q.reject({ message: 'User has no token.' });

7 };

The important part here is { cache: true }. Now whenever the Auth.getUser() call is made, this will check if that information has already been cached. If it is in the cache, then it will return the cached information. If not, then it will make the API call.


Log a user in We will have a function to log a user in. This will authenticate the user with username and password. vm.loginData.username is bound to an input that we will create in our view.

Log a user out We will call Auth.logout(), which will delete the user’s token in localStorage and any information stored in the mainController vm.user object. Then we will redirect the user to the home page since they will be unauthenticated and therefore won’t have access to any other pages.

Home Page View

Just like in the Angular routing chapter, we will need a main file to be the overall layout for the site. Since Node.js is returning this index.html file to our users, this will be our layout file.

public/app/views/index.html

1 <!DOCTYPE html>

2 <html lang="en">

3 <head>

4 <meta charset="UTF-8">

5 <title>User CRM</title>

6

7 <!-- FOR ANGULAR ROUTING -->

8 <base href="/">

9

10 <!-- CSS -->

11 <!-- load bootstrap from CDN and custom CSS -->

12 <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootswatch/3.3.1/paper/\

13 bootstrap.min.css">

14 <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/animate.css/3.1.\

15 1/animate.min.css">

16 <link rel="stylesheet" href="assets/css/style.css">

17

18 <!-- JS -->

19 <!-- load angular and angular-route via CDN -->

20 <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.8/angular.min.js"><\

21 /script>

22 <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.8/angular-route.js"\

23 ></script>

24 <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.8/angular-animate.j\

25 s"></script>

26

27 <!-- controllers -->

28 <script src="app/controllers/mainCtrl.js"></script>

29 <script src="app/controllers/userCtrl.js"></script>

30

31 <!-- services -->

32 <script src="app/services/authService.js"></script>

33

34 <!-- main Angular app files -->

35 <script src="app/app.routes.js"></script>

36 <script src="app/app.js"></script>

37 </head>

38 <body ng-app="userApp" ng-controller="mainController as main">

39

40 <!-- NAVBAR -->

41 <header>

42

43 <div class="navbar navbar-inverse" ng-if="main.loggedIn">

44 <div class="container">

45 <div class="navbar-header">

46 <a href="/" class="navbar-brand"><span class="glyphicon glyphicon-fire tex\

47 t-danger"></span> User CRM</a>

48 </div>

49 <ul class="nav navbar-nav">

50 <li><a href="/users"><span class="glyphicon glyphicon-user"></span> Users<\

51 /a></li>

52 </ul>

53 <ul class="nav navbar-nav navbar-right">

54 <li ng-if="!main.loggedIn"><a href="/login">Login</a></li>

55 <li ng-if="main.loggedIn" class="navbar-text">Hello {{ main.user.name }}!<\

56 /li>

57 <li ng-if="main.loggedIn"><a href="#" ng-click="main.doLogout()">Logout</a\

58 ></li>

59 </ul>

60 </div>

61 </div>

62

63 </header>

64

65 <main class="container">

66 <!-- ANGULAR VIEWS -->

67 <div ng-view></div>

68 </main>

69

70 </body>

71 </html>

We are applying our Angular application to the <body> tag and using the controller as syntax (mainController as main). We’re also loading the userCtrl.js that we’ll build soon.

All of our files are loaded for our CSS and our Angular application.

We are making use of the variable vm.loggedIn to show if a user is logged in or not. The ngIf Angular directives will help us show or hide the navbar, since a user that isn’t logged in should not be able to see it.

We have also created a logout link that will use the doLogout() function we created in our mainController. Right next to the logout button, we have also added a message to say hello to our logged in user. That data came from our mainController when we called Auth.getUser() and bound that information to vm.user.


Tip

Tip

ngIf vs. ngShow/ngHide

The biggest difference in using ngIf over ngShow or ngHide is that ngIf will not show that HTML element in view source or inspect element.

This is helpful if you want to hide information that only authenticated users should see.


To finish off the home page, let’s add some custom CSS stylings to our CSS file.

public/assets/css/style.css

1 main {

2 padding-top:30px;

3 }

4

5 /* HEADER

6 ===================== */

7 header .navbar {

8 border-radius:0;

9 }

Since we aren’t logged in, we won’t see the navbar and our users will just see the welcome text and the link to the home page.

Home Page

Home Page

Let’s move onto creating the login page now so that our users will actually be able to login.

Login Page

We will need to do two things to create our login page:

· Create the login route

· Create the login view

We’ll add the login route under the home page route to our app.routes.js file:

1 // route for the home page

2 .when('/', {

3 templateUrl : 'app/views/pages/home.html'

4 })

5

6 // login page

7 .when('/login', {

8 templateUrl : 'app/views/pages/login.html',

9 controller : 'mainController',

10 controllerAs: 'login'

11 });

We are applying the mainController to this route in addition to the overall site except we are naming the controllerAs attribute login. This allows us to access the mainControllers functions using login.doLogin().

Let’s create the login view now:

app/views/pages/login.html

1 <div class="row col-sm-6 col-sm-offset-3">

2

3 <div class="jumbotron">

4 <h1>Login</h1>

5

6 <form ng-submit="login.doLogin()">

7

8 <!-- USERNAME INPUT -->

9 <div class="form-group">

10 <label>Username</label>

11 <input type="text" class="form-control"

12 ng-model="login.loginData.username">

13 </div>

14

15 <!-- PASSWORD INPUT -->

16 <div class="form-group">

17 <label>Password</label>

18 <input type="password" class="form-control"

19 ng-model="login.loginData.password">

20 </div>

21

22 <!-- LOGIN BUTTON -->

23 <button type="submit" class="btn btn-block btn-primary">

24 <span>Login</span>

25 </button>

26

27 </form>

28 </div>

29

30 </div>

We have added line breaks in the <input> tags to make them easier to read.

We have created a normal login form and bound our Angular functions and variables using ng-submit and ng-model. Our user form will look clean and simple:

Login Page

Login Page

If we go ahead and enter our credentials (chris, supersecret), then we will be redirected to the /users page. That behavior was set in the doLogin() function we created in mainController. The users page doesn’t exist yet since we haven’t created the route or views.

Let’s add a few things to our login page like a processing icon and handling error messages.

Adding a Processing Icon

It makes sense to show a processing indicator when our form is processing. We can do this with two steps:

1. Creating a variable called processing

2. Adding spinning icons in our views

Whenever a user clicks Login, we want the view to show a processing icon. When the form is done processing, we want the processing button to disappear.

We will show and hide the processing icon using ngIf and bind it to a variable called processing. Let’s update the doLogin() function in our mainController file to add this variable.

1 // function to handle login form

2 vm.doLogin = function() {

3 vm.processing = true;

4

5 Auth.login(vm.loginData.username, vm.loginData.password)

6 .success(function(data) {

7 vm.processing = false;

8

9 // if a user successfully logs in, redirect to users page

10 $location.path('/users');

11 });

12 };

When a user clicks Login, we have set the processing variable to true. When the function is done processing, we will set the variable to false.

Now all well have left to do is create the processing icon and show it.

In our login.html file, let’s add the icon to the Login button.

1 <button type="submit" class="btn btn-block btn-primary">

2

3 <span ng-if="!login.processing">Login</span>

4

5 <span ng-if="login.processing" class="spinner"><span class="glyphicon glyphico\

6 n-repeat"></span></span>

7

8 </button>

If the variable is true, we will show the spinner. Otherwise, our button will always say Login.

Just a little CSS to handle the spinning animation and we should be good. Add the following to style.css:

1 /* ANIMATIONS

2 ====================== */

3 .spinner {

4 animation:spin 1s infinite;

5 -webkit-animation:spin 1s infinite;

6 -moz-animation:spin 1s infinite;

7 }

8

9 @keyframes spin {

10 from { transform:rotate(0deg); }

11 to { transform:rotate(360deg); }

12 }

13

14 @-webkit-keyframes spin {

15 from { -webkit-transform:rotate(0deg); }

16 to { -webkit-transform:rotate(360deg); }

17 }

18

19 @-moz-keyframes spin {

20 from { -moz-transform:rotate(0deg); }

21 to { -moz-transform:rotate(360deg); }

22 }

Whatever element we add this spinner class to will start spinning. If we click the Login button now, we will see the spinner.

Login Spinner

Login Spinner

This technique can be used throughout our entire application.

Adding a Login Error Message

If login is not successful, we will want to let our users know. Since our API already gives back error messages, all we have to do is display them.

Inside of our login function, we will check for the success variable that was returned to us by the API.

1 vm.doLogin = function() {

2 vm.processing = true;

3

4 // clear the error

5 vm.error = '';

6

7 Auth.login(vm.loginData.username, vm.loginData.password)

8 .success(function(data) {

9 ...

10

11 // if a user successfully logs in, redirect to users page

12 if (data.success)

13 $location.path('/users');

14 else

15 vm.error = data.message;

16 });

Every time we click login, the error message will be cleared so we don’t see an out-of-date error message. In our view, we just have to check for that error. Above the login button, add the following:

1 <div class="alert alert-danger" ng-if="login.error">

2 {{ login.error }}

3 </div>

If the error variable exists, then we will display this div.

User Doesn't Exist

User Doesn’t Exist

Wrong Password

Wrong Password

With our foundation of the site and login ready to go, we will now want to handle a little more authentication and then create the pages that will show our users.

Authentication

We have already used the Auth factory in our mainController. We’ve called all the functions including isLoggedIn, login, logout, and getUser.

Our user can login and their token is stored in localStorage. If you check your browser’s local storage, you will be able to see the token. Before we can get our list of users for the users pages, we must attach this token to every request. This is where the AuthInterceptor we created in ourauthService comes in handy.

We will use this in the main app.js file. Let’s look at how it is applied:

1 angular.module('userApp', [

2 'ngAnimate',

3 'app.routes',

4 'authService',

5 'mainCtrl',

6 'userCtrl',

7 'userService'

8 ])

9

10 // application configuration to integrate token into requests

11 .config(function($httpProvider) {

12

13 // attach our auth interceptor to the http requests

14 $httpProvider.interceptors.push('AuthInterceptor');

15

16 });

Just like we create .controllers and .factorys on our angular.modules, we can use .config to add extra configurations to our application. In this case, we are adding the AuthInterceptor to the $httpProvider.

The $httpProvider will attach the token to each request. We’ll see exactly where this happens when we request user information in the next section.

Now that we are authenticated, we will be redirected to a page that lists all users in the database.

User Pages

To create each user page, we will need a new controller, route, and view. Let’s start by showing all the users on a single page.

All Users

We will need to go into our userCtrl.js file and add a controller for this page.

All Users Controller

1 // start our angular module and inject userService

2 angular.module('userCtrl', ['userService'])

3

4 // user controller for the main page

5 // inject the User factory

6 .controller('userController', function(User) {

7

8 var vm = this;

9

10 // set a processing variable to show loading things

11 vm.processing = true;

12

13 // grab all the users at page load

14 User.all()

15 .success(function(data) {

16

17 // when all the users come back, remove the processing variable

18 vm.processing = false;

19

20 // bind the users that come back to vm.users

21 vm.users = data;

22 });

23

24 })

When the user page loads, we will use the User factory to go and grab all of our users. Those users will be bound to the vm.users variable so that we can use them in our view. Like the login page, we are using the processing variable to show loading icons.

All Users View (pages/users/all.html)

Our main view will contain a few different components.

· A header with a button to create a new user

· A message that says “Loading Users…”

· A table of our users

· Button to edit a user

1 <div class="page-header">

2 <h1>

3 Users

4 <a href="/users/create" class="btn btn-default">

5 <span class="glyphicon glyphicon-plus"></span>

6 New User

7 </a>

8 </h1>

9

10 </div>

11

12 <!-- LOADING MESSAGE -->

13 <div class="jumbotron text-center" ng-show="user.processing">

14 <span class="glyphicon glyphicon-repeat spinner"></span>

15 <p>Loading Users...</p>

16 </div>

17

18 <table class="table table-bordered table-striped" ng-show="user.users">

19 <thead>

20 <tr>

21 <th>_id</th>

22 <th>Name</th>

23 <th>Username</th>

24 <th class="col-sm-2"></th>

25 </tr>

26 </thead>

27 <tbody>

28

29 <!-- LOOP OVER THE USERS -->

30 <tr ng-repeat="person in user.users">

31 <td>{{ person._id }}</td>

32 <td>{{ person.name }}</td>

33 <td>{{ person.username }}</td>

34 <td class="col-sm-2">

35 <a ng-href="/users/{{ person._id }}" class="btn btn-danger">Edit</a>

36 </td>

37 </tr>

38

39 </tbody>

40 </table>

This will show all of our users in a table. We will only show the loading message if the processing variable is true. We will also only show the user table if the users object has users.

We will use the ng-repeat directive as a table row to loop over all our users. There is also an edit button that will link to the edit user page and pass in the user’s id into the URL.

The last part needed to see this users page is to create the route.

All Users Route

In our app.routes.js file, add the following below the login route:

1 // show all users

2 .when('/users', {

3 templateUrl: 'app/views/pages/users/all.html',

4 controller: 'userController',

5 controllerAs: 'user'

6 });

That will use the controller and view we just created. Visit http://localhost:8080/users in your browser and you will see all the work we’ve done come together.

All Users

All Users

Another thing to note is that if we go into our Chrome network tools and look at the request to get all the users, we will see that Angular did in fact attach our token.

Access Token

Access Token

There is one more thing left to do on this page and that is to provide the functionality to delete a user.

Delete a User

We will need two things to add delete functionality.

1. A function in our controller

2. A button in our view

Let’s create the function first. In our userCtrl.js, add the function:

1 // function to delete a user

2 vm.deleteUser = function(id) {

3 vm.processing = true;

4

5 // accepts the user id as a parameter

6 User.delete(id)

7 .success(function(data) {

8

9 // get all users to update the table

10 // you can also set up your api

11 // to return the list of users with the delete call

12 User.all()

13 .success(function(data) {

14 vm.processing = false;

15 vm.users = data;

16 });

17

18 });

19 };

This deleteUser function will call the delete function in our User factory. When that call is successful, we will make a call to grab all our users and then update the users object, which in turn will update our table.

All that’s needed now is a delete button in the view. In your user table in views/pages/users/all.html, add the following right next to the edit button:

1 <a href="#"

2 ng-click="user.deleteUser(person._id)"

3 class="btn btn-primary">Delete</a>

We are passing in the person._id and we’ll call the deleteUser function when this button is clicked thanks to the ngClick Angular directive.

Now when we click delete, the user will be deleted and our table will be updated. Next up, let’s make the create a user components.

Create a User

Just like the list all users page, we will need a controller, route, and view for the create user page.

Create User Controller

We will add a controller to the userCtrl.js file beneath the first controller we created. We’ll call this one userCreateController.

1 // controller applied to user creation page

2 .controller('userCreateController', function(User) {

3

4 var vm = this;

5

6 // variable to hide/show elements of the view

7 // differentiates between create or edit pages

8 vm.type = 'create';

9

10 // function to create a user

11 vm.saveUser = function() {

12 vm.processing = true;

13

14 // clear the message

15 vm.message = '';

16

17 // use the create function in the userService

18 User.create(vm.userData)

19 .success(function(data) {

20 vm.processing = false;

21

22 // clear the form

23 vm.userData = {};

24 vm.message = data.message;

25 });

26

27 };

28

29 })

This is a pretty standard process to us by now. We have a function called saveUser that will be used in a form in our view. We are also calling the User.create() function in our userService. After a user is created, we will show a message and clear the form so that we will be able to enter a new user.

We are passing in the entire userData object into the create user function which includes:

1 // all info is bound to our form using ng-model

2 {

3 name: "Holly",

4 username: "hollylawly",

5 password: "supersecret"

6 }

The other addition here is the type variable. This will be used in our view. Since we are using the same file (views/pages/users/single.html) for both the creation and the editing pages, we will need a way to differentiate between the two. Sure you could create two separate view files for these pages, but there will only really be 2 minor differences in the view. We are eliminating some repeated code by doing it this way.

Create User Route

1 // form to create a new user

2 // same view as edit page

3 .when('/users/create', {

4 templateUrl: 'app/views/pages/users/single.html',

5 controller: 'userCreateController',

6 controllerAs: 'user'

7 })

Create User View (pages/users/single.html)

1 <div class="page-header">

2 <h1 ng-if="user.type == 'create'">Create User</h1>

3 <h1 ng-if="user.type == 'edit'">Edit User</h1>

4 </div>

5

6 <form class="form-horizontal" ng-submit="user.saveUser()">

7

8 <div class="form-group">

9 <label class="col-sm-2 control-label">Name</label>

10 <div class="col-sm-6">

11 <input type="text"

12 class="form-control"

13 ng-model="user.userData.name">

14 </div>

15 </div>

16

17 <div class="form-group">

18 <label class="col-sm-2 control-label">Username</label>

19 <div class="col-sm-6">

20 <input type="text"

21 class="form-control"

22 ng-model="user.userData.username">

23 </div>

24 </div>

25

26 <div class="form-group">

27 <label class="col-sm-2 control-label">Password</label>

28 <div class="col-sm-6">

29 <input type="password"

30 class="form-control"

31 ng-model="user.userData.password">

32 </div>

33 </div>

34

35 <div class="form-group">

36 <div class="col-sm-offset-2 col-sm-6">

37 <button type="submit"

38 class="btn btn-success btn-lg btn-block"

39 ng-if="user.type == 'create'">Create User</button>

40 <button type="submit"

41 class="btn btn-success btn-lg btn-block"

42 ng-if="user.type == 'edit'">Update User</button>

43 </div>

44 </div>

45

46 </form>

47

48 <div class="row show-hide-message" ng-show="user.message">

49 <div class="col-sm-6 col-sm-offset-2">

50

51 <div class="alert alert-info">

52 {{ user.message }}

53 </div>

54

55 </div>

56 </div>

We have our basic form here with inputs bound using ngModel, the form being submitted using ngSubmit, and our error message being shown using ngShow.

The thing to notice here is that we are checking for that type variable and showing Create User vs. Edit User and the button of the form will show Create User or Update User.

Create User

Create User

When we add information into our form and click Create User, the user will be created and the form will be cleared. Our message will also show up.

Edit a User

Let’s run through the edit user page. We will use the same view as the create user page. The only differences are that:

· we have to pass a parameter into the URL (/users/user_id)

· we have to get the users information on page load

· we have to bind that information to our form

Edit User Controller

Since we are being passed the user ID through the URL, we will need to inject Angular’s $routeParams module to get URL parameters.

We will know what the specific parameter is called based on what we name it in our routes file. In this case, we are grabbing $routeParams.user_id.

1 // controller applied to user edit page

2 .controller('userEditController', function($routeParams, User) {

3

4 var vm = this;

5

6 // variable to hide/show elements of the view

7 // differentiates between create or edit pages

8 vm.type = 'edit';

9

10 // get the user data for the user you want to edit

11 // $routeParams is the way we grab data from the URL

12 User.get($routeParams.user_id)

13 .success(function(data) {

14 vm.userData = data;

15 });

16

17 // function to save the user

18 vm.saveUser = function() {

19 vm.processing = true;

20 vm.message = '';

21

22 // call the userService function to update

23 User.update($routeParams.user_id, vm.userData)

24 .success(function(data) {

25 vm.processing = false;

26

27 // clear the form

28 vm.userData = {};

29

30 // bind the message from our API to vm.message

31 vm.message = data.message;

32 });

33 };

34

35 });

When the edit user page loads, we will go and grab that user’s data by using the User.get() function. This will hit our API and grab the user info. We will then bind that object to vm.userData.

Since userData is the object that we used in our views/pages/users/single.html, our form will automatically populate with this data!

Edit User Route

This is where we define the parameter name. We will pass in the :user_id here and that lets $routeParams know that $routeParams.user_id exists in the controller we just made.

1 // page to edit a user

2 .when('/users/:user_id', {

3 templateUrl: 'app/views/pages/users/single.html',

4 controller: 'userEditController',

5 controllerAs: 'user'

6 });

Edit User View (pages/users/single.html)

1 <div class="page-header">

2 <h1 ng-if="user.type == 'create'">Create User</h1>

3 <h1 ng-if="user.type == 'edit'">Edit User</h1>

4 </div>

5

6 <form class="form-horizontal" ng-submit="user.saveUser()">

7

8 <div class="form-group">

9 <label class="col-sm-2 control-label">Name</label>

10 <div class="col-sm-6">

11 <input type="text"

12 class="form-control"

13 ng-model="user.userData.name">

14 </div>

15 </div>

16

17 <div class="form-group">

18 <label class="col-sm-2 control-label">Username</label>

19 <div class="col-sm-6">

20 <input type="text"

21 class="form-control"

22 ng-model="user.userData.username">

23 </div>

24 </div>

25

26 <div class="form-group">

27 <label class="col-sm-2 control-label">Password</label>

28 <div class="col-sm-6">

29 <input type="password"

30 class="form-control"

31 ng-model="user.userData.password">

32 </div>

33 </div>

34

35 <div class="form-group">

36 <div class="col-sm-offset-2 col-sm-6">

37 <button type="submit"

38 class="btn btn-success btn-lg btn-block"

39 ng-if="user.type == 'create'">Create User</button>

40 <button type="submit"

41 class="btn btn-success btn-lg btn-block"

42 ng-if="user.type == 'edit'">Update User</button>

43 </div>

44 </div>

45

46 </form>

47

48 <div class="row show-hide-message" ng-show="user.message">

49 <div class="col-sm-6 col-sm-offset-2">

50

51 <div class="alert alert-info">

52 {{ user.message }}

53 </div>

54

55 </div>

56 </div>

This is the exact same view as the create users page. What is neat here is that since our inputs are data-bound to the userData object and we made the call to grab data in our controller, the user’s information will automatically show in our form.

Edit User

Edit User

Our password of course won’t be shown for security purposes since our API does not return that data.

Animating the Message

Since we already pulled in animate.css via CDN in our index.html file, we can use its classes to animate our Angular directives.

We just have to add some custom CSS. We’ll use the animate.css class to have our message zoom in.

1 /* NGANIMATE

2 ====================== */

3

4 /* show and hide */

5 .show-hide-message.ng-hide-remove {

6 -webkit-animation:zoomIn 0.3s both ease;

7 -moz-animation:zoomIn 0.3s both ease;

8 animation:zoomIn 0.3s both ease;

9 }

To add animations to the ngShow and ngHide directives, the classes are .ng-hide-remove and .ng-hide-add.

Our message will now fly in using this CSS animation.

Conclusion

We now have all the pages done and can handle CRUD on our users. By applying the Node API through the use of Angular services, we are able to create an entire frontend application!

These concepts can be applied to more than just users and you can duplicate these across many types of resources to create a larger application. Hopefully seeing how an entire MEAN application comes together from the backend to the frontend will help understand many things including:

· the separation of backend and frontend

· how using an API can help speed up workflow

· the mechanics of using an API

· creating an Angular application that uses services

· handling frontend Angular authentication

· and much more…

Recap of the Process

Although we have only dealt with users here, this is the foundation for how you can add components to your site. Let’s say you wanted to add a resource to your site like Articles. You will want to perform the same CRUD options on this resource.

Here are the main steps for creating this new component:

Node.js Side

1. Create the Mongoose Model

2. Create the API endpoints as routes

AngularJS Side

1. Create the service to communicate with the API

2. Create controllers for all the different pages (all, create, edit)

3. Set up your views and routes and assign controllers

4. Polish your application with loading icons, animations, and more

The steps above recap the steps we have taken throughout this book. It doesn’t seem like much when broken down into 6 bullet points, but there is a lot of knowledge necessary to execute all those points.


Tip

Tip

A More Advanced Approach (Components)

As you build more Angular applications, you will see a pattern arise; your site will start to be separated by sections. For instance, we have a user section and could move forward to create an article section, music, and whatever other resource.

Lumping all those controllers into the controllers/ folder will become tedious since changing something for our users will require us to go into controllers/userCtrl.js, services/userService.js, app.routes.js, and any views/pages/users/ views that correspond to that.

We can separate our site out even further so that all the user parts are encompassed into a user component. We can then inject the controllers, services, and routes into one main user Angular module and inject that into our entire applications parent module.

To better understand this concept, here’s how the directory structure would look.

Components Public Folder Structure

1 - public/

2 ----- assets/

3 ---------- css/

4 --------------- style.css

5 ----- app/

6 ---------- shared/ // reusable components

7 --------------- sidebar/

8 -------------------- sidebarView.html

9 -------------------- sidebarController.html

10 -------------------- sidebarService.html

11 ---------- components/

12 --------------- users/

13 -------------------- userCtrl.js

14 -------------------- userService.js

15 -------------------- userView.html

16 --------------- articles/

17 -------------------- articleCtrl.js

18 -------------------- articleService.js

19 -------------------- articleView.html

20 ---------- app.js

21 ---------- app.routes.js

22 ---------- index.html

Again, this is a more advanced approach for larger applications. For the purposes of our demos, the structure we’ve been using works. Just keep it in mind because as our applications grow, it might be worth looking into this structure.


Practice makes perfect and the more MEAN applications that you create the more these concepts will solidify in your mind.

Next Up

We have our MEAN stack application done, but there is still so much more to learn. The next thing we need to do is deploy our site so that other people are able to view it online. After all, what good is all this work if we can’t show it off? Let’s look at how we can deploy our awesome new site to the web.