假设我熟悉使用jQuery开发客户端应用程序,但是现在我想开始使用AngularJS。您能否描述必要的范式转换?以下是一些可以帮助您确定答案的问题:
我不希望在jQuery和之间进行详细的比较AngularJS。
jQuery
AngularJS
1.不要设计您的页面,然后通过DOM操作对其进行更改
在jQuery中,您可以设计一个页面,然后将其动态化。这是因为jQuery是为增强而设计的,并且在此简单前提下得到了难以置信的增长。
但是在AngularJS中,您必须从头开始,牢记架构。您不必从“我拥有DOM的这个部分,而我想使其成为X”开始,而是必须从要完成的事情开始,然后开始设计应用程序,然后最后开始设计视图。
同样,不要以jQuery做X,Y和Z的想法开始,所以我只在模型和控制器的基础上添加AngularJS。这是 真的 ,当你刚刚起步的,这就是为什么我总是建议新AngularJS开发完全不使用jQuery,至少直到他们习惯做的事情“角路”诱人。
我在这里和邮件列表中已经看到许多开发人员使用jQuery插件(包含150或200行代码)创建了这些精心设计的解决方案,然后将其粘贴到AngularJS中,并使用了一系列$apply令人困惑和费解的回调和;但是他们最终使它起作用了!问题在于,在 大多数 情况下,可以用少量代码在AngularJS中重写jQuery插件,突然之间所有事情都变得容易理解和直接了。
$apply
底线是:解决时,首先“在AngularJS中思考”;如果您想不出解决方案,请询问社区;如果毕竟,有没有简单的解决方案, 然后 随意达到了jQuery。但是不要让jQuery成为拐杖,否则您将永远无法掌握AngularJS。
首先知道单页应用程序。它们 不是 网页。因此 ,除了 像客户端开发人员那样思考 之外 ,我们还需要像服务器端开发人员 那样 思考。我们必须考虑如何将应用程序划分为各个可扩展的可测试组件。
那你该 怎么 做呢?您如何“在AngularJS中思考”?以下是一些与jQuery相反的一般原则。
在jQuery中,我们以编程方式更改视图。我们可以将下拉菜单定义如下ul:
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操作” 的指令。她不需要输入正确的答案或筛选任何代码。该视图告诉我们应该发生什么。干净得多。
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" />。有很多的欣喜。
<input ng-model="entry.msg" />
在jQuery中,DOM有点像模型。但是在AngularJS中,我们有一个单独的模型层,我们可以用它想要的任何方式进行管理,完全独立于视图。这有助于实现上述数据绑定,保持关注点分离,并引入更大的可测试性。其他答案都提到了这一点,因此我将其保留。
以上所有内容都与这个总体主题相关:将您的关注点分开。您的观点充当了将要发生的事情的正式记录(大部分情况下);您的模型代表您的数据;您有一个服务层来执行可重用的任务;您进行DOM操作并使用指令扩展视图;然后将它们与控制器粘合在一起。其他答案中也提到了这一点,我唯一要添加的内容就是可测试性,我将在下面的另一部分中进行讨论。
帮助我们分离关注点的是依赖注入(DI)。如果您来自服务器端语言(从Java到PHP),您可能已经熟悉此概念,但是如果您是来自jQuery的客户端人员,那么这个概念似乎从愚蠢到多余到时髦都不是。 。但事实并非如此。:-)
从广泛的角度来看,DI意味着您可以非常自由地声明组件,然后可以从任何其他组件中声明它们的实例,然后将其授予。您不必了解加载顺序,文件位置或类似内容。该功能可能不会立即可见,但我仅提供一个(常见)示例:测试。
假设在我们的应用程序中,我们需要一个服务,该服务通过REST API以及本地存储(取决于应用程序状态)来实现服务器端存储。在我们的控制器上运行测试时,我们不需要与服务器通信- 毕竟,我们正在测试 控制器 。我们可以添加一个与原始组件同名的模拟服务,并且注入器将确保我们的控制器自动获得伪造的服务-我们的控制器不需要也不需要知道两者之间的区别。
说到测试…
这确实是第3部分有关体系结构的一部分,但它非常重要,因此我将其作为自己的顶层部分。
在您已经看到,使用或编写的所有jQuery插件中,有多少具有相应的测试套件?并不是很多,因为jQuery不太适合这种情况。但是AngularJS是。
在jQuery中,测试的唯一方法通常是使用示例/演示页面独立创建组件,我们的测试可以针对该示例/演示页面执行DOM操作。因此,我们必须分别开发一个组件, 然后 将其集成到我们的应用程序中。多么不便!很多时候,当使用jQuery开发时,我们选择迭代而不是测试驱动的开发。谁能责怪我们?
但是因为我们有关注点分离,所以我们可以在AngularJS中迭代进行测试驱动的开发!例如,假设我们要一个超级简单的指令在菜单中指示当前路线。我们可以在应用程序视图中声明我们想要的:
<a href="/hello" when-active>Hello</a>
好的,现在我们可以为不存在的when-active指令编写测试:
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' ); } }); } }; });
现在 , 我们的测试通过了 , 并且菜单按要求执行。我们的开发 既是 迭代的 又是 测试驱动的。邪恶的酷。
您会经常听到“仅在指令中进行DOM操作”。 这是必须的。 谨慎对待它!
但是让我们深入一点…
有些指令只是修饰视图中已经存在的内容(认为ngClass),因此有时会立即进行DOM操作,然后基本完成。但是,如果一条指令就像一个“小部件”并具有一个模板,则它 也 应该尊重关注点的分离。也就是说,模板在链接和控制器功能中 也 应在很大程度上与其实现无关。
ngClass
AngularJS附带了一整套工具,使这一过程变得非常容易。与ngClass我们可以动态更新的类; ngModel允许双向数据绑定;ngShow并以ngHide编程方式显示或隐藏元素;还有更多-包括我们自己编写的内容。换句话说,我们可以在 没有 DOM操作的 情况下进行 各种出色的工作。DOM操作越少,指令的测试就越容易,指令的样式就越容易,将来更改就越容易,它们的重用性和可分发性就越高。
ngModel
ngShow
ngHide
我看到很多使用指令作为投掷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); }); } }; });
这有一些问题:
angular.element
$
element
link
可以更简单地重写此指令(即使是非常复杂的情况!),如下所示:
.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等。
ngClick
甚至不使用jQuery。甚至不包含它。它会让你退缩。并且当您遇到问题时,您认为自己已经知道如何使用jQuery解决问题,因此在$尝试之前,请尝试考虑如何在AngularJS范围内解决该问题。如果您不知道,请询问!20的19倍中,最好的方法不需要jQuery,并尝试使用jQuery解决它会为您带来更多工作。