在jQuery中,您设计一个页面,然后使其动态化.这是因为jQuery是为增强而设计的,并且从这个简单的前提发展而来.
但是在AngularJS中,您必须从头开始考虑您的体系 struct .不要一开始就想"我有这块DOM,我想让它做X",您必须从您想要完成的事情开始,然后开始设计您的应用程序,最后开始设计您的视图.
类似地,不要从jQuery执行X、Y和Z的 idea 开始,所以我将在模型和控制器的基础上添加AngularJS.当你刚开始的时候,这是非常诱人的,这就是为什么我总是建议新的AngularJS开发人员不要使用jQuery,至少在他们习惯用"Angular 的方式"做事情之前.
我在这里和邮件列表上看到许多开发人员使用150或200行代码的jQuery插件创建了这些精心设计的解决方案,然后将这些插件粘到AngularJS中,其中包含一组回调和$apply
个令人困惑和费解的代码;但他们最终还是成功了!问题是,在most种情况下,jQuery插件可以在一小部分代码中用AngularJS重写,突然之间,一切都变得易懂和简单.
底线是:解决问题时,首先要"用AngularJS思考";如果你想不出一个解决方案,问问社区;如果在所有这些之后没有简单的解决方案,那么就可以自由地使用jQuery.但不要让jQuery成为拐杖,否则你永远都无法掌握AngularJS.
首先要知道single-page applications等于applications.它们是not个网页.因此,我们需要像服务器端开发人员in addition那样思考,也需要像客户端开发人员那样思考.我们必须考虑如何将我们的应用程序划分为单独的、可扩展的、可测试的组件.
那么你会这么做吗?你如何"用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版本中,任何查看模板的人都知道应该发生什么.每当开发团队的新成员加入时,她都可以看到这一点,然后know有一个名为dropdownMenu
的指令在其上运行;她不需要凭直觉找到正确答案,也不需要筛选任何代码.景色告诉了我们应该发生什么.干净多了.
刚接触AngularJS的开发人员经常会问这样一个问题:如何找到特定类型的所有链接并向其添加指令.当我们回答:你不这样做的时候,开发人员总是目瞪口呆.但你不这样做的原因是,这就像半个jQuery,半个AngularJS,没有什么好处.这里的问题是,开发人员试图在AngularJS的上下文中"执行jQuery".这永远不会奏效.该观点与官方记录一致.在指令之外(下文将对此进行详细介绍),您永远不会更改DOM.指令被应用in the view,所以意图是明确的.
记住:不要设计,然后再做标记.你必须先设计,然后再设计.
到目前为止,这是AngularJS最棒的特性之一,并且省go 了我在上一节提到的各种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 node .如果我们想要删除日志(log)条目,我们也必须针对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 alert 框.而且我们从来不需要更改控制器代码!但更重要的是,无论日志(log)更新到where或how,视图也会改变.自动地整洁的
虽然我没有在这里展示,但数据绑定是双向的.因此,这些日志(log)消息也可以在视图中编辑,只需执行以下操作:<input ng-model="entry.msg" />
.大家都很高兴.
在jQuery中,DOM有点像模型.但是在AngularJS中,我们有一个单独的模型层,可以以任何我们想要的方式进行管理,完全独立于视图.这有助于上述数据绑定,保持了separation of concerns,并引入了更好的可测试性.其他的回答都提到了这一点,所以我就到此为止了.
以上所有这些都与这个总体主题相联系:让你们的关注点分开.您的视图充当应该发生的事情的官方记录(在很大程度上);您的模型表示您的数据;您有一个服务层来执行可重用的任务;您执行DOM操作并使用指令增强您的视图;并且您使用控制器将其粘合在一起.在其他答案中也提到了这一点,我要添加的唯一一件事与可测试性有关,我将在下面的另一节中讨论这一点.
帮助我们分离关注点是dependency injection(DI).如果你来自服务器端语言(从Java到PHP),你可能已经熟悉这个概念了,但如果你是来自jQuery的客户端人员,这个概念可能看起来很愚蠢、多余,甚至时髦.但事实并非如此.:-)
从广泛的Angular 来看,DI意味着您可以非常自由地声明组件,然后从任何其他组件,只要请求它的一个实例,它就会被授予.您不必知道加载顺序、文件位置或诸如此类的信息.这种能力可能不会立即显现出来,但我只提供一个(常见的)例子:测试.
假设在我们的应用程序中,我们需要一个通过REST API实现服务器端存储的服务,根据应用程序状态,还需要本地存储.在控制器上运行测试时,我们不想与服务器通信——毕竟,我们正在测试controller.我们只需添加一个与原始组件同名的模拟服务,注入器将确保我们的控制器自动获得假服务——我们的控制器不知道也不需要知道区别.
说到测试...
这实际上是关于架构的第3节的一部分,但是它太重要了,所以我把它作为它自己的顶级部分.
在您看到、使用或编写的众多jQuery插件中,有多少有附带的测试套件?不是很多,因为jQuery对此不太适应.但是AngularJS是.
在jQuery中,测试的唯一方法通常是使用示例/演示页面独立创建组件,我们的测试可以根据该页面执行DOM操作.因此,我们必须单独开发一个组件,并将其集成到我们的应用程序中.太不方便了!很多时候,当使用jQuery开发时,我们 Select 迭代开发,而不是测试驱动的开发.谁又能责怪我们呢?
但是因为我们有分离的关注点,我们可以在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' );
}
});
}
};
});
我们的测试现在超过了and,我们的菜单按要求执行.我们的开发是both迭代and测试驱动的.太棒了-太酷了.
您会经常听到"只在指令中执行DOM操作".This is a necessity.以应有的尊重对待它!
不过,让我们再潜得更深一点.
有些指令只是修饰视图中已经存在的内容(想想ngClass
),因此有时会直接进行DOM操作,然后基本上就完成了.但是,如果一个指令像一个"小部件"并且有一个模板,它应该尊重关注点的分离.也就是说,模板too应该在很大程度上独立于其在链路和控制器功能中的实现.
AngularJS提供了一整套工具,使之非常容易;使用ngClass
,我们可以动态更新类;ngModel
允许双向数据绑定;ngShow
和ngHide
以编程方式显示或隐藏元素;还有更多——包括我们自己写的那些.换句话说,我们可以做各种令人惊叹的操作.DOM操作越少,测试指令就越容易,样式就越容易,将来就越容易更改,它们的可重用性和可分发性也就越强.
我看到很多刚接触AngularJS的开发人员使用指令作为抛出大量jQuery的地方.换句话说,他们认为"既然我不能在控制器中进行DOM操作,我就把代码放在指令中".虽然这当然好得多,但通常是still wrong.
想想我们在第3节编程的记录器.即使我们把它放在指令中,我们也希望以"Angular "的方式来做.它不需要任何DOM操作!有很多时候DOM操作是必要的,但这比你想象的要少!在应用程序中执行DOM操作anywhere之前,问问自己是否真的需要.也许有更好的办法.
这里有一个快速示例,它显示了我最常看到的模式.我们想要一个可移动的按钮.(注意:这个示例有点做作,非常冗长,用来表示以完全相同的方式解决的更复杂的 case .)
.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
,当我们的组件被放到一个没有jQuery的项目中时,它仍然可以工作.angular.element
)也将always使用jQuery(如果它已加载)!所以我们不需要用$
,我们可以用angular.element
.$
中——传递给link
函数的element
将是一个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;
};
}
};
});
同样,模板的东西在模板中,所以你(或你的用户)可以很容易地把它换成一个符合任何必要风格的,而且logic个从来都不用碰.可重用性-轰!
还有其他的好处,比如测试——很简单!无论模板中有什么,指令的内部API都不会被触及,因此重构很容易.您可以随意更改模板,而无需触碰指令.不管你做了什么改变,你的测试还是通过了.
w00t!
那么,如果指令不仅仅是类似jQuery的函数的集合,它们是什么呢?指令实际上是extensions of HTML个.如果HTML不能完成你需要它做的事情,你可以编写一个指令来完成它,然后像使用HTML一样使用它.
换句话说,如果AngularJS没有做一些开箱即用的事情,想一想团队将如何完成它,以适应ngClick
、ngClass
等.
甚至不要使用jQuery.甚至都不要包括在内.它会阻碍你前进.当你遇到一个你认为自己已经知道如何在jQuery中解决的问题时,在你达到$
之前,试着想想如何在AngularJS的范围内解决它.如果你不知道,问吧!20次中有19次,最好的方法不需要jQuery,而try 用jQuery解决它会为您带来更多的工作.