使用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數據,不完整版,非常抱歉.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM