好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

使用TypeScript,AngularJs和Web API构建基本的CRUD Web 应用

原文地址: using typescript with angularjs and web api  版权归其作者所有.

在这篇文章中我将向大家展示如何使用TypeScript,Angular Js 和Asp.net Web API 来构建一个基本的实现CRUD功能的Web应用程序. Typescript提供了一系列的功能来方便程序员构造和组织更具有可维护性的Js应用程序.同时也可以集成现有的第三方库文件,你可以在接下来的Demo中看到这项特性.这个基本的HTML应用将借助Asp.net Web API实现增,删,查,改功能.

主要特点如下:

使用TypeScript构建AngularJs的Controller, 使用TypeScript并利用AngularJs 的Ajax功能与Web API进行通信, 使用TypeScript的强类型来定义AngularJs对象.

在这个例子中,我使用VS 2012和Sublime Text来编写代码.你也可以选择自己喜欢的文本编辑器,如果你需要语法高亮或者代码自动补全功能,你需要单独下载程序包.

对于VS插件和windows下的可执行文件(tsc.exe,安装包的一部分),请访问:  http://www.typescriptlang.org/#Download . 如果你想选择一个不同的编辑器,(例如:Sublime Text或者Vim),请从这里寻找帮助 :  http://aka.ms/qwe1qu . TypeScript编译程序是必不可少的工具.

服务端通过Asp.net Web API实现(.Net平台下构建Http服务的实现,我个人很喜欢),当然你也可以选择自己喜欢的技术.

使用Asp.net Web API 提供Http服务.

对于这个例子,我处理的模型由一个单一实体构成----Product.

 //Model
public abstract class Entity { public Guid Id { get ; set ; } } public class Product : Entity { public string Name { get ; set ; } public decimal Price { get ; set ; } }

我们需要一个持久化机制来存储这些实体.我选择使用仓储模式把这些内容存在内存里.请根据需要,随意替换成适合你的东西(例如:Entity Framework或者Nhibernate.)

 // IRepository
public interface IRepository<TEntity> where TEntity : Entity { TEntity Add(TEntity entity); TEntity Delete(Guid id); TEntity Get(Guid id); TEntity Update(TEntity entity); IQueryable <TEntity> Items { get ; } }

 //  InMemoryRepository 
 public   class  InMemoryRepository<TEntity> : IRepository<TEntity>  where   TEntity : Entity
{
      private   readonly  ConcurrentDictionary<Guid, TEntity>  _concurrentDictionary 
         =  new  ConcurrentDictionary<Guid, TEntity> ();

      public   TEntity Add(TEntity entity)
    {
          if  (entity ==  null  )
        {
              //  we dont want to store nulls in our collection 
             throw   new  ArgumentNullException( "  entity  "  );
        }

          if  (entity.Id ==  Guid.Empty)
        {
              //  we assume no Guid collisions will occur 
            entity.Id =  Guid.NewGuid();
        }

          if   (_concurrentDictionary.ContainsKey(entity.Id))
        {
              return   null  ;
        }

          bool  result =  _concurrentDictionary.TryAdd(entity.Id, entity);

          if  (result ==  false  )
        {
              return   null  ;
        }
          return   entity;
    }

      public   TEntity Delete(Guid id)
    {
        TEntity removed;
          if  (! _concurrentDictionary.ContainsKey(id))
        {
              return   null  ;
        }
          bool  result = _concurrentDictionary.TryRemove(id,  out   removed);
          if  (! result)
        {
              return   null  ;
        }
          return   removed;
    }

      public   TEntity Get(Guid id)
    {
          if  (! _concurrentDictionary.ContainsKey(id))
        {
              return   null  ;
        }
        TEntity entity;
          bool  result = _concurrentDictionary.TryGetValue(id,  out   entity);
          if  (! result)
        {
              return   null  ;
        }
          return   entity;
    }

      public   TEntity Update(TEntity entity)
    {
          if  (entity ==  null  )
        {
              throw   new  ArgumentNullException( "  entity  "  );
        }
          if  (! _concurrentDictionary.ContainsKey(entity.Id))
        {
              return   null  ;
        }
        _concurrentDictionary[entity.Id]  =  entity;
          return   entity;
    }

      public  IQueryable<TEntity>  Items
    {
          get  {  return   _concurrentDictionary.Values.AsQueryable(); }
    }
} 

一旦我们在这里完成了对象持久化,我们就可以创造一个Http服务,来提供一些基本的操作.我使用的是Asp.net Web API,所以我还要再创建一个 Controller.

 //  Product HTTP Service 
 public   class   ProductsController : ApiController
{
      public   static  IRepository<Product>  ProductRepository
         =  new  InMemoryRepository<Product> ();

      public  IEnumerable<Product>  Get()
    {
          return   ProductRepository.Items.ToArray();
    }

      public   Product Get(Guid id)
    {
        Product entity  =  ProductRepository.Get(id);
          if  (entity ==  null  )
        {
              throw   new   HttpResponseException(HttpStatusCode.NotFound);
        }
          return   entity;
    }

      public   HttpResponseMessage Post(Product value)
    {
          var  result =  ProductRepository.Add(value);
          if  (result ==  null  )
        {
              //   the entity with this key already exists 
             throw   new   HttpResponseException(HttpStatusCode.Conflict);
        }
          var  response = Request.CreateResponse<Product> (HttpStatusCode.Created, value);
          string  uri = Url.Link( "  DefaultApi  " ,  new  { id =  value.Id });
        response.Headers.Location  =  new   Uri(uri);
          return   response;
    }

      public   HttpResponseMessage Put(Guid id, Product value)
    {
        value.Id  =  id;
          var  result =  ProductRepository.Update(value);
          if  (result ==  null  )
        {
              //   entity does not exist 
             throw   new   HttpResponseException(HttpStatusCode.NotFound);
        }
          return   Request.CreateResponse(HttpStatusCode.NoContent);
    }

      public   HttpResponseMessage Delete(Guid id)
    {
          var  result =  ProductRepository.Delete(id);
          if  (result ==  null  )
        {
              throw   new   HttpResponseException(HttpStatusCode.NotFound);
        }
          return   Request.CreateResponse(HttpStatusCode.NoContent);
    }
} 

我们努力去实现标准的Http,因此附加逻辑来处理 Response.在这个步骤结束后,我们将会有一个功能完整的,(实现了CRUD)Http服务.

 

开始使用Angular Js 和 TypeScript

服务端创建好以后,我们接着开始创建真正的网站部分.它将完全由Html/CSS/Js(TypeScript将会编译成Js)构成.我选择的初始模版像这样:

 <!  DOCTYPE html  > 
 <  html   ng-app  > 
 <  head  > 
     <  title  > Product list </  title  > 
     <  link   rel  ="stylesheet"   href  ="Content/bootstrap.css"  /> 
     <  script   type  ="text/javascript"   src  ="Scripts/bootstrap.js"  ></  script  > 
     <  script   type  ="text/javascript"   src  ="Scripts/angular.js"  ></  script  > 
     <  script   type  ="text/javascript"   src  ="Scripts/Controllers/ProductsController.js"  ></  script  > 
 </  head  > 
     <  body  > 
         <  div   ng-controller  ="Products.Controller"  > 
         </  div  > 
     </  body  > 
 </  html  > 

 

请注意: ProductsController.js文件是从名为 ProductsController.ts的TypeScript源代码通过tsc.exe编译得到的.(我使用了命令行来执行这个编译步骤.).让我们为这个页面创建一个Angular Js的Controller.

 

 //  ProductsController.ts 
 module Products {
    export interface Scope {
        greetingText: string;
    }

    export class Controller {
        constructor ($scope: Scope) {
            $scope.greetingText  = "Hello from TypeScript + AngularJS" ;
        }
    }
} 

 

Produscts Module将被编译成一个 Js的命名空间,我们的Controller将被转译为 Products.Controller供程序使用.现在我们可以在页面中绑定一个欢迎致辞.请注意:结合TypeScript的特点,我们以Scope接口的形式定义了$scope对象.Typescript也允许我们使用 any 这种类型来定义,但就个人而言,我更倾向于使用更加严格的定义方式,因为他可以帮助你在编译时捕获错误信息.(VS2012甚至会在你编写的错误代码下面加入红色下划线,这真是太棒了.)现在,我们需要编译ProductsController.ts,在HTML中引用 ProductsController.js,并且修改视图(view)来显示信息.

 <  div   ng-controller  ="Products.Controller"  > 
     <  p  > {{greetingText}} </  p  > 
 </  div  > 

现在AngularJs Controller已经完成,让我们继续添加更多的功能.

创建Model模块

我们将创建一个Model模块,它会包含一个Product类,作为DTO对象(序列化为JSON)与Http的服务端进行交互.

 

 module Model {
    export class Product {
        Id: string;
        Name: string;
        Price: number;
    }
} 

 

这个简单的模块包含了一个类型的定义,将会在页面的contrller中使用.

环境声明(   ambient declarations. )

为了使用AngularJs提供的Ajax功能,我们将$Http服务传入到Controller的构造器中:

 

 class Controller {
    private httpService: any;

    constructor ($scope: Scope, $http: any) {
          this .httpService =  $http;
          //  ... 
     }
      //  ... 
}

 

由于我们将$Http定义成了any的类型,所以编译器不会帮助我们在编译时发现潜在的错误.为了解决这个问题,我们使用环境声明 (译者注:元数据??) .环境声明用来告诉编译器将要被引入的元素有特殊的意义(在这里是指Angular Js)并且不会被扩展.换而言之就是第三方类库的接口.声明源文件(.d.ts扩展名)被限制为只能包含环境声明.下面的angular.d.ts文件定义了两个接口,被我们用来调用Http服务.

 

 declare module Angular {
    export interface HttpPromise {
        success(callback: Function) : HttpPromise;
        error(callback: Function) : HttpPromise;
    }
    export interface Http {
        get(url: string): HttpPromise;
        post(url: string, data: any): HttpPromise;
          delete  (url: string): HttpPromise;
    }
} 

 

declare关键字是可选的,他会被隐式声明在所有的 .d.ts 文件中.

TypeScript 和 Angular Js

为了让编译器知道新增的两个模块(Model和Angular),我们需要添加两个引用信息到ProductsController.ts中.同时我们想要定义 Scope接口来包含视图使用的所有属性和方法.

页面将包含一个新增Product的表单(name,price文本框和一个按钮)同时展示一个包含所有Product的列表,其中的每个记录都可以被单独的删除.为了使演示变的简单,我忽略了更新记录的功能,但是只要我们实现了相应的操作,更新实现起来也是很简单的.

Scope接口看起来如下:

 //  / <reference path='angular.d.ts' />  
//  / <reference path='model.ts' /> 
 
module Products {

    export interface Scope {
        newProductName: string;
        newProductPrice: number;
        products: Model.Product[];
        addNewProduct: Function;
        deleteProduct: Function;
    }
      //   ... 
}

只要有product被新增或者被删除,我们就从服务端重新获取所有的product并刷新列表.由于先前我们所做的工作,现在我们可以使用强类型的 Angular.Http和Angular.HttpPromise接口.

controller将包含私有方法与我们的服务端进行通信(getAllProducts, addProduct and deleteProduct).

 export class Controller {
    private httpService: any;

    constructor ($scope: Scope, $http: any) {
          this .httpService =  $http;

          this  .refreshProducts($scope);

          var  controller =  this  ;

        $scope.addNewProduct  =  function   () {
              var  newProduct =  new   Model.Product();
            newProduct.Name  =  $scope.newProductName;
            newProduct.Price  =  $scope.newProductPrice;

            controller.addProduct(newProduct,   function   () {
                controller.getAllProducts(  function   (data) {
                    $scope.products  =  data;
                });
            });
        };

        $scope.deleteProduct  =  function   (productId) {
            controller.deleteProduct(productId,   function   () {
                controller.getAllProducts(  function   (data) {
                    $scope.products  =  data;
                });
            });
        }
    }

    getAllProducts(successCallback: Function):   void  {
          this .httpService.get('/api/products').success( function   (data, status) {
            successCallback(data);
        });
    }

    addProduct(product: Model.Product, successCallback: Function):   void   {
          this .httpService.post('/api/products', product).success( function   () {
            successCallback();
        });
    }

    deleteProduct(productId: string, successCallback: Function):   void   {
          this .httpService. delete ('/api/products/'+productId).success( function   () {
            successCallback();
        });
    }

    refreshProducts(scope: Scope) {
          this .getAllProducts( function   (data) {
                    scope.products  =  data;
                });
    }

} 

非常棒的一点就是,我们不需要人为的引入任何定制的序列化逻辑.当通过$http服务取回数据后,我们可以把他们当成强类型的Product集合进行操作.add操作也是一样----我们仅仅传入一个Product,他将被自动序列化并最终在服务端被解析.

创建视图(View)

最后一步,我们需要创建一个集合新controller特性的视图.我将使用bootstrap来使他变简单一些.

 

 <!  DOCTYPE html  > 
 <  html   ng-app  > 
 <  head  > 
     <  title  > Product list </  title  > 
     <  link   rel  ="stylesheet"   href  ="Content/bootstrap.css"   /> 
     <  script   type  ="text/javascript"   src  ="Scripts/angular.js"  ></  script  > 
     <  script   type  ="text/javascript"   src  ="Scripts/Controllers/model.js"  ></  script  > 
     <  script   type  ="text/javascript"   src  ="Scripts/Controllers/productsController.js"  ></  script  > 
 </  head  > 
 <  body  > 
     <  div   ng-controller  ="Products.Controller"  > 
         <  form   class  ="form-horizontal"   ng-submit  ="addNewProduct()"  > 
             <  input   type  ="text"   ng-model  ="newProductName"   size  ="30"  
                placeholder  ="product name"  > 
             <  input   type  ="text"   ng-model  ="newProductPrice"   size  ="5"  
                placeholder  ="product price"  > 
             <  button   class  ="btn"   type  ="submit"   value  ="add"  > 
                 <  i   class  ="icon-plus"  ></  i  > 
             </  button  > 
         </  form  > 
         <  table   class  ="table table-striped table-hover"   style  ="width: 500px;"  > 
             <  thead  > 
                 <  tr  > 
                     <  th  > Name </  th  > 
                     <  th  > Price </  th  > 
                     <  th  ></  th  > 
                 </  tr  > 
             </  thead  > 
             <  tbody  > 
                 <  tr   ng-repeat  ="product in products"  > 
                     <  td  > {{product.Name}} </  td  > 
                     <  td  > ${{product.Price}} </  td  > 
                     <  td  > 
                         <  button   class  ="btn-small"   ng-click  ="deleteProduct(product.Id)"  > 
                             <  i   class  ="icon-trash"  ></  i  > 
                         </  button  > 
                     </  td  > 
                 </  tr  > 
             </  tbody  > 
         </  table  > 
     </  div  > 
 </  body  > 
 </  html  > 

 

最终的结果就像这样:

 

现在我们的页面应该实现了应有的功能,并且可以与Http 服务端按照预期的情况进行通信工作了.

 

最后:Wordpress和Android应用可能会因为一些原因影响post数据,不完整版,非常抱歉.

 

查看更多关于使用TypeScript,AngularJs和Web API构建基本的CRUD Web 应用的详细内容...

  阅读:51次