1.不要设计您的页面,然后使用DOM操作对其进行更改
在jQuery中,您可以设计一个页面,然后将其动态化。这是因为jQuery是为增强而设计的,并且在此简单前提下得到了难以置信的增长。
但是在AngularJS中,您必须从头开始,牢记架构。您不必从“我拥有DOM的这个部分,而我想使其成为X”开始,而是必须从要完成的事情开始,然后开始设计应用程序,然后最后开始设计视图。
2.不要用AngularJS扩充jQuery
同样,不要以jQuery做X,Y和Z的想法开始,所以我只在模型和控制器的基础上添加AngularJS。当您刚入门时,这确实很诱人,这就是为什么我总是建议新的AngularJS开发人员根本不使用jQuery,至少直到他们习惯于“ Angular Way”为止。
我在这里和邮件列表上已经看到许多开发人员使用150或200行代码的jQuery插件创建这些精心设计的解决方案,然后将其粘贴到AngularJS中,并使用一系列令人困惑和费解的回调和$apply
;但是他们最终使它起作用了!问题在于,在大多数情况下,jQuery插件可以用少量代码在AngularJS中重写,从而突然之间所有内容都变得可理解和直接。
底线是:解决时,首先“在AngularJS中思考”;如果您想不出解决方案,请询问社区;如果毕竟没有简单的解决方案,请随时使用jQuery。但是不要让jQuery成为拐杖,否则您将永远无法掌握AngularJS。
3.总是从架构上思考
首先知道single-page applications是应用程序。它们不是网页。因此,除了像客户端开发人员那样思考之外,我们还需要像服务器端开发人员那样思考。我们必须考虑如何将应用程序划分为各个可扩展的可测试组件。
那你该怎么做呢?您如何“在AngularJS中思考”?这里有一些一般性的原则,与jQuery相反。
该观点是“官方记录”
在jQuery中,我们以编程方式更改视图。我们可以将一个下拉菜单定义为ul
,如下所示:
<ul class="main-menu">
<li class="active">
<a href="#/home">Home</a>
</li>
<li>
<a href="#/menu1">Menu 1</a>
<ul>
<li><a href="#/sm1">Submenu 1</a></li>
<li><a href="#/sm2">Submenu 2</a></li>
<li><a href="#/sm3">Submenu 3</a></li>
</ul>
</li>
<li>
<a href="#/home">Menu 2</a>
</li>
</ul>
在jQuery中,在我们的应用程序逻辑中,我们将使用以下方式激活它:
$('.main-menu').dropdownMenu();
当我们仅查看视图时,并没有立即发现这里有任何功能。对于小型应用程序,这很好。但是对于非平凡的应用程序,事情很快就会变得混乱并且难以维护。
但是,在AngularJS中,视图是基于视图的功能的正式记录。我们的
ul
声明看起来像这样:
<ul class="main-menu" dropdown-menu>
...
</ul>
两者的作用相同,但是在AngularJS版本中,任何查看模板的人都知道应该发生什么。每当开发团队的新成员加入时,她都可以查看一下,然后知道有一个名为
dropdownMenu
的指令正在运行;她不需要输入正确的答案或筛选任何代码。该视图告诉我们应该发生什么。干净得多。
刚接触AngularJS的开发人员经常会提出类似的问题:如何找到特定种类的所有链接并向其添加指令。当我们回复时,开发人员总是为之震惊:您没有。但是,您不这样做的原因是,这就像是一半的jQuery,一半的AngularJS,而且不好。这里的问题是开发人员试图在AngularJS上下文中“执行jQuery”。那永远行不通。该视图是官方记录。在指令之外(请参见下文),您永远不会更改DOM。并且在视图中应用了指令,因此意图很明确。
记住:不要设计,然后标记。您必须先架构师,然后进行设计。
数据绑定
到目前为止,这是AngularJS最令人敬畏的功能之一,并且消除了我在上一节中提到的进行DOM操作的大量需求。 AngularJS将自动更新您的视图,因此您不必这样做!在jQuery中,我们响应事件,然后更新内容。就像是:
$.ajax({
url: '/myEndpoint.json',
success: function ( data, status ) {
$('ul#log').append('<li>Data Received!</li>');
}
});
对于如下所示的视图:
<ul class="messages" id="log">
</ul>
除了混合考虑之外,我们还遇到了我之前提到的表示意图的问题。但更重要的是,我们必须手动引用和更新DOM节点。而且,如果我们要删除日志条目,则也必须针对DOM进行编码。除了DOM,我们如何测试逻辑?如果我们要更改演示文稿怎么办?
这有点凌乱和脆弱。但是在AngularJS中,我们可以这样做:
$http( '/myEndpoint.json' ).then( function ( response ) {
$scope.log.push( { msg: 'Data Received!' } );
});
我们的视图如下所示:
<ul class="messages">
<li ng-repeat="entry in log">{{ entry.msg }}</li>
</ul>
但就此而言,我们的观点可能是这样的:
<div class="messages">
<div class="alert" ng-repeat="entry in log">
{{ entry.msg }}
</div>
</div>
现在,我们使用了Bootstrap警报框,而不是使用无序列表。而且,我们无需更改控制器代码!但更重要的是,无论在何处或如何更新日志,视图也会改变。自动地。整齐!
尽管这里没有显示,但数据绑定是双向的。因此,只需执行以下操作即可在视图中编辑这些日志消息:
<input ng-model="entry.msg" />
。有很多的欣喜。
不同的模型层
在jQuery中,DOM有点像模型。但是在AngularJS中,我们有一个单独的模型层,我们可以用它想要的任何方式进行管理,完全独立于视图。这有助于上述数据绑定,保持
separation of concerns,并引入了更大的可测试性。其他答案都提到了这一点,所以我就把它留在那儿。
关注点分离
以上所有内容都与这个总体主题相关:将您的关注点分开。您的观点充当了将要发生的事情的正式记录(大部分情况下);您的模型代表您的数据;您有一个服务层来执行可重用的任务;您进行DOM操作并使用指令扩展视图;然后将它们与控制器粘合在一起。其他答案中也提到了这一点,我唯一要添加的内容就是可测试性,我将在下面的另一部分中进行讨论。
依赖注入
帮助我们分离关注点的是
dependency injection(DI)。如果您来自服务器端语言(从
Java到
PHP),那么您可能已经很熟悉此概念,但是如果您是来自jQuery的客户端,那么这个概念似乎很愚蠢。多余的时髦。但事实并非如此。 :-)
从广泛的角度来看,DI意味着您可以非常自由地声明组件,然后再从任何其他组件声明它们,只需索要它的一个实例即可。您不必了解加载顺序,文件位置或类似内容。功能可能不会立即可见,但我仅提供一个(常见)示例:测试。
假设在我们的应用程序中,我们需要一个服务,该服务通过
REST API以及本地存储(取决于应用程序状态)来实现服务器端存储。在我们的控制器上运行测试时,我们不想与服务器通信-毕竟我们正在测试控制器。我们可以添加一个与原始组件同名的模拟服务,并且注入器将确保我们的控制器自动获得伪造的服务-我们的控制器不需要也不需要知道两者之间的区别。
说到测试...
4.测试驱动的开发-始终
这确实是第3部分有关体系结构的一部分,但它非常重要,因此我将其作为自己的顶层部分。
在您已经看到,使用或编写的所有jQuery插件中,有多少具有相应的测试套件?并不是很多,因为jQuery不太适合这种情况。但是AngularJS是。
在jQuery中,测试的唯一方法通常是使用示例/演示页面独立创建组件,我们的测试可以针对该示例/演示页面执行DOM操作。因此,我们必须分别开发一个组件,然后将其集成到我们的应用程序中。多么不方便!很多时候,当使用jQuery开发时,我们选择迭代而不是测试驱动的开发。谁能责怪我们?
但是因为我们有关注点分离,所以我们可以在AngularJS中迭代进行测试驱动的开发!例如,假设我们要一个超级简单的指令在菜单中指示当前路线是什么。我们可以在应用程序视图中声明我们想要的:
<a href="/hello" when-active>Hello</a>
好的,现在我们可以为不存在的
when-active
指令编写测试:
it( 'should add "active" when the route changes', inject(function() {
var elm = $compile( '<a href="/hello" when-active>Hello</a>' )( $scope );
$location.path('/not-matching');
expect( elm.hasClass('active') ).toBeFalsey();
$location.path( '/hello' );
expect( elm.hasClass('active') ).toBeTruthy();
}));
并且当我们运行测试时,我们可以确认它失败。只有现在,我们才应该创建指令:
.directive( 'whenActive', function ( $location ) {
return {
scope: true,
link: function ( scope, element, attrs ) {
scope.$on( '$routeChangeSuccess', function () {
if ( $location.path() == element.attr( 'href' ) ) {
element.addClass( 'active' );
}
else {
element.removeClass( 'active' );
}
});
}
};
});
现在,我们的测试通过了,并且菜单按要求执行。我们的开发既是迭代的又是测试驱动的。邪恶的酷。
5.从概念上讲,指令不是打包的jQuery
您会经常听到“仅在指令中进行DOM操作”。这是必须的。谨慎对待它!
但是让我们深入一点...
有些指令只是修饰视图中已经存在的内容(请考虑
ngClass
),因此有时会立即进行DOM操作,然后基本完成。但是,如果一条指令就像一个“小部件”并具有一个模板,则它也应该尊重关注点的分离。也就是说,模板在链接和控制器功能中也应在很大程度上与其实现无关。
AngularJS附带了一整套工具,使这一过程变得非常容易。使用
ngClass
我们可以动态更新类;
ngModel
允许双向数据绑定;
ngShow
和
ngHide
以编程方式显示或隐藏元素;还有更多-包括我们自己编写的内容。换句话说,我们可以在没有DOM操作的情况下进行各种出色的工作。 DOM操作越少,指令的测试就越容易,指令的样式就越容易,将来就越容易更改,它们的可重用性和可分发性就越高。
我看到很多使用指令作为投掷jQuery的地方的AngularJS新手。换句话说,他们认为“由于我无法在控制器中进行DOM操作,因此我会将代码放入指令中”。虽然这肯定好得多,但通常仍然是错误的。
想想我们在第3节中编写的记录器。即使将其放入指令中,我们仍然希望以“ Angular Way”方式进行操作。它仍然不需要任何DOM操作!很多时候需要进行DOM操作,但这比您想像的要稀少得多!在对应用程序中的任何位置进行DOM操作之前,请问自己是否确实需要这样做。可能有更好的方法。
这是一个简单的示例,显示了我最常看到的模式。我们想要一个可切换的按钮。 (请注意:此示例有些虚构,有些冗长,表示以完全相同的方式解决的更复杂的案例。)
.directive( 'myDirective', function () {
return {
template: '<a class="btn">Toggle me!</a>',
link: function ( scope, element, attrs ) {
var on = false;
$(element).click( function () {
on = !on;
$(element).toggleClass('active', on);
});
}
};
});
这有一些问题:
首先,jQuery从来没有必要。我们在这里所做的一切根本不需要jQuery!
其次,即使我们的页面上已经有了jQuery,也没有理由在这里使用它。我们可以简单地使用
angular.element
,当我们的组件放入没有jQuery的项目时,它仍然可以工作。
第三,即使假设该指令需要使用jQuery,jqLite(
angular.element
)如果已加载,也将始终使用jQuery!因此我们不必使用
$
-我们可以只使用
angular.element
。
第四,与第三个紧密相关,是jqLite元素不需要包装在
$
中-传递给
element
函数的
link
已经是一个jQuery元素!
第五,我们在前面的部分中已经提到过,为什么我们将模板内容混入我们的逻辑中?
可以更简单地重写此指令(即使是非常复杂的情况!),如下所示:
.directive( 'myDirective', function () {
return {
scope: true,
template: '<a class="btn" ng-class="{active: on}" ng-click="toggle()">Toggle me!</a>',
link: function ( scope, element, attrs ) {
scope.on = false;
scope.toggle = function () {
scope.on = !scope.on;
};
}
};
});
同样,模板内容在模板中,因此您(或您的用户)可以轻松地将其替换为符合任何必需样式的样式,而无需触动逻辑。可重用性-繁荣!
而且还有所有其他好处,例如测试-很简单!无论模板中包含什么内容,该指令的内部API都不会被触及,因此重构很容易。您可以根据需要随意更改模板,而无需触摸指令。而且,无论您进行什么更改,您的测试仍然可以通过。
w00t!
因此,如果指令不仅仅是类似jQuery的函数的集合,它们是什么?指令实际上是HTML的扩展。如果HTML不能做您需要做的事情,您可以编写一条指令来为您做,然后像使用HTML一样使用它。
换句话说,如果AngularJS没有做一些开箱即用的事情,请考虑团队将如何完成它以适合
ngClick
,
ngClass
等。
摘要
甚至不使用jQuery。甚至不包含它。它会让你退缩。当您遇到问题时,您认为自己已经知道如何使用jQuery解决问题,因此在尝试使用
$
之前,请尝试考虑如何在AngularJS范围内解决该问题。如果您不知道,请询问! 20的19倍中,最好的方法不需要jQuery,并尝试使用jQuery解决它会为您带来更多工作。
我是一名优秀的程序员,十分优秀!