Trong bài trước, mình đã hướng dẫn các bạn thực hiện kết nối database và truy vấn CRUD đơn giản trên nền tảng của NodeJS, tuy nhiên nó chưa có giao diện và còn khá sơ xài, trong bài này chúng ta sẽ kết hợp với những modules AngularJS đã xây dựng trong phần trước để tạo ra ứng dụng MEAN Stack hoàn chỉnh.

Đầu tiên, chúng ta sẽ cần cài đặt thêm một thư viện NodeJS nữa để nhận giá trị từ form của AngularJS gửi lên. Hãy bổ sung vào phần dependencies dòng này.


"body-parser": "~1.4.2"

Và chạy lệnh npm install để cài đặt thư viện.

Tiếp theo, hãy cấu hình thư viện này trong file server.js. Thêm đoạn code bên dưới sau phần khai báo port nha 🙂


var bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.json({type:'application/vnd.api+json'}));
app.use(bodyParser.urlencoded({extended: true}));

Trong bài này chúng ta sẽ làm việc với thao tác Read – hiển thị danh sách bài viết và bài viết chi tiết đầu tiên vì thao tác này là đơn giản nhất.

Để bắt đầu hãy mở view public/views/list.html lên và thay đổi nội dung cho nó.

<div class="col-xs-6 col-xs-offset-3">
  <h2 class="text-center">Bài viết</h2>
   <div ng-repeat="post in posts">
      <div>
         <h4><a ui-sref="detail({id: post._id})" ng-bind="post.title"></a></h4>
         <a href="#" class="btn btn-info">Sửa</a>
         <a href="#" class="btn btn-danger">Xóa</a>
      </div>
      <hr>
   </div>
</div>

Ở đây ta dùng ng-repeat để lặp biến posts chứa danh sách các bài viết có trong db. ui-sref là một directive của angular ui route nó gần giống thẻ href nhưng nó sử dụng route chứ không phải là đường dẫn tuyệt đối. Route detail chúng ta sẽ viết nó sau.

Giờ hãy mở ListPostController lên và thêm nội dung cho nó. Tại sao chúng ta lại viết trong controller này. Đó là vì chúng ta đã chỉ định controller quản lý view danh sách bài viết là ListPostController, nếu bạn chưa nhớ thì hãy mở file public/js/appRoutes.js lên xem nhé.


.controller('ListPostController',['$scope', '$state','Post', function($scope, $state, Post) {
   Post.get().success(function(data){
     $scope.posts = data;
   });
}])

Tiếp theo viết hàm get() trong postService.js

angular.module('postService', [])
   .factory('Post',['$http', function($http) {
     return {
       get : function() {
         return $http.get('/api/post/list');
     }
   };
}]);

Chuyển qua file app/routes/index.js và mở comment dòng này. Trước khi làm điều này bạn hãy chạy đường dẫn localhost:3000/api/post/create để tạo một số bài viết mẫu đã nhé 😀

app.get('*', function(req, res){
   res.sendfile('public/index.html');
});

Rồi, giờ hãy chạy ứng dụng và truy cập vào localhost:3000, nhấn vào menu danh sách bạn sẽ thấy các bài viết có trong db của bạn.

Tiếp theo ta sẽ làm cho trang chi tiết bài viết. Hãy tạo thêm public/views/detail.html

<div class="post-detail">
   <h1 class="post-title" ng-bind="post.title"></h1>
   <div class="post-info">
     <span class="create-time">
        <i class="fa fa-calendar"></i> <span ng-bind="post.creationDate"></span>
     </span>
   </div>
   <div class="post-content text-justify" ng-bind="post.content"></div>
</div>

Ta sẽ bind một số nội dung như tiêu đề, ngày tạo và nội dung trong view này. giờ mở appRoutes.js lên để quy định đường dẫn cho nó. Hãy thêm vào đoạn code dưới.

.state('detail', {
   url: "/post/detail/:id",
   templateUrl : 'views/detail.html',
   controller :'DetailPostController'
})

Tiếp theo mở DetailPostController và sửa thành.

.controller('DetailPostController',['$scope', '$state', '$http', '$stateParams', 'Post',
   function($scope, $state, $http,$stateParams, Post)
   {
      Post.detail($stateParams.id)
     .success(function(data)
     {
        if(data.title != null){
           $scope.post = data;
        }
        else
          $state.go('404');
    })
   .error(function()
   {
     console.log('error');
   });
}]);

Và mở postService.js thêm vào hàm detail()

detail : function(id){
   return $http.get('api/post/detail/'+ id);
}

Giờ chạy thử và nhấn vào một bài viết trên trang danh sách thì bạn sẽ thấy được trang detail của nó.

Rồi chuyển qua thao tác Delete – xóa bài viết. Bổ sung hàm deletePost() trong ListPostController.

$scope.deletePost = function(post_id){
   Post.delete(post_id).success(function()
   {
      $scope.success = 'Xóa bài viết thành công';
      Post.get().success(function(data){
        $scope.posts = data;
      });
   })
   .error(function() {
      $scope.error = 'Có lỗi trong quá trình xóa bài viết';
   });
}

Mở postService.js lên và thêm vào hàm delete()

delete : function(id){
  return $http.delete('/api/post/delete/'+ id);
}

Phương thức mà ta gọi ở đây là DELETE vậy nên hãy mở app/routes/index.js và sửa lại route xóa thành phương thức DELETE.

app.delete('/api/post/delete/:post_id', function(req, res) {
   Post.remove({_id : req.params.post_id}, function(err, post) {
      if (err)
        return res.send(err);
      return res.json(post);
   });
});

Giờ chuyển qua view list.html và thêm hàm xóa vào view. Ở nút xóa hãy sửa thành

<a ng-click="deletePost(post._id)" class="btn btn-danger">Xóa</a>

Chạy thử và nhấn nút xóa bạn sẽ thấy bài viết sẽ được xóa nhưng không thấy hiện thông báo nào cả, giờ ta sẽ thêm chỗ hiển thị thông báo cho nó. Thêm đoạn code dưới vào trước đoạn lặp giá trị của posts trong view list.html

 <div ng-if="success">
   <div class="alert alert-success">
      <p ng-bind="success"></p>
   </div>
 </div>
 <div ng-if="error">
    <div class="alert alert-danger">
       <p ng-bind="error"></p>
    </div>
 </div>

Giờ chạy thử bạn sẽ thấy thông báo thế này khi xóa thành công.

2015-07-26_073723

Tiếp theo chúng ta sẽ làm việc với thao tác Create. Hãy chuyển qua view tạo bài viết mới nào.

Hãy sửa lại nội dung file public/views/create.html thành thế này:

<div class="col-xs-6 col-xs-offset-3">
   <h2 class="text-center">Thêm bài viết</h2>
   <form name="form" ng-submit="createPost()">
     <div class="form-group">
       <label for="title" class="control-label">Tiêu đề</label>
       <input class="form-control" name="title" ng-model="formData.title" />
   </div>
   <div class="form-group">
      <label for="description" class="control-label">Mô tả ngắn</label>
      <textarea class="form-control" name="description" ng-model="formData.description">
      </textarea>
   </div>
<div class="form-group">
     <label for="content" class="control-label">Nội dung</label>
     <textarea class="form-control" rows="10" name="content" ng-model="formData.content">
     </textarea>
   </div>
<div class="form-group">
   <button type="submit" name="submit" class="btn btn-primary">
     <i class="fa fa-plus" ng-show="!Proccess"></i>
     <i class="fa fa-spinner fa-spin" ng-show="Proccess" ></i>
     Thêm
   </button>
  </div>
 </form>
</div>

Các bạn lưu ý 1 số điểm thay đổi so với ban đầu như sau:

– Bổ sung ng-submit=”createPost()” trong cặp thẻ form: điều này có nghĩa là khi ta nhấn submit form thì nó sẽ gọi và thực hiện các xử lý trong hàm createPost() trong CreatePostController mà ta sẽ viết sau.

– Thêm các thẻ ng-model cho các input.

– Thêm 1 chút xử lý cho button submit, ở đây mình sẽ dùng biến Proccess, nếu giá trị biến này bằng true thì nó sẽ hiện spinner biểu thị trạng thái đang thực thi công việc chèn dữ liệu vào database.

Tiếp theo, ta sẽ viết service để kết nối từ AngularJS qua NodeJS. Hãy sửa nội dung file public/js/services/postService.js thành thế này.

angular.module('postService', [])
  .factory('Post',['$http', function($http) {
    return {
      create : function(formData) {
         return $http.post('/api/post/create', formData);
      }
   };
}]);

Trong file này ta bổ sung thêm hàm create, hàm này có nhiệm vụ chuyển dữ liệu từ form lên đường dẫn tới route ‘/api/post/create’ để xử lý.

Tiếp theo, viết hàm createPost() trong CreatePostController nào.

.controller('CreatePostController',['$scope', '$state','Post', function($scope, $state, Post){
   $scope.formData = {};
   $scope.createPost= function()
   {
     $scope.Proccess = true;
     if (!$.isEmptyObject($scope.formData)) {
       Post.create($scope.formData)
       .success(function(data)
       {
          $scope.formData = {};
          $scope.form.$setPristine();
          $scope.Proccess = false;
          $scope.success = 'Thêm bài viết mới thành công!';
          $state.go('detail', {id: data._id});
       })
       .error(function(data)
       {
          console.log(data);
          $scope.error = 'Có lỗi trong quá trình thêm bài viết.';
       });
    }
    else{
       $scope.error = 'Bạn cần điền đầy đủ các mục.';
       $scope.Proccess = false;
    }
  };
}])

Sửa nội dung route thêm bài viết một chút trong app/routes/index.js, giờ chúng ta sẽ dùng phương thức POST và lấy dữ liệu từ form chứ không gán giá trị mặc định nữa.

app.post('/api/post/create', function(req, res){
  var newPost = new Post();
  newPost.title = req.body.title;
  newPost.description = req.body.description;
  newPost.content = req.body.content;
  newPost.creationDate = new Date();
  newPost.save(function(err, post) {
    if (err)
      return res.send(err);
    return res.json(post);
  });
});

Giờ chạy thử và vào mục thêm bài viết, nhập nội dung và nhấn thêm, nếu thêm thành công nó sẽ chuyển sang trang bài viết chi tiết luôn. Có thể kiểm tra lại bằng cách vào trang /list-post.

Tiếp theo ta sẽ đến thao tác cuối cùng là Update – sửa bài viết. Đầu tiên là tạo view cho nó public/views/edit.html

<div class="col-xs-6 col-xs-offset-3">
   <h2 class="text-center">Sửa bài viết</h2>
   <form name="form" ng-submit="editPost()">
      <div class="form-group">
         <label for="title" class="control-label">Tiêu đề</label>
         <input class="form-control" name="title" ng-model="post.title" />
      </div>
      <div class="form-group">
         <label for="description" class="control-label">Mô tả ngắn</label>
         <textarea class="form-control" name="description" ng-model="post.description">
         </textarea>
      </div>
      <div class="form-group">
         <label for="content" class="control-label">Nội dung</label>
         <textarea class="form-control" rows="10" name="content" ng-model="post.content">
         </textarea>
     </div>
     <div class="form-group">
       <button type="submit" name="submit" class="btn btn-primary"> 
         <i class="fa fa-edit" ng-show="!Proccess"></i>
         <i class="fa fa-spinner fa-spin" ng-show="Proccess" ></i>Cập nhật
       </button>
     </div>
  </form>
</div>

Cũng tương tự như view create.html nhưng thay hàm khi submit là editPost(). Ta sẽ bổ sung hàm này trong DetailPostController.

Viết route cho nó trong appRoutes.js

.state('edit', {
  url: "/post/edit/:id",
  templateUrl : 'views/edit.html',
  controller :'DetailPostController'
})

Giờ thêm hàm editPost() trong DetailPostController

 $scope.editPost = function()
 {
   $scope.Proccess = true;
   if (!$.isEmptyObject($scope.post)) {
     Post.edit($scope.post)
     .success(function(data)
     {
        $scope.Proccess = false;
        $scope.success = 'Sửa bài viết thành công!';
     })
     .error(function()
     {
        $scope.error = 'Có lỗi trong quá trình sửa bài viết.';
     });
   } else {
      $scope.error = 'Bạn cần điền đầy đủ các mục.';
      $scope.Proccess= false;
   }
 };

Tiếp theo viết service cho nó, bổ sung hàm edit trong postService


edit : function(formData) {
  return $http.put('/api/post/edit', formData);
}

Ở đây ta sẽ dùng phương thức PUT cho thao tác sửa bài viết. Sửa lại route index.js một chút.


app.put('/api/post/edit', function(req, res){
  Post.findById(req.body._id, function(err, data){
   if(err)
      return res.send(err);
   data.title = req.body.title;
   data.description = req.body.description;
   data.content = req.body.content;
   data.save(function(err, post) {
     if (err)
       return res.send(err);
     return res.json(post);
   });
  });
});

Bạn mở lại view list.html và thay đổi nút sửa thành thế này


<a ui-sref="edit({id: post._id})" class="btn btn-info">Sửa</a>

Rồi chạy thử nào. Sửa thành công nó sẽ đẩy về trang danh sách bài viết, các thông báo thành công hay lỗi các bạn có thể tự thêm, tương tự như hiển thị thông báo cho thao tác xóa nha 😀

Mình làm demo xong hết rồi mới viết hướng dẫn, vì vậy có thể trong quá trình viết bài này mình bỏ sót đoạn xử lý nào đó. Nếu có vấn đề gì trong quá trình thực hiện bài hướng dẫn này các bạn hãy để lại bình luận nhé 🙂

Cuối cùng, chúc các bạn thành công!

Mình là 1 developer mới vào nghề, chưa có nhiều kinh nghiệm với lập trình web nhưng luôn muốn chia sẻ những hiểu biết của mình với các lập trình viên khác. Khá là gà và lười viết blog, chỉ ham code và chuyên Laravel.