个人网站首页布局设计百度一下首页手机版
大约有五六年前的某个时候,jQuery统治了网络的客户端。 它读起来像普通的英语,易于安装,并且学习曲线平坦,足以使幼儿在上面骑三轮车。 但是,这种便捷的访问方式带来了许多问题。 jQuery使“工作”的东西容易被黑在一起,但这是以最佳实践,可维护性和可伸缩性为代价的。
然后,框架之战开始了,很快每个人都大声疾呼尝试将其承诺的结构和可伸缩性引入其应用程序的最新,最出色的框架。 这些框架之一就是AngularJS。 现在,Angular的学习曲线比jQuery的陡峭得多,但我认为它已经达到了许多开发人员可以自信地设置基本应用程序的地步。 也就是说,使用框架并不能自动解决应用程序设计的核心问题。 仍然有可能在不可维护或不可扩展的AngularJS,EmberJS或React之类的框架中构建应用程序-实际上,初学者甚至中级框架用户都会犯此错误。
事情如何如此轻松地失控?
为了演示这种突然的复杂性在甚至最基本的AngularJS应用中如何发生,让我们开始构建一个并观察可能出问题的地方。 然后,稍后,我们将研究修复它的方法。
让我们创建一个简单的应用程序
我们将要创建的应用程序是为Dribbble播放器评分的应用程序。 我们将能够输入Dribbble用户的姓名并将其添加到记分板中。
剧透 –您可以在这里看到最终产品的有效实施。
首先创建一个具有以下内容的index.html
文件以开始使用:
<!DOCTYPE html>
<html><head><title>Angular Refactoring</title><link href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet"><script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.15/angular.min.js"></script></head><body><div><div class="panel panel-default"><div class="panel-heading">Dribbble Player Scores</div><div class="panel-body"><p>Add Dribbble players to see how they rank:</p><div class="form-inline"><input class="form-control" type="text" /><button class="btn btn-default">Add</button></div></div><ul class="list-group">...</ul></div></div></body>
</html>
创建我们的AngularJS应用
如果您之前已经编写过Angular应用程序,那么接下来的几步应该是您相当熟悉的。 首先,我们将创建一个app.js
文件,在其中实例化AngularJS应用程序:
var app = angular.module("dribbbleScorer", []);
现在,将其包含在我们的index.html
文件中。 我们还将在我们的<html>
标记中添加ng-app="dribbbleScorer"
属性,以引导Angular应用。
<html ng-app="dribbbleScorer"><head><title>Angular Refactoring</title><link href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet"><script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.15/angular.min.js"></script><script src="app.js"></script></head>...
现在我们的应用程序已安装并启动,我们可以开始处理应用程序的业务逻辑。
使它起作用
现在是时候真正实现我们的应用了。 请记住,我们正在以“让它正常工作”的方式进行处理,因为这通常是我们面临的现实。 就像可能急于在jQuery中添加点击处理程序的方式一样,Angular用户通常会找到通向工作应用程序的最快途径: ng-controller
。 让我们看看这可能如何工作。
在app.js
我们将定义一个控制器和一些虚拟播放器数据:
var app = angular.module("dribbbleScorer", []);app.controller("DribbbleController", function($scope) {$scope.players = ["Tom", "Dick", "Harry"];
});
在index.html
我们将使用ng-controller
插入ng-controller
,并将编辑ul
列表以遍历播放器,并在li
显示每个播放器:
<body><!-- Add our DribbbleController --><div ng-controller="DribbbleController">...<ul class="list-group"><!-- Loop over players using ng-repeat --><li class="list-group-item" ng-repeat="player in players">{{player}}</li></ul>...</div>
</body>
如果您同时保存两个文件并在浏览器中打开index.html
,则应该看到三个名称Tom,Dick和Harry的列表。 到目前为止很容易,很干净。
实施表格
接下来,让我们的表格开始工作。 我们需要一个变量用作输入字段的ng-model
,我们需要按钮的单击处理程序。 点击处理程序将需要将我们的输入添加到当前玩家列表中。
在index.html
添加模型并单击处理程序到我们的表单中:
<div ng-controller="DribbbleController">...<div class="form-inline"><input class="form-control" type="text" ng-model="newPlayer" /><button class="btn btn-default" ng-click="addPlayer(newPlayer)">Add</button></div>...
</div>
接下来,我们将在app.js
实现这两件事:
app.controller("DribbbleController", function($scope) {$scope.newPlayer = null; // Our model value is null by default$scope.players = ["Tom", "Dick", "Harry"];// Adds a player to the list of players$scope.addPlayer = function(player) {$scope.players.push(player); }
});
在浏览器中进行测试。 输入名称,单击“添加”按钮,它应出现在列表中。 使用AngularJS控制器使某些东西真正快速工作很容易。
从Dribbble中获取数据
现在,我们不只是使用虚拟的玩家名称,而是从Dribbble获取玩家信息。 我们将更新addPlayer()
函数,将播放器名称发送到Dribbble的API,然后将结果推送到列表中:
app.controller("DribbbleController", function($scope, $http) {$scope.newPlayer = null; // Our model value is null by default$scope.players = ["Tom", "Dick", "Harry"];// Fetches a Dribbble player and adds them to the list$scope.addPlayer = function(player) {$http.jsonp('http://api.dribbble.com/players/' + player + '?callback=JSON_CALLBACK').success(function(dribbble_player){$scope.players.push(dribbble_player.name);}).error(function(){// handle errors}); }
});
请记住首先将$http
服务注入到控制器中。 Dribbble API是基于JSONP的,因此我们需要使用$http.jsonp()
方法,并在URL中添加?callback=JSON_CALLBACK
,以便Angular为我们自动处理响应。 其余的非常简单。 在成功回调中,我们将玩家的名字推入列表。 继续并在浏览器中尝试一下。
卸下播放器
让我们在播放器行中添加一个删除按钮。 首先,对index.html
进行以下更改。
<ul class="list-group"><!-- Loop over players using ng-repeat --><li class="list-group-item" ng-repeat="player in players">{{player}}<a href="" ng-click="removePlayer(player)"><i class="glyphicon glyphicon-remove pull-right"></i></a></li>
</ul>
然后,在app.js
进行以下更改:
app.controller("DribbbleController", function($scope, $http) {...$scope.removePlayer = function(player) {$scope.players.splice($scope.players.indexOf(player), 1);};
});
现在,您应该能够从列表中添加和删除播放器。
使用player
对象
现在是时候开始重构我们的应用程序了。 我们将为我们的玩家创建一个任意的“评论分数”和“喜欢分数”。 但是首先,我们需要将播放器字符串转换为对象,以便它们可以具有属性,然后可以在DOM中显示它们。 让我们更新app.js
以使用从Dribbble返回的实际播放器对象:
app.controller("DribbbleController", function($scope, $http) {$scope.newPlayer = null; // Our model value is null by default$scope.players = []; // We'll start with an empty list// Fetches a Dribbble player and adds them to the list$scope.addPlayer = function(player) {$http.jsonp('http://api.dribbble.com/players/' + player + '?callback=JSON_CALLBACK').success(function(dribbble_player){$scope.players.push(dribbble_player); // Here we add the dribbble_player object to the list}).error(function(){// handle errors}); };
});
接下来,让我们更新DOM以使用播放器的属性:
<ul class="list-group"><!-- Loop over players using ng-repeat --><li class="list-group-item" ng-repeat="player in players"><!-- We use player.name here instead of just player -->{{player.name}}<a href="" ng-click="removePlayer(player)"><i class="glyphicon glyphicon-remove pull-right"></i></a></li>
</ul>
该应用程序此时仍应正常运行。
计算分数
让我们将分数信息添加到DOM,然后在我们的JavaScript文件中实现它:
<ul class="list-group"><li class="list-group-item" ng-repeat="player in players">{{player.name}} L: {{likeScore(player)}} C:{{commentScore(player)}}<a href="" ng-click="removePlayer(player)"><i class="glyphicon glyphicon-remove pull-right"></i></a></li>
</ul>
我们将通过从收到的评论数中减去给出评论的球员,以及同样地(打扰双关语),计算出给定喜欢和收到的喜欢计数的分数。 我们将如下实现:
app.controller("DribbbleController", function($scope, $http){...$scope.likeScore = function(player) {return player.likes_received_count - player.likes_count;};$scope.commentScore = function(player) {return player.comments_received_count - player.comments_count;};
});
重新加载页面,添加一些玩家,您应该会看到每个玩家的赞(L)分数和评论(C)分数。
看那个控制器!
现在,我们的应用程序可以正常工作了,但是只需看看我们创建的控制器的大小和复杂性即可! 在理想的世界中,控制器应该只关心以下方面:控制应用程序不同部分之间的通信。 在这里,我们的控制器绝对负责一切。
app.controller("DribbbleController", function($scope, $http) {$scope.newPlayer = null; // Our model value is null by default$scope.players = []; // We'll start with an empty list// Fetches a Dribbble player and adds them to the list$scope.addPlayer = function(player) {$http.jsonp('http://api.dribbble.com/players/' + player + '?callback=JSON_CALLBACK').success(function(dribbble_player) {$scope.players.push(dribbble_player); // Here we add the dribbble_player object to the list}).error(function() {// handle errors}); };$scope.removePlayer = function(player) {$scope.players.splice($scope.players.indexOf(player), 1);};$scope.likeScore = function(player) {return player.likes_received_count - player.likes_count;};$scope.commentScore = function(player) {return player.comments_received_count - player.comments_count;};
});
我们可以做得更好。
使用角度工厂抽象我们的担忧
添加和删除播放器是控制器中属于两个概念。 控制器公开这些功能并不是事实,而是它也负责实现这些功能。 如果控制器的addPlayer()
函数只是将该请求传递给应用程序的另一部分,它可以处理实际添加播放器的来龙去脉,那不是更好。 嗯,这就是AngularJS工厂出现的地方。
建立我们的工厂
如果我们以面向对象的方式思考,那么我们正在处理Dribbble Player对象。 因此,让我们创建一个可以制造Dribbble播放器的工厂。 为了简便起见,我们将在同一app.js
文件中实现此操作:
app.controller("DribbbleController", function($scope, $http) {...
});app.factory("DribbblePlayer", function() {// Define the DribbblePlayer functionvar DribbblePlayer = function(player) {};// Return a reference to the functionreturn (DribbblePlayer);
});
您会注意到,我们已经使用大写语法定义了DribbblePlayer
。 这是因为它是构造函数。 还要注意,构造函数需要一个player参数。 当我们将该工厂注入到控制器中时,我们将能够调用new DribbblePlayer(player)
并使它返回配置为该播放器的自身构造实例。
让我们向DribbblePlayer
构造函数添加一个初始化函数来设置一些默认属性:
// We need to inject the $http service in to our factory
app.factory("DribbblePlayer",function($http) {// Define the DribbblePlayer functionvar DribbblePlayer = function(player) {// Define the initialize functionthis.initialize = function() {// Fetch the player from Dribbblevar url = 'http://api.dribbble.com/players/' + player + '?callback=JSON_CALLBACK';var playerData = $http.jsonp(url);var self = this;// When our $http promise resolves// Use angular.extend to extend 'this'// with the properties of the responseplayerData.then(function(response) {angular.extend(self, response.data); });};// Call the initialize function for every new instancethis.initialize();};// Return a reference to the functionreturn (DribbblePlayer);
});
这里有几件事要注意:
我们将self
变量定义为this
上下文的引用,因为上下文是构造的DribbblePlayer
实例。 我们这样做是为了可以在promise的then()
回调中扩展实例。
我们还使用angular.extend()
将所有从API返回的Dribbble播放器属性添加到DribbblePlayer
实例中。 这等效于:
playerData.then(function(response) {self.name = response.data.name;self.likes_count = response.data.likes_count;// etc
});
我们在定义它之后立即调用this.initialize()
。 这是为了模拟正常的OOP行为,在该行为中,定义构造函数或initialize()
方法将在创建该类的新实例时使该方法执行。
使用工厂
是时候使用我们的工厂了。 我们需要将其注入到控制器中,然后可以使用它从控制器中抽象出一些职责:
...// Inject DribbblePlayer into your controller and remove the $http service
app.controller("DribbbleController", function($scope, DribbblePlayer) {$scope.newPlayer = null;$scope.players = [];$scope.addPlayer = function(player) {// We can push a new DribbblePlayer instance into the list$scope.players.push(new DribbblePlayer(player));$scope.newPlayer = null;};...
});
在浏览器中重新加载该应用程序,它应该像以前一样工作。 那不是很棒吗?
这到底是怎么回事?
回顾一下,我们已经将DribbblePlayer
工厂注入了我们的控制器中。 工厂允许我们创建DribbblePlayer
构造函数的新实例。 构造initialize()
的initialize()
方法使用播放器名称参数从Dribbble获取播放器详细信息,并将其设置为实例的属性。 最后,该实例就是我们进入列表的对象。
我们根本不需要更改DOM,因为它期望具有name
和like_count
对象,而这正是我们所提供的。
真的值得吗?
绝对! 我们不仅简化了控制器,而且将关注点分离了。 我们的控制器不再关心添加播放器的实现。 我们可以将new DribbblePlayer()
换成new BaseballSuperstar()
,而我们只需要更改一行代码即可。 此外,我们现在也可以使用更具可读性和可扩展性的OOP方法来抽象控制器的其他部分。
让我们将likeScore()
和commentScore()
移入我们的工厂,并将其设置为每个播放器实例上的方法,而不是使用带有播放器参数的函数:
...this.initialize = function(argument) {...};this.likeScore = function() {return this.likes_received_count - this.likes_count;};this.commentScore = function() {return this.comments_received_count - this.comments_count;};
}
现在,每次我们调用new DribbblePlayer(player)
,返回的对象将具有likeScore()
方法和commentScore()
方法。 它们需要保留为函数而不是属性,以使它们在Angular的每个$digest
循环中都将生成新值,以表示DribbblePlayer
模型中的任何潜在变化。
我们需要更新DOM以反映这些更改:
<ul class="list-group"><li class="list-group-item" ng-repeat="player in players"><!-- We can now use player.likeScore instead of likeScore(player) -->{{player.name}} L: {{player.likeScore()}} C:{{player.commentScore()}}<a href="" ng-click="removePlayer(player)"><i class="glyphicon glyphicon-remove pull-right"></i></a></li>
</ul>
包起来
我试图证明对于我们来说,编写“使之起作用”的代码是多么容易,并且使该代码很快失控。 我们最终得到了一个混乱的控制器,充满了功能和责任。 但是,经过一些重构,我们的控制器文件现在看起来像这样:
app.controller("DribbbleController", function($scope, DribbblePlayer) {$scope.newPlayer = null;$scope.players = [];$scope.addPlayer = function(player) {$scope.players.push(new DribbblePlayer(player));};$scope.removePlayer = function(player) {$scope.players.splice($scope.players.indexOf(player), 1);};
});
它具有更高的可读性,并且很少涉及到自身,这就是重构的全部内容。 希望我为您提供了开始考虑使用更好的方法来构建AngularJS应用程序所需的工具。 重构愉快!
本教程中的代码可在GitHub上获得 !
额外信用
我们当然改进了addPlayer()
函数,但是为什么addPlayer()
呢? 这是我们可以进行的其他一些改进:
- 将
$http
调用抽象到Angular资源中以分离持久性/资源。 然后,您可以将资源注入工厂以使用它。 - 创建一个
PlayerList
工厂来处理列表管理,包括添加,删除和排序。 这样,您可以抽象PlayerList.add()
和PlayerList.remove()
之后的push()
和splice()
方法,这样您就不必直接依赖于控制器内部的该实现。
From: https://www.sitepoint.com/tidy-angular-controllers-factories-services/