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

大量的单页应用都有CRUD操作。如果你使用angularjs创建一个CRUD操作,那么你可以利用$resource服务的优势。$resource服务是建立在$http服务之上,并且可以使前后端用RESTful标准进行交互变得简单的一个factory。所以,我们一起探索一下$resource,并且在angular中实现CRUD的操作。

前提

$resource服务没有打包到angularjs中。你需要下载一个独立的文件叫angular-resource.js并且把它引入到HTML页面中。这个文件可以从这里下载:http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.16/angular-resource.min.js。

另外,为了使用$resource你需要在主模块中注入ngResource模块。示例:

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

入门

$resource和 RESTful API工作。这意味着你的 URLs 应该类似于下面的模式:

你可以创建接口使用你选择的服务器端语言。我使用的是Node + Express + MongoDB为这个示例应用设计RESTful接口。创建好了这些URLs以后,你就可以借助于$resource与这些URLs交互。我们来看一下$resource实际上是怎么工作的。

$resource是怎么工作的?

在controller/service中使用$resource需要先声明依赖。接下来就像下面的示例一样,在REST端调用$resource()方法。调用方法之后返回一个$resource一个可用于与REST后端交互的资源类:

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

方法返回的是一个资源类对象,默认包含下面5种方法:

  1. get()

  2. query()

  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',{
    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',{
    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',$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',$stateParams,Movie) {
  $scope.movie = Movie.get({ id: $stateParams.id }); //Get a single movie.Issues a GET to /api/movies/:id
}).controller('MovieCreateController',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',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进行下载。

[译] 使用angularjs创建一个CRUD应用的更多相关文章

  1. HTML5新增form控件和表单属性实例代码详解

    这篇文章主要介绍了HTML5新增form控件和表单属性实例代码详解,需要的朋友可以参考下

  2. HTML5表单验证特性(知识点小结)

    这篇文章主要介绍了HTML5表单验证特性的一些知识点,本文通过实例代码截图的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  3. amazeui页面分析之登录页面的示例代码

    这篇文章主要介绍了amazeui页面分析之登录页面的示例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  4. ios – 如何使对象ID具有人类可读性?

    故事板上的每个UIView都有一个唯一的对象ID,类似于:kvf-NI-koG我想知道有没有办法让这个更友好,例如’myLabel’?

  5. ios – Swift Eureka Form中的循环

    我正在构建一个Eureka表单,并希望在表单中放置一个循环来构建基于数组的步进器列表.我试图使用的代码是:但是,当我这样做时,我在StepperRow行上出现了一个错误:所以看起来Swift不再认为它在形式之内并且正在关注

  6. swift 上传图片和参数 upload image with params

    Alamofire.upload(urlRequest.0,urlRequest.1).progress{(bytesWritten,totalBytesWritten,totalBytesExpectedToWrite)inprintln("\(totalBytesWritten)/\(totalBytesExpectedToWrite)")}}

  7. Swift 3.0 API设计准则

    Swift代码的简洁性,不是指使用最少的字符来实现程序代码。Swift编程的简洁性带来的一个副作用是由强类型系统和减少引用文件的特性决定的。使用Swift的标记语法,为每一个方法和属性写注释性文本。{在初步设计时,编写注释性文档是一个好的主意,因为这能使你对API设计有更深入地理解,从而有利用于API的进一步设计。

  8. swift – 使用PostgreSQL在Vapor 3中上传图片

    我正在关注这些家伙MartinLasek教程,现在我正在“图片上传”.似乎没有人能回答“如何上传iVapor3图像”的问题Db连接正常,所有其他值都保存.这是我的创建方法:和型号:}和叶子模板:我知道需要一种管理文件的方法和原始图像字节,但我怎么去那里?这使用多部分表单的自动解码:upload.leaf文件是:使用File类型可以访问上载文件的本地文件名以及文件数据.如果将其余的Question字段添加到ExampleUpload结构中,则可以使用该路径捕获整个表单的字段.

  9. Android Studio是否支持用于Android UI设计的AngularJS?

    我对AndroidStudio有疑问:AS在设计XML文件时是否支持AngularJS代码,例如:对于小动画或效果?

  10. android – 如何使用ClientID和ClientSecret在Phonegap中使用Angularjs登录Google OAuth2

    我正尝试使用Angularjs(使用IonicFramework)通过GoogleOAuth2从我的Phonegap应用程序登录.目前我正在使用http://phonegap-tips.com/articles/google-api-oauth-with-phonegaps-inappbrowser.html进行登录.但是当我使用Angular-UI-RouterforIonic时,它正在创建非常

随机推荐

  1. Angular2 innerHtml删除样式

    我正在使用innerHtml并在我的cms中设置html,响应似乎没问题,如果我这样打印:{{poi.content}}它给了我正确的内容:``但是当我使用[innerHtml]=“poi.content”时,它会给我这个html:当我使用[innerHtml]时,有谁知道为什么它会剥离我的样式Angular2清理动态添加的HTML,样式,……

  2. 为Angular根组件/模块指定@Input()参数

    我有3个根组件,由根AppModule引导.你如何为其中一个组件指定@input()参数?也不由AppModalComponent获取:它是未定义的.据我所知,你不能将@input()传递给bootstraped组件.但您可以使用其他方法来做到这一点–将值作为属性传递.index.html:app.component.ts:

  3. angular-ui-bootstrap – 如何为angular ui-bootstrap tabs指令指定href参数

    我正在使用角度ui-bootstrap库,但我不知道如何为每个选项卡指定自定义href.在角度ui-bootstrap文档中,指定了一个可选参数select(),但我不知道如何使用它来自定义每个选项卡的链接另一种重新定义问题的方法是如何使用带有角度ui-bootstrap选项卡的路由我希望现在还不算太晚,但我今天遇到了同样的问题.你可以通过以下方式实现:1)在控制器中定义选项卡href:2)声明一个函数来改变控制器中的散列:3)使用以下标记:我不确定这是否是最好的方法,我很乐意听取别人的意见.

  4. 离子框架 – 标签内部的ng-click不起作用

    >为什么标签标签内的按钮不起作用?>但是标签外的按钮(登陆)工作正常,为什么?>请帮我解决这个问题.我需要在点击时做出回复按钮workingdemo解决方案就是不要为物品使用标签.而只是使用divHTML

  5. Angular 2:将值传递给路由数据解析

    我正在尝试编写一个DataResolver服务,允许Angular2路由器在初始化组件之前预加载数据.解析器需要调用不同的API端点来获取适合于正在加载的路由的数据.我正在构建一个通用解析器,而不是为我的许多组件中的每个组件设置一个解析器.因此,我想在路由定义中传递指向正确端点的自定义输入.例如,考虑以下路线:app.routes.ts在第一个实例中,解析器需要调用/path/to/resourc

  6. angularjs – 解释ngModel管道,解析器,格式化程序,viewChangeListeners和$watchers的顺序

    换句话说:如果在模型更新之前触发了“ng-change”,我可以理解,但是我很难理解在更新模型之后以及在完成填充更改之前触发函数绑定属性.如果您读到这里:祝贺并感谢您的耐心等待!

  7. 角度5模板形式检测形式有效性状态的变化

    为了拥有一个可以监听其包含的表单的有效性状态的变化的组件并执行某些组件的方法,是reactiveforms的方法吗?

  8. Angular 2 CSV文件下载

    我在springboot应用程序中有我的后端,从那里我返回一个.csv文件WheniamhittingtheURLinbrowsercsvfileisgettingdownloaded.现在我试图从我的角度2应用程序中点击此URL,代码是这样的:零件:服务:我正在下载文件,但它像ActuallyitshouldbeBook.csv请指导我缺少的东西.有一种解决方法,但您需要创建一个页面上的元

  9. angularjs – Angular UI-Grid:过滤后如何获取总项数

    提前致谢:)你应该避免使用jQuery并与API进行交互.首先需要在网格创建事件中保存对API的引用.您应该已经知道总行数.您可以使用以下命令获取可见/已过滤行数:要么您可以使用以下命令获取所选行的数量:

  10. angularjs – 迁移gulp进程以包含typescript

    或者我应该使用tsc作为我的主要构建工具,让它解决依赖关系,创建映射文件并制作捆绑包?

返回
顶部