65.9K
CodeProject 正在变化。 阅读更多。
Home

Angular 教程 - 第 5 部分: 理解和实现服务

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.98/5 (26投票s)

2015年7月10日

CPOL

8分钟阅读

viewsIcon

40482

downloadIcon

2529

在本文中,我们将讨论 Angular 中的服务。

引言

在本文中,我们将讨论 Angular 中的服务。我们将了解服务是如何以及为何有用的。我们将尝试使用一些内置服务并在 Angular 中创建我们自己的服务。

完整系列链接

  1. AngularJS 教程 - 第一部分:AngularJS 简介
  2. Angular 教程 - 第 2 部分: 理解模块和控制器
  3. AngularJS 教程 - 第三部分:理解和使用指令
  4. Angular 教程 - 第 4 部分: 理解和实现过滤器
  5. Angular 教程 - 第 5 部分: 理解和实现服务
  6. Angular 教程 - 第 6 部分: 构建和验证数据录入表单
  7. Angular 教程 - 第 7 部分: 理解单页应用程序和 Angular 路由

背景

在我们的 Web 应用程序中,我们总是会有些业务逻辑和服务器通信来从服务器获取/保存数据。那么,哪里是处理这类事情的最佳场所呢?我们可以将此代码写在控制器中并拥有一个可用的应用程序,但问题是我们是否应该将此代码写在控制器中。

如果我们将业务逻辑和服务器通信放在控制器中,会存在两个主要问题。第一个问题是该功能将不可重用,即,如果我们想在其他控制器中重用某些行为,我们将不得不重写此代码。第二个问题是从最佳实践的角度来看。如果我们把所有这些代码都放在控制器中,那么我们就违反了单一职责原则,因为我们的控制器应该只负责填充与视图相关的 $scope 属性。此外,如果我们把所有业务逻辑都放在控制器中,就不会有关注点分离,最终我们的控制器中会再次出现混乱的代码。

那么,我们如何以一种能够良好分离关注点的方式编写代码呢?也就是说,每个业务逻辑组件都彼此独立,并且与控制器分离。此外,我们如何从应用程序的其他部分使用和重用业务逻辑组件呢?这个问题的答案就是 Angular 服务。

Angular 允许我们创建可重用的无状态组件,这些组件可以从应用程序的其他部分使用。这些在 Angular 中被称为服务。服务是一个单例对象,可以定义为包含任何业务逻辑。然后,它们可以从应用程序的其他部分使用,如控制器、指令、过滤器以及可能其他的服务。

因此,为了更密切地观察服务概念并更好地理解主题,让我们在我们的应用程序中引入一个服务。到目前为止,我们已经创建了一个应用程序,该应用程序在单击按钮时显示书籍列表。

此应用程序的控制器如下所示:

(function () {

    var booksController = function ($scope, $filter) {
        $scope.message = "List of books";

        $scope.books = [];

        $scope.fetchBooks = function () {
            $scope.books = [
                { ID: 1, BookName: "4 Test Books", AuthorName: "5 Test Author", ISBN: "5 TEST" },
                { ID: 2, BookName: "5 Test Books", AuthorName: "4 Test Author", ISBN: "1 TEST" },
                { ID: 3, BookName: "1 Test Books", AuthorName: "3 Test Author", ISBN: "2 TEST" },
                { ID: 4, BookName: "2 Test Books", AuthorName: "2 Test Author", ISBN: "4 TEST" },
                { ID: 5, BookName: "3 Test Books", AuthorName: "1 Test Author", ISBN: "3 TEST" }
            ];
        }
    }

    angular.module('myAngularApplication').controller('booksController', 
                                            ["$scope", "$filter", booksController]);

}());

我们在上面的控制器中所做的是用一些硬编码的值填充书籍列表。但理想情况下,它们应该来自服务器。假设我们想在这个应用程序中引入 Angular 服务。实际上,我们将为这个应用程序创建两个服务。一个用于获取此硬编码的示例书籍列表。第二个服务将使用 RESTful API 从服务器获取相似的书籍列表。那么,让我们开始吧。

Using the Code

在开始为我们的应用程序创建服务之前,让我们先了解一些基本知识。

如何创建服务

首先要记住的是,在 Angular 中有四种创建服务的方式。使用这些方式中的任何一种都将为我们提供一个可以在应用程序中使用的无状态单例对象。使用哪种方法很大程度上取决于我们想要遵循的模式、该服务到底是为了什么以及我们想如何使用该服务。我们不会讨论或比较这些方法,因为这会偏离本文的主题(也许以后的文章可以涵盖这一点)。在本文中,我们将使用 factory 方法来创建我们的服务,这是 Angular 实现工厂模式以创建服务对象的机制。

我们需要做的是使用 Angular 模块上的 factory 方法,并将服务函数与之关联,以创建我们的服务类。让我们创建 localBooksService 的骨架,它将向我们的控制器返回一个硬编码的书籍列表。

(function () {
    var localBooksService = function () {       
    }
    angular.module('myAngularApplication').factory('localBooksService', [localBooksService]);
}());

现在,在我们开始编写此服务的内部实现之前,也许我们应该看一下用于实现的 JavaScript 模式。它称为揭示模块模式

理解揭示模块模式

从最佳实践的角度(也是面向对象编程的基础),我们应该始终将内部实现封装到一个类中,并让用户只看到公共抽象。但是,因为我们讨论的是 JavaScript,所以我们无法在服务实现中放置访问修饰符之类的东西。那么如何实现呢?这可以通过使用一种称为揭示模块模式的 JavaScript 模式来实现。

在此模式中,我们所做的是让函数返回一个对象。因此,该函数的调用者将获得此对象的实例。因此,让我们假设我们将返回一个包含书籍列表的对象。

var localBooksService = function () {
    
    return {
        books: []
    };
}

现在,我们返回的只是一个空数组。我们如何才能拥有实际的书籍列表呢?由于在此函数内部,我们可以访问内部变量和函数,为什么不将此书籍列表作为函数级别的变量,然后将其返回给用户呢?

var localBooksService = function () {
    
    var _books = [];

    return {
        books: _books
    };
}

同样,我们也可以定义一个内部函数,该函数可以使用此 return 对象在外部公开。

var localBooksService = function () {
    
    var _books = [];

    var _someFunction = function(){
        console.log("This is just a sample function");
    };

    return {
        books: _books,
        someFunction: _someFunction
    };
}

实现我们的服务

现在,通过这种揭示模块模式,我们已经实现了一定程度的封装,因为该函数只会公开返回的对象,并且可以隐藏实际实现。现在,让我们将这些硬编码的书籍列表移到我们的 localBooksService 中,并查看我们服务的实现。

(function () {

    var localBooksService = function () {
        var _books = [
                { ID: 1, BookName: "4 Test Books", AuthorName: "5 Test Author", ISBN: "5 TEST" },
                { ID: 2, BookName: "5 Test Books", AuthorName: "4 Test Author", ISBN: "1 TEST" },
                { ID: 3, BookName: "1 Test Books", AuthorName: "3 Test Author", ISBN: "2 TEST" },
                { ID: 4, BookName: "2 Test Books", AuthorName: "2 Test Author", ISBN: "4 TEST" },
                { ID: 5, BookName: "3 Test Books", AuthorName: "1 Test Author", ISBN: "3 TEST" }
        ];

        return {
            books: _books            
        };
    }

    angular.module('myAngularApplication').factory('localBooksService', [localBooksService]);

}());

使用服务

现在服务已经准备好了,让我们看看如何从控制器中使用它。我们需要做的第一件事是以一种防混淆的方式(我们在上一篇文章中已经见过)在我们的控制器中传递此服务的依赖项。然后,我们需要使用该服务来获取书籍列表,而不是使用硬编码的书籍。让我们看看这些更改后我们的控制器是什么样的。

(function () {

    var booksController = function ($scope, $filter, localBooksService) {
        $scope.message = "List of books";

        $scope.books = [];

        $scope.fetchBooks = function () {
            $scope.books = localBooksService.books;
        }
    }

    angular.module('myAngularApplication').controller('booksController', 
                   ["$scope", "$filter", "localBooksService",  booksController]);

}());

使用 Angular 内置服务

Angular 还附带了许多我们可以使用的内置服务。其中一些是 $location, $window, $resource$http。我们可以查看 Angular 文档,了解特定服务提供了什么以及如何在我们的应用程序中使用它们。在此示例中,让我们使用 $http 服务。我们将做的是实现另一个名为 remoteBooksService 的服务,并在此服务内部使用 $http 服务来从服务器获取书籍。

注意:我所做的是创建了一个 ASP.NET Web API 项目,该项目可用于对服务器上的 book 数据库执行 CRUD 操作。请查找附加的 API 项目以查看 Web API 详细信息。或者,我们可以使用 Node 或任何其他技术创建简单的 RESTful 服务,并在我们的 Angular 应用程序中,我们只需引用该服务托管的 URL。

让我们看看如何在我们自己的服务中使用 $http 服务。我们需要做的第一件事是将此服务注入到我们的服务中。一旦注入,我们就可以使用 $httpget 方法从服务器检索结果。我的服务器 API 正在运行:https://:57386/api/Books

(function () {

    var remoteBooksService = function ($http) {
        var _fetchBooks = function () {
            return $http.get('https://:57386/api/Books');
        };

        return {
            fetchBooks: _fetchBooks
        };
    }

    angular.module('myAngularApplication').factory('remoteBooksService', ["$http", remoteBooksService]);

}());

现在,让我们将此服务注入到我们的控制器中,并在控制器中添加另一个函数,该函数将使用此服务获取远程书籍列表。 $http.get 返回一个 Promise,由于我们直接从服务返回该 Promise,因此我们的控制器需要处理该 Promise 并填充书籍列表。

(function () {

    var booksController = function ($scope, $filter, localBooksService, remoteBooksService) {
        $scope.message = "List of books";

        $scope.books = [];

        $scope.fetchBooks = function () {
            $scope.books = localBooksService.books;
        }

        $scope.fetchBooksFromServer = function () {

            remoteBooksService.fetchBooks()
            .success(function (data, status, headers, config) {
                $scope.books = data;
            })
            .error(function (data, status, headers, config) {
                $scope.books = [];
                $scope.error = "Failed to retrieved items from server";
            });
        };
    }

    angular.module('myAngularApplication').controller('booksController', 
         ["$scope", "$filter", "localBooksService", "remoteBooksService",  booksController]);

}());

现在,如果我们运行应用程序并单击“从远程获取”,我们将从服务器获取书籍列表。

注意:上面显示的用于使用 $http 服务的方法相当粗糙,因为我们的主要重点是查看如何使用服务以及创建使用其他服务自己的服务。使用 $http 的理想解决方案将涉及使用 $Q defer API,我强烈建议您阅读有关它的内容。我们在此处不会讨论它,因为它超出了本文的范围。

看点

在本文中,我们探讨了 Angular 中服务背后的概念。我们通过查看 $http 服务的示例用法,了解了如何实现我们自己的服务以及如何重用 Angular 内置服务。本文是从初学者的角度编写的。我希望这能有所帮助。

历史

  • 2015年7月10日:第一个版本
© . All rights reserved.