[譯] 使用angularjs建立一個CRUD應用

原文地址:https://www.sitepoint.com/creating-crud-app-minutes-angulars-resourcejavascript

大量的單頁應用都有CRUD操做。若是你使用angularjs建立一個CRUD操做,那麼你能夠利用$resource服務的優點。$resource服務是創建在$http服務之上,而且可使先後端用RESTful標準進行交互變得簡單的一個factory。因此,咱們一塊兒探索一下$resource,而且在angular中實現CRUD的操做。css

前提

$resource服務沒有打包到angularjs中。你須要下載一個獨立的文件叫angular-resource.js而且把它引入到HTML頁面中。這個文件能夠從這裏下載:http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.16/angular-resource.min.jshtml

另外,爲了使用$resource你須要在主模塊中注入ngResource模塊。示例:前端

angular.module('mainApp',['ngResource']); //mainApp is our main module

入門

$resource和 RESTful API工做。這意味着你的 URLs 應該相似於下面的模式:java

圖片

你能夠建立接口使用你選擇的服務器端語言。我使用的是Node + Express + MongoDB爲這個示例應用設計RESTful接口。建立好了這些URLs之後,你就能夠藉助於$resource與這些URLs交互。咱們來看一下$resource其實是怎麼工做的。node

$resource是怎麼工做的?

在controller/service中使用$resource須要先聲明依賴。接下來就像下面的示例同樣,在REST端調用$resource()方法。調用方法以後返回一個$resource一個可用於與REST後端交互的資源類:git

angular.module('myApp.services').factory('Entry', function($resource) {
  return $resource('/api/entries/:id'); // Note the full endpoint address
});

方法返回的是一個資源類對象,默認包含下面5種方法:angularjs

  1. get()github

  2. query()ajax

  3. save()

  4. remove()

  5. delete()

下面咱們看一下如何在controller中使用get(),query()save()方法:

angular.module('myApp.controllers',[]);
angular.module('myApp.controllers').controller('ResourceController',function($scope, Entry) {
  var entry = Entry.get({ id: $scope.id }, function() {
    console.log(entry);
  }); // get() 返回單個entry

  var entries = Entry.query(function() {
    console.log(entries);
  }); //query() 返回有的entry

  $scope.entry = new Entry(); //實例化一個資源類

  $scope.entry.data = 'some data';

  Entry.save($scope.entry, function() {
    //data saved. do something here.
  }); //saves an entry. Assuming $scope.entry is the Entry object  
});

在上面的代碼片斷中 get()方法發起/api/entries/:id的 get 請求。參數:id在被替換爲$scope.id。注意get()方法返回一個空對象,當實際數據從服務器返回的時候自動填充到這個空對象。get()方法的第二個參數是回調方法,當服務器端返回數據的時候執行該回調。這是一個很是有用的技巧,由於你能夠設置get()返回一個空對象,掛載到$scope,綁定視圖。當真實的數據返回且填充到對象,雙向數據綁定觸發,而且更新視圖。

query()方法發起/api/entries(注意這裏沒有:id)的 get請求 並返回一個空數組。當服務器端返回數據時候填充到該數組。將該數組繼續掛在到$scope的模型上,而且在視圖上使用ng-repeat綁定。你也能夠給query()傳遞一個回調方法,在數據從服務前端返回的時候執行。

save()方法發起/api/entries的 post 請求, 第一個參數是 post body。第二個參數爲回調方法,當數據保存成功後執行。你應該記得$resource()方法返回的是一個資源類。因此,在咱們的用例中,咱們能夠調用new Entry()實例化一個真正的對象,設置若干個屬性給它,最後將該對象保存到後端。

假設你只使用get()query()在resource類上(在咱們的例子中resource類是Entry)。全部非 get 請求的方法,例如save()delete()new Entry()實例中都能用(在這裏稱爲$resource實例)。不一樣的是這些方法都以$的開頭。因此這些有用的方法爲:

  1. $save()

  2. $delete()

  3. $remove

例如,$save()方法像下面同樣使用:

$scope.entry = new Entry(); //這個對象有個$save()方法
$scope.entry.$save(function() {
  //$scope.entry序列化爲json做爲post body 發送
});

咱們已經實踐CRUD中的了增長,查找和刪除,剩下的最後一個修改。爲了支持修改操做,咱們須要像下面這樣修改咱們的Entryfactory:

angular.module('myApp.services').factory('Entry', function($resource) {
  return $resource('/api/entries/:id', { id: '@_id' }, {
    update: {
      method: 'PUT' // this method issues a PUT request
    }
  });
});

$resource的第二個 argument 明確標識須要url中:id參數的值是什麼。這裏將其設置爲@_id,這意味着在$resource的實例中無論何時調用方法例如$update()$delete():id的都會被設置爲實例中_id的屬性值。這個是爲 PUT 和 DELETE 請求使用的。注意第三個 argument,它容許咱們給資源類添加自定義方法。若是該方法是一個非 get 請求,在$resource會有一個以$的同名方法。咱們看一下如何使用$update方法。假設咱們在controller中使用:

$scope.entry = Movie.get({ id: $scope.id }, function() {
  // $scope.entry 是
    $scope.entry.data = 'something else';
  $scope.entry.$update(function() {
    //updated in the backend
  });
});

$update()方法調用的時候,過程以下:

  1. Angularjs 知道$update()方法會觸發 URL 爲 /api/entries/:id的 PUT 請求。

  2. 讀取$scope.entry._id值,將此值賦給:id而且生成 URL。

  3. 發起一個請求體爲 $scope.entity的 PUT 請求。

一樣,若是你想刪除一個entry能夠像下面這麼作:

$scope.entry = Movie.get({ id: $scope.id }, function() {
  // $scope.entry 是服務器端返回來的,是一個 Entry 的實例
  $scope.entry.data = 'something else';
  $scope.entry.$delete(function() {
    //gone forever!
  });
});

它和 update 有着一樣的步驟,只是使用 DELETE 替換了 PUT請求類型。

咱們已經覆蓋了CRUD的全部操做,可是還有一小點。$resource方法還有第4個可選擇的參數。這是一個自定義設置的值。目前只有一個 stripTrailingSlashes能夠設置。它的默認值是false,這意味着它會自動刪除 URL 的最後一個 /,若是你不須要能夠像下面這麼作:

angular.module('myApp.services').factory('Entry', function($resource) {
  return $resource('/api/entries/:id', { id: '@_id' }, {
    update: {
      method: 'PUT' // this method issues a PUT request
    }
  }, {
    stripTrailingSlashes: false
  });
});

順便說一句,我沒有覆蓋$resource相關的每個東西。咱們這裏介紹的是最基本的,這將幫助您很快的開始一個應用的 CRUD。若是你仔細研究$resource,你能夠閱讀這篇文章

建立一個Movie的應用

爲了增強$resource的概念咱們爲電影愛好者建立一個應用。這是一個單頁應用,用戶能夠新增一個電影,修改一個已存在的,最後還能夠刪除。咱們將使用$resource與後端 REST API 進行交互。你能夠查看這個在線例子,咱們將它部署在這裏

注意API容許CROS訪問(跨域資源共享),因此你能夠建立一個獨立的 Angular 應用,可使用 http://movieapp-sitepointdemo... 地址爲API。你能夠開發 Angular 應用不用擔憂沒有後端服務。

API

我已經使用 Node 和 Express建立了一個 RESTful 後端服務。看下面的截圖認識一下API接口:
圖片

目錄結構

咱們的 AngularJS 應用採用下面的目錄結構:

movieApp
  /css
    bootstrap.css
    app.css
  /js
    app.js
    controllers.js
    services.js
  /lib
    angular.min.js
    angular-resource.min.js
    angular-ui-router.min.js
  /partials
    _form.html
    movie-add.html
    movie-edit.html
    movie-view.html
    movies.html
  index.html

注意到咱們的路由使用Angular UI Router實現

建立 service 與REST後端交互

像上面部分討論的同樣,咱們建立一個標準的 service 使用$resource與後端 REST API 交互。這個服務定義在js/services.js中。

services.js

angular.module('movieApp.services', []).factory('Movie', function($resource) {
  return $resource('http://movieapp-sitepointdemos.rhcloud.com/api/movies/:id', { id: '@_id' }, {
    update: {
      method: 'PUT'
    }
  });
});

這個 factory 的名字是 Movie. 咱們使用的是 MongoDB,每一個 movie 實例都有一個_id的屬性。rest是簡單而直接的。

如今咱們已經開發好了 service,咱們接下來開發 views 和 controllers。

index.html:建立應用的入口頁面

index.html是咱們應用的入口。咱們須要將全部依賴的 scripts 和 css所有引入到頁面。咱們使用 Bootstrap 來快速的開始佈局。下面是index.html的內容:

<!DOCTYPE html>
  <html data-ng-app="movieApp">
  <head lang="en">
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <base href="/"/>
    <title>The Movie App</title>
    <link rel="stylesheet" type="text/css" href="css/bootstrap.min.css"/>
    <link rel="stylesheet" type="text/css" href="css/app.css"/>
  </head>
  <body>
    <nav class="navbar navbar-default" role="navigation">
      <div class="container-fluid">
        <div class="navbar-header">
          <a class="navbar-brand" ui-sref="movies">The Movie App</a>
        </div>
        <div class="collapse navbar-collapse">
          <ul class="nav navbar-nav">
            <li class="active"><a ui-sref="movies">Home</a></li>
          </ul>
        </div>
      </div>
    </nav>
    <div class="container">
      <div class="row top-buffer">
        <div class="col-xs-8 col-xs-offset-2">
          <div ui-view></div> <!-- This is where our views will load -->
        </div>
      </div>
    </div>
    <script type="text/javascript" src="lib/angular.min.js"></script>
    <script type="text/javascript" src="js/app.js"></script>
    <script type="text/javascript" src="js/controllers.js"></script>
    <script type="text/javascript" src="js/services.js"></script>
    <script type="text/javascript" src="lib/angular-ui-router.min.js"></script>
    <script type="text/javascript" src="lib/angular-resource.min.js"></script>
  </body>
</html>

標籤沒什麼特別須要說明的。須要注意的是<div ui-view></div>,ui-view指令來源於 UI Router模塊,而且做爲咱們視圖的容器。

建立主 Module 和 States

咱們主module和 states定義在js/app.js中:

app.js:

angular.module('movieApp', ['ui.router', 'ngResource', 'movieApp.controllers', 'movieApp.services']);
angular.module('movieApp').config(function($stateProvider) {
  $stateProvider.state('movies', { // 展現全部movie路由
    url: '/movies',
    templateUrl: 'partials/movies.html',
    controller: 'MovieListController'
  }).state('viewMovie', { //展現單個 movie 路由
    url: '/movies/:id/view',
    templateUrl: 'partials/movie-view.html',
    controller: 'MovieViewController'
  }).state('newMovie', { //添加一個新 movie 路由
    url: '/movies/new',
    templateUrl: 'partials/movie-add.html',
    controller: 'MovieCreateController'
  }).state('editMovie', { //修改一個movie路由
    url: '/movies/:id/edit',
    templateUrl: 'partials/movie-edit.html',
    controller: 'MovieEditController'
  });
}).run(function($state) {
  $state.go('movies'); //當程序啓動時候默認跳轉路由
});

因此,咱們的應用有如下4種狀態:

  1. movies

  2. viewMovie

  3. newMovie

  4. editMovie

每個state由url,templateUrlcontroller組成。注意到當主模塊加載的時候路由轉向 movies去展現咱們系統中全部的movies。下面的截圖看一看出每一個路由對應的url是什麼。
圖片

建立模板

全部的模板都在partials目錄下,咱們挨個來看一下他們的內容。

_form.html:

_form.html是一個讓用戶錄入數據的的簡單表單。注意到這個表單會被movie-add.htmlmovie-edit.html引入,由於它們都須要用戶輸入。

下面是_form.html的內容:

<div class="form-group">
  <label for="title" class="col-sm-2 control-label">Title</label>
  <div class="col-sm-10">
    <input type="text" ng-model="movie.title" class="form-control" id="title" placeholder="Movie Title Here"/>
  </div>
</div>
<div class="form-group">
  <label for="year" class="col-sm-2 control-label">Release Year</label>
  <div class="col-sm-10">
    <input type="text" ng-model="movie.releaseYear" class="form-control" id="year" placeholder="When was the movie released?"/>
  </div>
</div>
<div class="form-group">
  <label for="director" class="col-sm-2 control-label">Director</label>
  <div class="col-sm-10">
    <input type="text" ng-model="movie.director" class="form-control" id="director" placeholder="Who directed the movie?"/>
  </div>
</div>
<div class="form-group">
  <label for="plot" class="col-sm-2 control-label">Movie Genre</label>
  <div class="col-sm-10">
    <input type="text" ng-model="movie.genre" class="form-control" id="plot" placeholder="Movie genre here"/>
  </div>
</div>
<div class="form-group">
  <div class="col-sm-offset-2 col-sm-10">
    <input type="submit" class="btn btn-primary" value="Save"/>
  </div>
</div>

模板中使用ng-model綁定不一樣的movie變量屬性到不一樣的scope的movie模型。

movie-add.html:

這個模板接收用戶輸入而且在咱們的系統中保存一個新的movie,下面是具體內容:

<form class="form-horizontal" role="form" ng-submit="addMovie()">
  <div ng-include="'partials/_form.html'"></div>
</form>

當表單提交的時候,會觸發scope 的 addMovie()方法向後端發送一個建立movie的POST請求。

movie-edit.html:

這個模板用於接收用戶收入,而且修改系統在已存在的一個movie。

<form class="form-horizontal" role="form" ng-submit="updateMovie()">
  <div ng-include="'partials/_form.html'"></div>
</form>

這個表單一旦提交,就會觸發scope的 updateMovie()方法像向後端發送一個 PUT 請求去修改movie信息。

movie-view.html::

這個模板用於展現一個movie的詳細信息。內容以下:

<table class="table movietable">
  <tr>
    <td><h3>Details for {{movie.title}}</h3></td>
    <td></td>
  </tr>
  <tr>
    <td>Movie Title</td>
    <td>{{movie.title}}</td>
  </tr>
  <tr>
    <td>Director</td>
    <td>{{movie.director}}</td>
  </tr>
  <tr>
    <td>Release Year</td>
    <td>{{movie.releaseYear}}</td>
  </tr>
  <tr>
    <td>Movie Genre</td>
    <td>{{movie.genre}}</td>
  </tr>
</table>
<div>
  <a class="btn btn-primary" ui-sref="editMovie({id:movie._id})">Edit</a>
</div>

模板最後是一個編輯按鈕。點擊按鈕路由會轉向編輯狀態,而且當前編輯的movie id 會包含在$stateParams中。

movies.html:

這個模板用於展現系統中全部的 movie。

<a ui-sref="newMovie" class="btn-primary btn-lg nodecoration">Add New Movie</a>
<table class="table movietable">
  <tr>
    <td><h3>All Movies</h3></td>
    <td></td>
  </tr>
  <tr ng-repeat="movie in movies">
    <td>{{movie.title}}</td>
    <td>
      <a class="btn btn-primary" ui-sref="viewMovie({id:movie._id})">View</a>
      <a class="btn btn-danger"  ng-click="deleteMovie(movie)">Delete</a>
    </td>
  </tr>
</table>

經過循環展現後端返回的每一個movie對象的詳細詳細。這個也有一個添加按鈕能夠將路由轉向添加狀態,觸發它能夠轉向一個新的頁面而且添加一個新的movie。

在每一個movie後面都有兩個操做按鈕,分別是查看和刪除。查看能夠轉向movie的詳細信息頁面。刪除會將movie永久的刪除掉。

建立controllers

每個路由都對應一個controller。因此,咱們總共有4個controller對應4個路由。全部的controller都在js/controllers.js中。全部的controllers都是調用咱們上面討論的Movie service服務。下面看一下controllers如何調用:

controllers.js:

angular.module('movieApp.controllers', []).controller('MovieListController', function($scope, $state, popupService, $window, Movie) {
  $scope.movies = Movie.query(); //fetch all movies. Issues a GET to /api/movies

  $scope.deleteMovie = function(movie) { // Delete a movie. Issues a DELETE to /api/movies/:id
    if (popupService.showPopup('Really delete this?')) {
      movie.$delete(function() {
        $window.location.href = ''; //redirect to home
      });
    }
  };
}).controller('MovieViewController', function($scope, $stateParams, Movie) {
  $scope.movie = Movie.get({ id: $stateParams.id }); //Get a single movie.Issues a GET to /api/movies/:id
}).controller('MovieCreateController', function($scope, $state, $stateParams, Movie) {
  $scope.movie = new Movie();  //create new movie instance. Properties will be set via ng-model on UI

  $scope.addMovie = function() { //create a new movie. Issues a POST to /api/movies
    $scope.movie.$save(function() {
      $state.go('movies'); // on success go back to home i.e. movies state.
    });
  };
}).controller('MovieEditController', function($scope, $state, $stateParams, Movie) {
  $scope.updateMovie = function() { //Update the edited movie. Issues a PUT to /api/movies/:id
    $scope.movie.$update(function() {
      $state.go('movies'); // on success go back to home i.e. movies state.
    });
  };

  $scope.loadMovie = function() { //Issues a GET request to /api/movies/:id to get a movie to update
    $scope.movie = Movie.get({ id: $stateParams.id });
  };

  $scope.loadMovie(); // Load a movie which can be edited on UI
});

總結

假設應用部署在localhost/movieApp,你能夠經過http://localhost/movieApp/index.html訪問。若是你是一個電影愛好者,你也能夠在裏面添加你喜好的電影。部署和這篇文章中用到的源碼能夠在GitHub進行下載。

相關文章
相關標籤/搜索