Lesson 7: Creating a MEAN CRUD Module

In the previous Lessons, you learned how to set up each framework and how to connect them all together. In this Lesson, you're going to implement the basic operational building blocks of a MEAN application, the CRUD module. CRUD modules consist of a base entity with the basic functionality of creating, reading, updating, and deleting entity instances. In a MEAN application, your CRUD module is built from the server-side Express components and an AngularJS client module. In this Lesson, we'll cover the following topics:

  • Setting up the Mongoose model
  • Creating the Express controller
  • Wiring the Express routes
  • Creating and organizing the AngularJS module
  • Introduction to the AngularJS ngResource module
  • Implementing the AngularJS module MVC

Introducing CRUD modules

CRUD modules are the basic building block of a MEAN application. Each CRUD module consists of a two MVC structure supporting the module Express and AngularJS functionality. The Express part is built upon a Mongoose model, an Express controller, and an Express routes file. The AngularJS module is a bit more complex and contains a set of views, and an AngularJS controller, service, and routing configuration. In this Lesson, you'll learn how to combine these components together to build an example Article CRUD module. The examples in this Lesson will continue directly from those in previous Lessons, so copy the final example from Lesson 6, Introduction to AngularJS, and let's start from there.

Setting up the Express components

Let's begin with the Express part of the module. First, you'll create a Mongoose model that will be used to save and validate your articles. Then, you'll move on to the Express controller that will deal with the business logic of your module. Finally, you'll wire the Express routes to produce a RESTful API for your controller methods. We'll begin with the Mongoose model.

Creating the Mongoose model

The Mongoose model will consist of four simple properties that will represent our Article entity. Let's begin by creating the Mongoose model file in the app/models folder, create a new file named article.server.model.js that contains the following code snippet:

var mongoose = require('mongoose'),
    Schema = mongoose.Schema;

var ArticleSchema = new Schema({
  created: {
    type: Date,
    default: Date.now
  },
  title: {
    type: String,
    default: '',
    trim: true,
    required: 'Title cannot be blank'
  },
  content: {
    type: String,
    default: '',
    trim: true
  },
  creator: {
    type: Schema.ObjectId,
    ref: 'User'
  }
});

mongoose.model('Article', ArticleSchema);

You should be familiar with this code snippet, so let's quickly go over this model. First, you included your model dependencies and then you used the Mongoose Schema object to create a new ArticleSchema. The ArticleSchema defines four model fields:

  • created: This is a date field that represents the time at which the article was created
  • title: This is a string field that represents the article title; notice how you used the required validation to make sure all articles have a title
  • content: This is a string field that represents the article content
  • creator: This is a reference object that represents the user who created the article

In the end, you registered the Article Mongoose model to allow you to use it in the Articles Express controller. Next, you'll need to make sure your application is loading the model file, so go back to the config/mongoose.js file and change it as follows:

var config = require('./config'),
    mongoose = require('mongoose');

module.exports = function() {
  var db = mongoose.connect(config.db);

  require('../app/models/user.server.model');
  require('../app/models/article.server.model');

  return db;
};

This will load your new model file and make sure your application can use your Article model. Once you have your model configured, you'll be able to create your Articles controller.

Setting up the Express controller

The Express controller is responsible for managing articles related functionality on the server side. It is built to offer the basic CRUD operations to manipulate the MongoDB article documents. To begin writing the Express controller, go to your app/controllers folder and create a new file named articles.server.controller.js. In your newly created file, add the following dependencies:

var mongoose = require('mongoose'),
    Article = mongoose.model('Article');

In the preceding lines of code, you basically just included your Article mongoose model. Now, before you begin creating the CRUD methods, it is recommended that you create an error handling method for validation and other server errors.

The error handling method of the Express controller

In order to handle Mongoose errors, it is preferable to write a simple error handling method that will take care of extracting a simple error message from the Mongoose error object and provide it to your controller methods. Go back to your app/controllers/articles.server.controller.js file and append the following lines of code:

var getErrorMessage = function(err) {
  if (err.errors) {
    for (var errName in err.errors) {
      if (err.errors[errName].message) return err.errors[errName].message;
    }
  } else {
    return 'Unknown server error';
  }
};

The getErrorMessage() method gets the Mongoose error object passed as an argument then iterates over the errors collection and extract the first message. This is done because you don't want to overwhelm your users with multiple error messages at once. Now that you have error handling set up, it is time to write your first controller method.

The create() method of the Express controller

The create() method of the Express controller will provide the basic functions to create a new article document. It will use the HTTP request body as the JSON base object for the document and will use the model save() method to save it to MongoDB. To implement the create() method, append the following lines of code in your app/controllers/articles.server.controller.js file:

exports.create = function(req, res) {
  var article = new Article(req.body);
  article.creator = req.user;

  article.save(function(err) {
    if (err) {
      return res.status(400).send({
        message: getErrorMessage(err)
      });
    } else {
      res.json(article);
    }
  });
};

Let's go over the create() method code. First, you created a new Article model instance using the HTTP request body. Next, you added the authenticated Passport user as the article creator(). Finally, you used the Mongoose instance save() method to save the article document. In the save() callback function, it is worth noticing how you either return an error response and an appropriate HTTP error code or the new article object as a JSON response. Once you're done with the create() method, you will move on to implement the read operation. The read operation consists of two methods, one that retrieves a list of articles and a second method that retrieves a particular article. Let's begin with the method that lists a collection of articles.

The list() method of the Express controller

The list() method of the Express controller will provide the basic operations to retrieve a list of existing articles. It will use the model's find() method to retrieve all the documents in the articles collection then output a JSON representation of this list. To implement the list() method, append the following lines of code in your app/controllers/articles.server.controller.js file:

exports.list = function(req, res) {
  Article.find().sort('-created').populate('creator', 'firstName   lastName fullName').exec(function(err, articles) {
    if (err) {
      return res.status(400).send({
        message: getErrorMessage(err)
      });
    } else {
      res.json(articles);
    }
  });
};

In this controller method, notice how you used the find() function of Mongoose to get the collection of article documents, and while we could add a MongoDB query of some sort, for now we'll retrieve all the documents in the collection. Next, you'll notice how the articles collection is sorted using the created property. Then, you can see how the populate() method of Mongoose was used to add some user fields to the creator property of the articles objects. In this case, you populated the firstName, lastName, and fullName properties of the creator user object.

The rest of the CRUD operations involve a manipulation of a single existing article document. You could of course implement the retrieval of the article document in each method by itself, basically repeating this logic. However, the Express router has a neat feature for handling route parameters, so before you'll implement the rest of your Express CRUD functionality, you'll first learn how to leverage the route parameter middleware to save some time and code redundancy.

The read() middleware of the Express controller

The read() method of the Express controller will provide the basic operations to read an existing article document from the database. Since you're writing a sort of a RESTful API, the common usage of this method will be handled by passing the article's ID field as a route parameter. This means that your requests to the server will contain an articleId parameter in their paths.

Fortunately, the Express router provides the app.param() method for handling route parameters. This method allows you to attach a middleware for all requests containing the articleId route parameter. The middleware itself will then use the articleId provided to find the proper MongoDB document and add the retrieved article object to the request object. This will allow all the controller methods that manipulate an existing article to obtain the article object from the Express request object. To make this clearer, let's implement the route parameter middleware. Go to your app/controllers/articles.server.controller.js file and append the following lines of code:

exports.articleByID = function(req, res, next, id) {
  Article.findById(id).populate('creator', 'firstName lastName fullName').exec(function(err, article) {
    if (err) return next(err);
    if (!article) return next(new Error('Failed to load article ' + id));

    req.article = article;
    next();
  });
};

As you can see, the middleware function signature contains all the Express middleware arguments and an id argument. It then uses the id argument to find an article and reference it using the req.article property. Notice how the populate() method of the Mongoose model was used to add some user fields to the creator property of the article object. In this case, you populated the firstName, lastName, and fullName properties of the creator user object.

When you connect your Express routes, you'll see how to add the articleByID() middleware to different routes, but for now let's add the read() method of the Express controller, which will return an article object. To add the read() method, append the following lines of code to your app/controllers/articles.server.controller.js file:

exports.read = function(req, res) {
  res.json(req.article);
};

Quite simple, isn't it? That's because you already took care of obtaining the article object in the articleByID() middleware, so now all you have to do is just output the article object as a JSON representation. We'll connect the middleware and routes in next sections but before we'll do that, let's finish implementing the Express controller CRUD functionality.

The update() method of the Express controller

The update() method of the Express controller will provide the basic operations to update an existing article document. It will use the existing article object as the base object, and then update the title and content fields using the HTTP request body. It will also use the model save() method to save the changes to the database. To implement the update() method, go to your app/controllers/articles.server.controller.js file and append the following lines of code:

exports.update = function(req, res) {
  var article = req.article;

  article.title = req.body.title;
  article.content = req.body.content;

  article.save(function(err) {
    if (err) {
      return res.status(400).send({
        message: getErrorMessage(err)
      });
    } else {
      res.json(article);
    }
  });
};

As you can see, the update() method also makes the assumption that you already obtained the article object in the articleByID() middleware. So, all you have to do is just update the title and content fields, save the article, and then output the updated article object as a JSON representation. In case of an error, it will output the appropriate error message using the getErrorMessage() method you wrote before and an HTTP error code. The last CRUD operation left to implement is the delete() method; so let's see how you can add a simple delete() method to your Express controller.

The delete() method of the Express controller

The delete() method of the Express controller will provide the basic operations to delete an existing article document. It will use the model remove() method to delete the existing article from the database. To implement the delete() method, go to your app/controllers/articles.server.controller.js file and append the following lines of code:

exports.delete = function(req, res) {
  var article = req.article;

  article.remove(function(err) {
    if (err) {
      return res.status(400).send({
        message: getErrorMessage(err)
      });
    } else {
      res.json(article);
    }
  });
};

Again, you can see how the delete() method also makes use of the already obtained article object by the articleByID() middleware. So, all you have to do is just invoke the Mongoose model's remove() method and then output the deleted article object as a JSON representation. In case of an error, it will instead output the appropriate error message using the getErrorMessage() method you wrote before and an HTTP error code.

Congratulations! You just finished implementing your Express controller's CRUD functionality. Before you continue to wire the Express routes that will invoke these methods, let's take some time to implement two authorization middleware.

Implementing an authentication middleware

While building your Express controller, you probably noticed that most methods require your user to be authenticated. For instance, the create() method won't be operational if the req.user object is not assigned. While you can check this assignment inside your methods, this will enforce you to implement the same validation code over and over. Instead you can just use the Express middleware chaining to block unauthorized requests from executing your controller methods. The first middleware you should implement will check whether a user is authenticated at all. Since it is an authentication-related method, it would be best to implement it in the Express users controller, so go to the app/controllers/users.server.controller.js file and append the following lines of code:

exports.requiresLogin = function(req, res, next) {
  if (!req.isAuthenticated()) {
    return res.status(401).send({
      message: 'User is not logged in'
    });
  }

  next();
};

The requiresLogin() middleware uses the Passport initiated req.isAuthenticated() method to check whether a user is currently authenticated. If it finds out the user is indeed signed in, it will call the next middleware in the chain; otherwise it will respond with an authentication error and an HTTP error code. This middleware is great, but if you want to check whether a specific user is authorized to perform a certain action, you will need to implement an article specific authorization middleware.

Implementing an authorization middleware

In your CRUD module, there are two methods that edit an existing article document. Usually, the update() and delete() methods should be restricted so that only the user who created the article will be able to use them. This means you need to authorize any request made to these methods to validate whether the current article is being edited by its creator. To do so, you will need to add an authorization middleware to your Articles controller, so go to the app/controllers/articles.server.controller.js file and append the following lines of code:

exports.hasAuthorization = function(req, res, next) {
    if (req.article.creator.id !== req.user.id) {
        return res.status(403).send({
            message: 'User is not authorized'
        });
    }
    next();
};

The hasAuthorization() middleware is using the req.article and req.user objects to verify that the current user is the creator of the current article. This middleware also assumes that it gets executed only for requests containing the articleId route parameter. Now that you have all your methods and middleware in place, it is time to wire the routes that enable their execution.

Wiring the Express routes

Before we begin wiring the Express routes, let's do a quick overview of the RESTful API architectural design. The RESTful API provides a coherent service structure that represents a set of actions you can perform on an application resource. This means the API uses a predefined route structure along with the HTTP method name to provide context for HTTP requests. Though the RESTful architecture can be applied in different ways, a RESTful API usually complies with a few simple rules:

  • A base URI per resource, in our case http://localhost:3000/articles
  • A data structure, usually JSON, passed in the request body
  • Usage of standard HTTP methods (for example, GET, POST, PUT, and DELETE)

Using these three rules, you'll be able to properly route HTTP requests to use the right controller method. So, your articles API will consist of five routes:

  • GET http://localhost:3000/articles: This will return a list of articles
  • POST http://localhost:3000/articles : This will create and return a new article
  • GET http://localhost:3000/articles/:articleId: This will return a single existing article
  • PUT http://localhost:3000/articles/:articleId: This will update and return a single existing article
  • DELETE http://localhost:3000/articles/:articleId: This will delete and return a single article

As you probably noticed, these routes already have corresponding controller methods. You even have the articleId route parameter middleware already implemented, so all that is left to do is implement the Express routes. To do so, go to the app/routes folder and create a new file named articles.server.routes.js. In your newly created file, paste the following code snippet:

var users = require('../../app/controllers/users.server.controller'),
    articles = require('../../app/controllers/articles.server.controller');

module.exports = function(app) {
  app.route('/api/articles')
     .get(articles.list)
     .post(users.requiresLogin, articles.create);
  
  app.route('/api/articles/:articleId')
     .get(articles.read)
     .put(users.requiresLogin, articles.hasAuthorization, articles.update)
     .delete(users.requiresLogin, articles.hasAuthorization, articles.delete);

  app.param('articleId', articles.articleByID);
};

In the preceding code snippet, you did several things. First, you required the users and articles controllers, and then you used the Express app.route() method to define the base routes for your CRUD operations. You used the Express routing methods to wire each controller method to a specific HTTP method. You can also notice how the POST method uses the users.requiresLogin() middleware since a user need to log in before they can create a new article. The same way the PUT and DELETE methods use both the users.requiresLogin() and articles.hasAuthorization() middleware, since users can only edit and delete the articles they created. Finally, you used the app.param() method to make sure every route that has the articleId parameter will first call the articles.articleByID() middleware. Next, you'll need to do is configure your Express application to load your new Article model and routes file.

Configuring the Express application

In order to use your new Express assets, you have to configure your Express application to load your route file. To do so, go back to your config/express.js file and change it as follows:

var config = require('./config'),
    express = require('express'),
    morgan = require('morgan'),
    compress = require('compression'),
    bodyParser = require('body-parser'),
    methodOverride = require('method-override'),
    session = require('express-session'),
    flash = require('connect-flash'),
    passport = require('passport');

module.exports = function() {
  var app = express();

  if (process.env.NODE_ENV === 'development') {
    app.use(morgan('dev'));
  } else if (process.env.NODE_ENV === 'production') {
    app.use(compress());
  }

  app.use(bodyParser.urlencoded({
    extended: true
  }));
  app.use(bodyParser.json());
  app.use(methodOverride());
  app.use(session({
    saveUninitialized: true,
    resave: true,
    secret: config.sessionSecret
  }));

  app.set('views', './app/views');
  app.set('view engine', 'ejs');

  app.use(flash());
  app.use(passport.initialize());
  app.use(passport.session());

  require('../app/routes/index.server.routes.js')(app);
  require('../app/routes/users.server.routes.js')(app);	
  require('../app/routes/articles.server.routes.js')(app);

  app.use(express.static('./public'));

  return app;
};

This is it, your articles RESTful API is ready! Next, you'll learn how simple it is to use the ngResource module to let your AngularJS entities communicate with it.

Introducing the ngResource module

In Lesson 6, Introduction to AngularJS, we mentioned the $http service as means of communication between the AngularJS application and your backend API. While the $http service provides the developer with a low-level interface for the HTTP request, the AngularJS team figured out they could better help developers when it comes to RESTful APIs. Since the REST architecture is well structured, much of the client code dealing with AJAX requests could be obfuscated using a higher-level interface. For this purpose, the team created the ngResource module, which provides the developer with an easy way to communicate with a RESTful data source. It does so by presenting a factory, which creates an ngResource object that can handle the basic routes of a RESTful resource. We'll explain how it works in next sections but ngResource is an external module, so first you'll need to install it using Bower.

Installing the ngResource module

Installing the ngResource module is easy, simply go to your bower.json file and change it as follows:

{
  "name": "MEAN",
  "version": "0.0.8",
  "dependencies": {
    "angular": "~1.2",
    "angular-route": "~1.2",	
    "angular-resource": "~1.2"
  }
}

Now, use your command-line tool to navigate to the MEAN application's root folder and install the new ngResource module:

$ bower update

When Bower finishes installing the new dependency, you will see a new folder named angular-resource in your public/lib folder. Next, you will need to include the module file in your application's main page, so edit your app/views/index.ejs file as follows:

<!DOCTYPE html>
<html xmlns:ng="http://angularjs.org">
<head>
  <title><%= title %></title>
</head>
<body>
  <% if (user) { %>
    <a href="/signout">Sign out</a>
  <% } else { %>
    <a href="/signup">Signup</a>
    <a href="/signin">Signin</a>
  <% } %>
  <section ng-view></section>
  
  <script type="text/javascript">
    window.user = <%- user || 'null' %>;
  </script>
    
  <script type="text/javascript" src="/lib/angular/angular.js"></script>
  <script type="text/javascript" src="/lib/angular-route/angular-route.js"></script>
  <script type="text/javascript" src="/lib/angular-resource/angular-resource.js"></script>


  <script type="text/javascript" src="/example/example.client.module.js"></script>
  <script type="text/javascript" src="/example/controllers/example.client.controller.js"></script>
  <script type="text/javascript" src="/example/config/example.client.routes.js"></script>

  <script type="text/javascript" src="/users/users.client.module.js"></script>
  <script type="text/javascript" src="/users/services/authentication.client.service.js"></script>

  <script type="text/javascript" src="/application.js"></script>
</body>
</html>

Finally, you will need to add the ngResource module as a dependency for your main application module, so change your public/application.js file as follows:

var mainApplicationModuleName = 'mean';

var mainApplicationModule = angular.module(mainApplicationModuleName, ['ngResource', 'ngRoute', 'users', 'example']);

mainApplicationModule.config(['$locationProvider',
  function($locationProvider) {
    $locationProvider.hashPrefix('!');
  }
]);

if (window.location.hash === '#_=_') window.location.hash = '#!';

angular.element(document).ready(function() {
  angular.bootstrap(document, [mainApplicationModuleName]);
});

When you're done with these changes, the ngResource module will be set up and ready to use.

Using the $resource service

The ngResource module provides the developer with a new factory that can be injected to AngularJS entities. The $resource factory uses a base URL and a set of configuration options to allow the developer easy communication with RESTful endpoints. To use the ngResource module, you have to call the $resource factory method, which will return a $resource object. The $resource factory method accepts four arguments:

  • Url: This is a parameterized base URL with parameters prefixed by a colon such as /users/:userId
  • ParamDefaults: These are the default values for the URL parameters, which can include hardcoded values or a string prefixed with @ so the parameter value is extracted from the data object
  • Actions: These are objects representing custom methods you can use to extend the default set of resource actions
  • Options: These are objects representing custom options to extend the default behavior of $resourceProvider

The returned ngResource object will have several methods to handle the default RESTful resource routes, and it can optionally be extended by custom methods. The default resource methods are as follows:

  • get(): This method uses a GET HTTP method and expects a JSON object response
  • save(): This method uses a POST HTTP method and expects a JSON object response
  • query(): This method uses a GET HTTP method and expects a JSON array response
  • remove(): This method uses a DELETE HTTP method and expects a JSON object response
  • delete(): This method uses a DELETE HTTP method and expects a JSON object response

Calling each of these methods will use the $http service and invoke an HTTP request with the specified HTTP method, URL, and parameters. The $resource instance method will then return an empty reference object that will be populated once the data is returned from the server. You can also pass a callback function that will get called once the reference object is populated. A basic usage of the $resource factory method would be as follows:

var Users = $resource('/users/:userId', {
  userId: '@id'
});

var user = Users.get({
  userId: 123
}, function() {
  user.abc = true;
  user.$save();
});

Notice how you can also use the $resource methods from the populated reference object. This is because the $resource methods returns a $resource instance populated with the data fields. In the next section, you'll learn how to use the $resource factory to communicate with your Express API.

Implementing the AngularJS MVC module

The second part of your CRUD module is the AngularJS MVC module. This module will contain an AngularJS service that will communicate with the Express API using the $resource factory, an AngularJS controller that will contain the client-side module logic, and a set of views that provide your users with an interface to perform CRUD operations. Before you begin creating your AngularJS entities, let's first create the module initial structure. Go to your application's public folder and create a new folder named articles. In this new folder, create the module initialization file named articles.client.module.js and paste the following line of code:

angular.module('articles', []);

This will handle module initialization for you, but you will also need to add your new module as a dependency of your main application module. To do so, change your public/application.js file as follows:

var mainApplicationModuleName = 'mean';

var mainApplicationModule = angular.module(mainApplicationModuleName, ['ngResource', 'ngRoute', 'users', 'example', 'articles']);

mainApplicationModule.config(['$locationProvider',
  function($locationProvider) {
    $locationProvider.hashPrefix('!');
  }
]);

if (window.location.hash === '#_=_') window.location.hash = '#!';
angular.element(document).ready(function() {
  angular.bootstrap(document, [mainApplicationModuleName]);
});

This will take care of loading your new module, so you can move on to create your module entities. We'll begin with the module service.

Creating the AngularJS module service

In order for your CRUD module to easily communicate with the API endpoints, it is recommended that you use a single AngularJS service that will utilize the $resource factory method. To do so, go to your public/articles folder and create a new folder named services. In this folder, create a new file named articles.client.service.js and add the following lines of code:

angular.module('articles').factory('Articles', ['$resource', function($resource) {
  return $resource('api/articles/:articleId', {
    articleId: '@_id'
  }, {
    update: {
      method: 'PUT'
    }
  });
}]);

Notice how the service uses the $resource factory with three arguments: the base URL for the resource endpoints, a routing parameter assignment using the article's document _id field, and an actions argument extending the resource methods with an update() method that uses the PUT HTTP method. This simple service provides you with everything you need to communicate with your server endpoints, as you will witness in the next section.

Setting up the AngularJS module controller

As you already know, most of the module logic is usually implemented in an AngularJS controller. In this case, the controller should be able to provide you with all the methods needed to perform CRUD operations. You'll begin by creating the controller file. To do so, go to your public/articles folder and create a new folder named controllers. In this folder, create a new file named articles.client.controller.js with the following code snippet:

angular.module('articles').controller('ArticlesController', ['$scope', '$routeParams', '$location', 'Authentication', 'Articles',
  function($scope, $routeParams, $location, Authentication, Articles) {
    $scope.authentication = Authentication;
  }
]);

Notice how your new ArticlesController is using four injected services:

  • $routeParams: This is provided with the ngRoute module and holds references to route parameters of the AngularJS routes you'll define next
  • $location: This allows you to control the navigation of your application
  • Authentication: You created this service in the previous Lesson and it provides you with the authenticated user information
  • Articles: You created this service in the previous section and it provides you with a set of methods to communicate with RESTful endpoints

Another thing that you should notice is how your controller binds the Authentication service to the $scope object so that views will be able to use it as well. Once you have the controller defined, it will be easy to implement the controller CRUD methods.

The create() method of the AngularJS controller

The create() method of our AngularJS controller will provide the basic operations for creating a new article. To do so, it will use the title and content form fields from the view that called the method, and it will use the Articles service to communicate with the corresponding RESTful endpoint and save the new article document. To implement the create() method, go to your public/articles/controllers/articles.client.controller.js file and append the following lines of code inside your controller's constructor function:

$scope.create = function() {
  var article = new Articles({
    title: this.title,
    content: this.content
  });

  article.$save(function(response) {
    $location.path('articles/' + response._id);
  }, function(errorResponse) {
    $scope.error = errorResponse.data.message;
  });
};

Let's go over the create() method functionality. First, you used the title and content form fields, and then the Articles resource service to create a new article resource. Then, you used the article resource $save() method to send the new article object to the corresponding RESTful endpoint, along with two callbacks. The first callback will be executed when the server responds with a success (200) status code, marking a successful HTTP request. It will then use the $location service to navigate to the route that will present the created article. The second callback will be executed when the server responds with an error status code, marking a failed HTTP request. The callback will then assign the error message to the $scope object, so the view will be able to present it to the user.

The find() and findOne() methods of the AngularJS controller

Your controller will contain two read methods. The first will take care of retrieving a single article and the second will retrieve a collection of articles. Both methods will use the Articles service to communicate with the corresponding RESTful endpoints. To implement these methods, go to your public/articles/controllers/articles.client.controller.js file and append the following lines code inside your controller's constructor function:

$scope.find = function() {
  $scope.articles = Articles.query();
};

$scope.findOne = function() {
  $scope.article = Articles.get({
    articleId: $routeParams.articleId
  });
};

In the preceding code, you defined two methods: the find() method that will retrieve a list of articles and a findOne() method that will retrieve a single article based on the articleId route parameter, which the function obtains directly from the URL. The find() method uses the resource query() method because it expects a collection, while the findOne() method is using the resource get() method to retrieve a single document. Notice how both methods are assigning the result to the $scope variable so that views could use it to present the data.

The update() method of the AngularJS controller

The update() method of the AngularJS controller will provide the basic operations for updating an existing article. To do so, it will use the $scope.article variable, then update it using the view inputs, and the Articles service to communicate with the corresponding RESTful endpoint and save the updated document. To implement the update() method, go to your public/articles/controllers/articles.client.controller.js file and append the following lines of code inside your controller's constructor function:

$scope.update = function() {
  $scope.article.$update(function() {
    $location.path('articles/' + $scope.article._id);
  }, function(errorResponse) {
    $scope.error = errorResponse.data.message;
  });
};

In the update() method, you used the resource article's $update() method to send the updated article object to the corresponding RESTful endpoint, along with two callbacks. The first callback will be executed when the server responds with a success (200) status code, marking a successful HTTP request. It will then use the $location service to navigate to the route that will present the updated article. The second callback will be executed when the server responds with an error status code, marking a failed HTTP request. The callback will then assign the error message to the $scope object so that the view will be able to present it to the user.

The delete() method of the AngularJS controller

The delete() method of the AngularJS controller will provide the basic operations for deleting an existing article. Since the user might delete an article from the list view as well as the read view, the method will either use the $scope.article or $scope.articles variables. This means that it should also address the issue of removing the deleted article from the $scope.articles collection if necessary. The Articles service will be used again to communicate with the corresponding RESTful endpoint and delete the article document. To implement the delete() method, go to your public/articles/controllers/articles.client.controller.js file and append the following lines of code inside your controller's constructor function:

$scope.delete = function(article) {
  if (article) {
    article.$remove(function() {
      for (var i in $scope.articles) {
        if ($scope.articles[i] === article) {
          $scope.articles.splice(i, 1);
        }
      }
    });
  } else {
    $scope.article.$remove(function() {
      $location.path('articles');
    });
  }
};

The delete() method will first figure out whether the user is deleting an article from a list or directly from the article view. It will then use the article's $remove() method to call the corresponding RESTful endpoint. If the user deleted the article from a list view, it will then remove the deleted object from the articles collection; otherwise, it will delete the article then redirect the user back to the list view.

Once you finish setting up your controller, the next step is to implement the AngularJS views that will invoke the controller methods, and then connect them to the AngularJS routing mechanism.

Implementing the AngularJS module views

The next component of your CRUD module is the module views. Each view will take care of providing the user with an interface to execute the CRUD methods you created in the previous section. Before you begin creating the views, you will first need to create the views folder. Go to the public/articles folder, create a new folder named views, and then follow the instructions given in the next section to create your first view.

The create-article view

The create-article view will provide your user with an interface to create a new article. It will contain an HTML form and will use your controller's create method to save the new article. To create your view, go to the public/articles/views folder and create a new file named create-article.client.view.html. In your new file, paste the following code snippet:

<section data-ng-controller="ArticlesController">
<h1>New Article</h1>
  <form data-ng-submit="create()" novalidate>
    <div>
      <label for="title">Title</label>
      <div>
        <input type="text" data-ng-model="title" id="title" placeholder="Title" required>
      </div>
    </div>
    <div>
      <label for="content">Content</label>
      <div>
        <textarea data-ng-model="content" id="content" cols="30" rows="10" placeholder="Content"></textarea>
      </div>
    </div>
    <div>
      <input type="submit">
    </div>
    <div data-ng-show="error">
      <strong data-ng-bind="error"></strong>
    </div>
  </form>
</section>

The create-article view contains a simple form with two text input fields and a submit button. The text fields use the ng-model directive to bind the user input to the controller scope, and as you specified in the ng-controller directive, this controller will be your ArticlesController. It is also important to notice the ng-submit directive you placed on the form element. This directive tells AngularJS to call a specific controller method when the form is submitted; in this case, the form submission will execute your controller's create() method. The last thing you should notice is the error message at the end of the form that will be shown in case of a creation error.

The view-article view

The view-article view will provide your user with an interface to view an existing article. It will contain a set of HTML elements and will use your controller's findOne() method to get an existing article. Your view will also contain a set of buttons only visible to the article creator that will allow the creator to delete the article or navigate to the update-article view. To create the view, go to the public/articles/views folder and create a new file named view-article.client.view.html. In your new file, paste the following code snippet:

<section data-ng-controller="ArticlesController" data-ng-init="findOne()">
  <h1 data-ng-bind="article.title"></h1>
  <div data-ng-show="authentication.user._id == article.creator._id">
    <a href="/#!/articles/{{article._id}}/edit">edit</a>
    <a href="#" data-ng-click="delete();">delete</a>
  </div>
  <small>
    <em>Posted on</em>
    <em data-ng-bind="article.created | date:'mediumDate'"></em>
    <em>by</em>
    <em data-ng-bind="article.creator.fullName"></em>
  </small>
  <p data-ng-bind="article.content"></p>
</section>

The view-article view contains a simple set of HTML elements presenting the article information using the ng-bind directive. Similar to what you did in the create-article view, you used the ng-controller directive to tell the view to use the ArticlesController. However, since you need to load the article information, your view uses the ng-init directive to call the controller's findOne() method when the view is loaded. It is also important to notice how you used the ng-show directive to present the article edit and delete links only to the creator of the article. The first link will direct the user to the update-article view, while the second one will call the delete() method of your controller.

The edit-article view

The edit-article view will provide your user with an interface to update an existing article. It will contain an HTML form and will use your controller's update() method to save the updated article. To create this view go to the public/articles/views folder and create a new file named edit-article.client.view.html. In your new file, paste the following code snippet:

<section data-ng-controller="ArticlesController" data-ng-init="findOne()">
  <h1>Edit Article</h1>
  <form data-ng-submit="update()" novalidate>
    <div>
      <label for="title">Title</label>
      <div>
        <input type="text" data-ng-model="article.title" id="title" placeholder="Title" required>
      </div>
    </div>
    <div>
      <label for="content">Content</label>
      <div>
        <textarea data-ng-model="article.content" id="content" cols="30" rows="10" placeholder="Content"></textarea>
      </div>
    </div>
    <div>
      <input type="submit" value="Update">
    </div>
    <div data-ng-show="error">
      <strong data-ng-bind="error"></strong>
    </div>
  </form>
</section>

The edit-article view contains a simple form with two text input fields and a submit button. In the edit-article view, the text fields use the ng-model directive to bind the user input to the controller's scope.article object. Since you need to load the article information before editing it, your view uses the ng-init directive to call the controller's findOne() method when the view is loaded. It is also important to notice the ng-submit directive you placed on the form element. This time, the directive tells AngularJS that the form submission should execute your controller's update() method. The last thing you should notice is the error message in the end of the form that will be shown in the case of an editing error.

The list-articles view

The list-articles view will provide your user with an interface to view the list of existing articles. It will contain a set of HTML elements and will use your controller's find() method to get the collection of articles. Your view will also use the ng-repeat directive to render a list of HTML elements, each representing a single article. If there aren't any existing articles, the view will offer the user to navigate to the create-article view. To create your view, go to the public/articles/views folder and create a new file named list-articles.client.view.html. In your new file, paste the following code snippet:

<section data-ng-controller="ArticlesController" data-ng-init="find()">
  <h1>Articles</h1>
  <ul>
    <li data-ng-repeat="article in articles">
      <a data-ng-href="#!/articles/{{article._id}}" data-ng-bind="article.title"></a>
      <br>
      <small data-ng-bind="article.created | date:'medium'"></small>
      <small>/</small>
      <small data-ng-bind="article.creator.fullName"></small>
      <p data-ng-bind="article.content"></p>
    </li>
  </ul>
 <div data-ng-hide="!articles || articles.length">
    No articles yet, why don't you <a href="/#!/articles/create">create one</a>?
  </div>
</section>

The list-articles view contains a simple set of repeating HTML elements that represent the list of articles. It uses the ng-repeat directive to duplicate the list item for every article in the collection and displays each article's information using the ng-bind directive. In the same way as in other views, you used the ng-controller directive to connect the view to your ArticlesController. However, since you need to load the articles list, your view also uses the ng-init directive to call the controller's find method when the view is loaded. It is also important to notice how you used the ng-hide directive to ask the user to create a new article in case there are no existing articles.

By implementing your AngularJS views, you came very close to finishing your first CRUD module. All that is left to do is wire the module's routes.

Wiring the AngularJS module routes

To complete your CRUD module, you will need to connect your views to your AngularJS application routing mechanism. This means that you'll need to have a route specified for each view you created. To do so, go to the public/articles folder and create a new config folder. In your config folder, create a new file named articles.client.routes.js that contains the following code:

angular.module('articles').config(['$routeProvider',
  function($routeProvider) {
    $routeProvider.
    when('/articles', {
      templateUrl: 'articles/views/list-articles.client.view.html'
    }).
    when('/articles/create', {
      templateUrl: 'articles/views/create-article.client.view.html'
    }).
    when('/articles/:articleId', {
      templateUrl: 'articles/views/view-article.client.view.html'
    }).
    when('/articles/:articleId/edit', {
      templateUrl: 'articles/views/edit-article.client.view.html'
    });
  }
]);

As you can see, each view will be assigned with its own route. The last two views, which handle an existing article, will also include the articleId route parameters in their URL definition. This will enable your controller to extract the articleId parameter using the $routeParams service. Having your routes defined is the last thing you will have to configure in your CRUD module. All that is left to do is include your module files in the main application page and provide the user with some links to your CRUD module views.

Finalizing your module implementation

To complete your module implementation, you have to include the module JavaScript files in your main application page and change the example view from the previous Lesson to properly show the links to your new module routes. Let's begin by changing your main application page; go to your app/views/index.ejs file and modify it as follows:

<!DOCTYPE html>
<html xmlns:ng="http://angularjs.org">
<head>
  <title><%= title %></title>
</head>
<body>
  <section ng-view></section>
    
  <script type="text/javascript">
    window.user = <%- user || 'null' %>;
  </script>
  
  <script type="text/javascript" src="/lib/angular/angular.js"></script>
  <script type="text/javascript" src="/lib/angular-route/angular-route.js"></script>
  <script type="text/javascript" src="/lib/angular-resource/angular-resource.js"></script>
  <script type="text/javascript" src="/articles/articles.client.module.js"></script>
  <script type="text/javascript" src="/articles/controllers/articles.client.controller.js"></script>
  <script type="text/javascript" src="/articles/services/articles.client.service.js"></script>
  <script type="text/javascript" src="/articles/config/articles.client.routes.js"></script>

  <script type="text/javascript" src="/example/example.client.module.js"></script>
  <script type="text/javascript" src="/example/controllers/example.client.controller.js"></script>
  <script type="text/javascript" src="/example/config/example.client.routes.js"></script>

  <script type="text/javascript" src="/users/users.client.module.js"></script>
  <script type="text/javascript" src="/users/services/authentication.client.service.js"></script>

  <!--Bootstrap AngularJS Application-->
  <script type="text/javascript" src="/application.js"></script>
</body>
</html>

As you can probably see, the authentication links were also removed from the main page. However, don't worry; we'll add them in our home view of the example module. To do so, go to the public/example/views/example.client.view.html file and change it as follows:

<section ng-controller="ExampleController">
  <div data-ng-show="!authentication.user">
    <a href="/signup">Signup</a>
    <a href="/signin">Signin</a>
  </div>
  <div data-ng-show="authentication.user">
    <h1>Hello <span data-ng-bind="authentication.user.fullName"></span></h1>
    <a href="/signout">Signout</a>
    <ul>
      <li><a href="/#!/articles">List Articles</a></li>
      <li><a href="/#!/articles/create">Create Article</a></li>
    </ul>
  </div>
</section>

Notice how the example view now shows the authentication links when the user is not authenticated and your articles module links once the user is signed in. To make this work, you will also need to make a slight change in your ExampleController. Go to the public/example/controllers/example.client.controller.js file and change the way you use your Authentication service:

angular.module('example').controller('ExampleController', ['$scope', 'Authentication',
  function($scope, Authentication) {
    $scope.authentication = Authentication;
  }
]);

This change will allow your example view to fully use the Authentication service. This is it! Everything is ready for you to test your new CRUD module. Use your command-line tool and navigate to the MEAN application's root folder. Then run your application:

$ node server

Once your application is running, use your browser and navigate to http://localhost:3000/#!/. You will see the sign up and sign in links; try signing in and watch how the home view changes. Then, try navigating to the http://localhost:3000/#!/articles URL and see how the list-articles view suggests that you create a new article. Continue to create a new article and try to edit and delete it using the views you previously created. Your CRUD module should be fully operational.

Finalizing your module implementation
Finalizing your module implementation
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.126.239