协慌网

登录 贡献 社区

如果我有 jQuery 背景,“在 AngularJS 中思考”?

假设我熟悉在jQuery 中开发客户端应用程序,但现在我想开始使用AngularJS 。你能描述一下必要的范式转变吗?以下是一些可能有助于您确定答案的问题:

  • 如何以不同方式构建和设计客户端 Web 应用程序?最大的区别是什么?
  • 我应该停止做什么 / 使用什么; 我应该开始做什么 / 使用什么呢?
  • 有任何服务器端考虑因素 / 限制吗?

我不是在寻找jQueryAngularJS之间的详细比较。

答案

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. 始终从架构的角度思考

首先要知道单页应用程序应用程序 。他们不是网页。因此,我们需要这样想, 除了思维就像一个客户端开发的服务器端的开发。我们必须考虑如何将我们的应用程序划分为单独的,可扩展的,可测试的组件。

所以,那你怎么办呢?你如何 “在 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 中,我们有一个单独的模型层,我们可以以任何方式管理,完全独立于视图。这有助于上述数据绑定,保持关注点分离 ,并引入更大的可测试性。其他答案提到了这一点,所以我就把它留在那里。

关注点分离

以上所有内容都与这个主题相关:将您的担忧分开。你的观点作为应该发生的事情的官方记录(大多数情况下); 你的模型代表你的数据; 你有一个服务层来执行可重用的任务; 你做 DOM 操作并用指令扩充你的视图; 然后将它们与控制器粘合在一起。在其他答案中也提到了这一点,我要添加的唯一内容与可测试性有关,我将在下面的另一部分中讨论。

依赖注入

为了帮助我们分离关注点, 依赖注入 (DI)。如果你来自服务器端语言(从JavaPHP ),你可能已经熟悉了这个概念,但如果你是一个来自 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' );
                }
            });
        }
    };
});

我们的测试现在通过我们的菜单按要求执行。我们的开发既是迭代的, 也是测试驱动的。妖兽爽。

从概念上讲,指令不是打包的 jQuery

您经常会听到 “只在指令中执行 DOM 操作”。 这是必要的。善意对待它!

但是让我们深入一点......

一些指令只是装饰视图中的内容(想想ngClass ),因此有时会直接进行 DOM 操作,然后基本完成。但是,如果指令就像一个 “小部件” 并且有一个模板,那么它应该尊重关注点的分离。也就是说,模板应该在很大程度上独立于链接和控制器功能中的实现。

AngularJS 附带了一整套工具,使这一切变得非常简单; 使用ngClass我们可以动态更新类; ngModel允许双向数据绑定; ngShowngHide编程方式显示或隐藏元素; 还有更多 - 包括我们自己写的那些。换句话说,我们可以在没有 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);
            });
        }
    };
});

这有一些问题:

  1. 首先,jQuery 从来都不是必需的。我们在这里做的一切都不需要 jQuery!
  2. 其次,即使我们的页面上已经有 jQuery,也没有理由在这里使用它; 我们可以简单地使用angular.element ,我们的组件在放入没有 jQuery 的项目时仍然可以工作。
  3. 第三,即使假设:jQuery 需要这种指令工作,jqLite( angular.element )将始终使用 jQuery,如果它是装的!所以我们不需要使用$ - 我们可以使用angular.element
  4. 四是密切相关的第三个,就是 jqLite 元件不必被包裹在$ - 的element传递给link功能将已经是 jQuery 的元素!
  5. 第五,我们在前面的章节中提到过,为什么我们将模板内容混合到逻辑中?

这个指令可以被重写(即使对于非常复杂的情况!)更简单如下:

.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 不需要它做某事,你可以编写一个指令来为你完成,然后就像使用它一样使用它。

换句话说,如果 AngularJS 没有开箱即用,请考虑团队如何完成它以适应ngClickngClass等。

摘要

甚至不使用 jQuery。甚至不包括它。它会阻止你。当你遇到一个问题,你认为你已经知道如何在 jQuery 中解决,在你达到$ ,试着考虑如何在 AngularJS 的范围内做到这一点。如果你不知道,请问! 20 次中有 19 次,最好的方法是不需要 jQuery 并尝试使用 jQuery 解决它,为您提供更多的工作。

势在必行→陈述性

在 jQuery 中, 选择器用于查找DOM元素,然后将事件处理程序绑定 / 注册到它们。当事件触发时,执行(命令性)代码以更新 / 更改 DOM。

在 AngularJS 中,您需要考虑视图而不是 DOM 元素。视图是包含 AngularJS 指令的 (声明性)HTML。指令为我们幕后设置了事件处理程序,并为我们提供了动态数据绑定。选择器很少使用,因此对 ID(以及某些类型的类)的需求大大减少。视图与模型相关 (通过范围)。视图是模型的投影。事件更改模型(即数据,范围属性)以及投影这些模型的视图会 “自动” 更新。

在 AngularJS 中,考虑模型,而不是 jQuery 选择的 DOM 元素来保存您的数据。将视图视为这些模型的投影,而不是注册回调来操纵用户看到的内容。

关注点分离

jQuery 使用不引人注目的 JavaScript - 行为(JavaScript)与结构(HTML)分离。

AngularJS 使用控制器和指令(每个控制器和指令可以有自己的控制器和 / 或编译和链接函数)来从视图 / 结构(HTML)中删除行为。 Angular 还提供服务过滤器,以帮助分离 / 组织您的应用程序。

另请参见https://stackoverflow.com/a/14346528/215945

应用设计

设计 AngularJS 应用程序的一种方法:

  1. 想想你的模特。为这些模型创建服务或您自己的 JavaScript 对象。
  2. 想想你想要如何展示你的模特 - 你的观点。为每个视图创建 HTML 模板,使用必要的指令来获取动态数据绑定。
  3. 将控制器连接到每个视图(使用 ng-view 和 routing,或 ng-controller)。让控制器只查找 / 获取视图完成其工作所需的任何模型数据。使控制器尽可能薄。

原型继承

你可以在不知道 JavaScript 原型继承如何工作的情况下使用 jQuery 做很多事情。在开发 AngularJS 应用程序时,如果您对 JavaScript 继承有很好的理解,就可以避免一些常见的陷阱。推荐阅读: AngularJS 中范围原型 / 原型继承的细微差别是什么?

AngularJS 与 jQuery

AngularJS 和 jQuery 采用了截然不同的意识形态。如果您来自 jQuery,您可能会发现一些令人惊讶的差异。 Angular 可能会让你生气。

这是正常的,你应该推进。 Angular 值得。

差异很大(TLDR)

jQuery 为您提供了一个工具包,用于选择 DOM 的任意位并对其进行临时更改。你几乎可以做任何你喜欢的事情。

相反,AngularJS 为您提供了一个编译器

这意味着 AngularJS 从上到下读取您的整个 DOM 并将其视为代码,就像编译器的指令一样。当它遍历 DOM 时,它会查找告诉 AngularJS 编译器如何操作以及如何操作的特定指令 (编译器指令)。指令是充满 JavaScript 的小对象,可以匹配属性,标签,类甚至注释。

当 Angular 编译器确定 DOM 的一部分与特定指令匹配时,它调用指令函数,向其传递 DOM 元素,任何属性,当前 $ scope(本地变量存储)以及一些其他有用位。这些属性可能包含可由指令解释的表达式,它告诉它如何呈现,以及何时应重绘自身。

然后,指令可以引入其他 Angular 组件,例如控制器,服务等。编译器底部出现的是一个完整形成的 Web 应用程序,已连线并准备就绪。

这意味着 Angular 是模板驱动的 。您的模板驱动 JavaScript,而不是相反。这是角色的彻底颠倒,与我们过去 10 年来一直在写的不引人注目的 JavaScript 完全相反。这可能需要一些时间来适应。

如果这听起来像是过度规范和限制,那么没有什么可以离真相更远。由于 AngularJS 将您的 HTML 视为代码,因此您可以在 Web 应用程序中获得HTML 级别的粒度 。一切皆有可能,一旦你做出一些概念上的飞跃,大多数事情都会非常容易。

让我们深入了解细节。

首先,Angular 不会取代 jQuery

Angular 和 jQuery 做了不同的事情。 AngularJS 为您提供了一组用于生成 Web 应用程序的工具。 jQuery 主要为您提供修改 DOM 的工具。如果您的页面上存在 jQuery,AngularJS 将自动使用它。如果不是这样,AngularJS 附带了 jQuery Lite,这是一个减少但仍然完美可用的 jQuery 版本。

Misko 喜欢 jQuery 并且不反对你使用它。但是,随着您的进步,您会发现使用范围,模板和指令的组合可以完成所有工作,并且您应该尽可能地选择此工作流程,因为您的代码将更加离散,更易于配置,以及更多角。

如果你使用 jQuery,你不应该把它洒到这个地方。 AngularJS 中 DOM 操作的正确位置是在一个指令中。稍后会详细介绍。

使用选择器与声明模板的不显眼的 JavaScript

jQuery 通常不引人注意地应用。您的 JavaScript 代码链接在标题(或页脚)中,这是它唯一提到的地方。我们使用选择器来挑选页面的位并编写插件来修改这些部分。

JavaScript 处于控制之中。 HTML 具有完全独立的存在。即使没有 JavaScript,您的 HTML 仍然是语义。 Onclick 属性是非常糟糕的做法。

您将注意到的关于 AngularJS 的第一件事是自定义属性无处不在 。您的 HTML 将充满 ng 属性,这些属性基本上是类固醇上的 onClick 属性。这些是指令(编译器指令),并且是模板挂钩到模型的主要方式之一。

当你第一次看到这个时,你很可能会把 AngularJS 写成旧式的侵入式 JavaScript(就像我之前做的那样)。事实上,AngularJS 不遵守这些规则。在 AngularJS 中,您的 HTML5 是一个模板。它由 AngularJS 编译以生成您的网页。

这是第一个很大的区别。对于 jQuery,您的网页是一个要操纵的 DOM。对于 AngularJS,您的 HTML 是要编译的代码。 AngularJS 读入您的整个网页,并使用其内置编译器将其编译成新的网页。

你的模板应该是声明性的; 通过阅读它的含义应该很清楚。我们使用有意义名称的自定义属性。我们再次使用有意义的名称组成新的 HTML 元素。具有最少 HTML 知识且无编码技能的设计人员可以阅读您的 AngularJS 模板并了解它的作用。他或她可以进行修改。 这是 Angular 的方式。

模板处于驾驶座位。

在开始使用 AngularJS 并运行教程时,我问自己的第一个问题是“我的代码在哪里?” 。我没有写过 JavaScript,但我有这些行为。答案很明显。因为 AngularJS 编译 DOM,所以 AngularJS 将您的 HTML 视为代码。对于许多简单的情况,只需编写一个模板就可以了,让 AngularJS 将它编译成一个应用程序。

您的模板可以驱动您的应用它被视为DSL 。您编写 AngularJS 组件,AngularJS 将负责将它们拉入并根据模板的结构在适当的时间提供它们。这与标准MVC模式非常不同,其中模板仅用于输出。

例如,它比Ruby on Rails更类似于XSLT

这是一种彻底的控制反转,需要一些人习惯。

停止尝试从 JavaScript 驱动您的应用程序。让模板驱动应用程序,让 AngularJS 负责将组件连接在一起。这也是 Angular 方式。

语义 HTML 与语义模型

使用 jQuery,您的 HTML 页面应包含语义有意义的内容。如果 JavaScript(由用户或搜索引擎)关闭,您的内容仍然可以访问。

因为 AngularJS 将您的 HTML 页面视为模板。模板不应该是语义的,因为您的内容通常存储在最终来自 API 的模型中。 AngularJS 使用模型编译 DOM 以生成语义 Web 页面。

您的 HTML 源不再是语义,而是您的 API 和编译的 DOM 是语义的。

在 AngularJS 中,意思是生活在模型中,HTML 只是一个模板,仅供显示。

在这一点上,你可能有各种关于SEO和可访问性的问题,这是正确的。这里存在未解决的问题。大多数屏幕阅读器现在将解析 JavaScript。搜索引擎还可以索引AJAXed内容。不过,您需要确保使用的是 pushstate URL,并且您有一个不错的站点地图。有关该问题的讨论,请参见此处: https//stackoverflow.com/a/23245379/687677

关注点分离(SOC)与 MVC

关注点分离 (SOC)是一种在多年的 Web 开发中长大的模式,其原因有多种,包括 SEO,可访问性和浏览器不兼容性。它看起来像这样:

  1. HTML - 语义含义。 HTML 应该是独立的。
  2. CSS - 样式,没有 CSS,页面仍然可读。
  3. JavaScript - 行为,没有脚本,内容仍然存在。

同样,AngularJS 不遵守他们的规则。 一举一动AngularJS 取消了十年的智慧 ,而是实现了 MVC 模式,其中模板不再是语义,甚至不是一点点。

它看起来像这样:

  1. 模型 - 您的模型包含您的语义数据。模型通常是JSON对象。模型作为名为 $ scope 的对象的属性存在。您还可以在 $ scope 上存储方便的实用程序函数,然后模板可以访问这些函数。
  2. 视图 - 您的视图以 HTML 格式编写。视图通常不是语义的,因为您的数据存在于模型中。
  3. 控制器 - 您的控制器是一个 JavaScript 函数,它将视图挂钩到模型。它的功能是初始化 $ scope。根据您的应用程序,您可能需要也可能不需要创建控制器。您可以在页面上拥有许多控制器。

MVC 和 SOC 不在相同比例的两端,它们位于完全不同的轴上。 SOC 在 AngularJS 上下文中没有任何意义。你必须忘记它并继续前进。

如果像我一样,你经历过浏览器大战,你可能会觉得这个想法很冒犯。我保证,克服它,它是值得的。

插件与指令

插件扩展了 jQuery。 AngularJS 指令扩展了浏览器的功能。

在 jQuery 中,我们通过向 jQuery.prototype 添加函数来定义插件。然后我们通过选择元素并在结果上调用插件将它们挂钩到 DOM 中。我们的想法是扩展 jQuery 的功能。

例如,如果您想在页面上显示轮播,则可以定义无序的图表列表,可能包含在 nav 元素中。然后,您可以编写一些 jQuery 来选择页面上的列表,并将其重新设置为具有超时的库以执行滑动动画。

在 AngularJS 中,我们定义了指令。指令是一个返回 JSON 对象的函数。该对象告诉 AngularJS 要查找哪些 DOM 元素,以及要对它们进行哪些更改。使用您发明的属性或元素将指令挂钩到模板。我们的想法是使用新的属性和元素扩展 HTML 的功能。

AngularJS 方法是扩展本机外观 HTML 的功能。您应该编写看起来像 HTML 的 HTML,并使用自定义属性和元素进行扩展。

如果你想要一个轮播,只需使用一个<carousel />元素,然后定义一个指令来拉入一个模板,并让那个吸盘工作。

很多小指令与配置交换机的大插件

jQuery 的趋势是编写像 lightbox 这样的大插件,然后我们通过传递大量的值和选项来配置。

这是 AngularJS 中的一个错误。

以下拉列表为例。在编写下拉插件时,您可能会想要在单击处理程序中进行编码,也许是一个添加到 V 形图中的功能,可以是向上或向下,也许更改展开元素的类,显示隐藏菜单,所有有用的东西。

直到你想做一个小改动。

假设您有一个要悬停的菜单。那么现在我们有一个问题。我们的插件已连接到我们的点击处理程序中,我们将需要添加一个配置选项,以使其在这种特定情况下的行为不同。

在 AngularJS 中,我们编写较小的指令。我们的下拉指令非常小。它可能保持折叠状态,并提供 fold(),展开()或 toggle()方法。这些方法只会更新 $ scope.menu.visible,这是一个保持状态的布尔值。

现在在我们的模板中,我们可以将其连线:

<a ng-click="toggle()">Menu</a>
<ul ng-show="menu.visible">
  ...
</ul>

需要更新鼠标悬停?

<a ng-mouseenter="unfold()" ng-mouseleave="fold()">Menu</a>
<ul ng-show="menu.visible">
  ...
</ul>

该模板驱动应用程序,因此我们获得 HTML 级别的粒度。如果我们想要逐案例外,模板可以轻松实现。

关闭与 $ 范围

JQuery 插件是在闭包中创建的。在该关闭内保持隐私。您可以在该闭包内维护您的范围链。您只能真正访问由 jQuery 传入插件的 DOM 节点集,以及在闭包中定义的任何局部变量以及您定义的任何全局变量。这意味着插件非常独立。这是一件好事,但在创建整个应用程序时会受到限制。尝试在动态页面的各个部分之间传递数据变成了一件苦差事。

AngularJS 有 $ scope 对象。这些是由 AngularJS 创建和维护的特殊对象,您可以在其中存储模型。某些指令将生成一个新的 $ scope,默认情况下,它使用 JavaScript 原型继承继承其包装 $ scope。 $ scope 对象可在控制器和视图中访问。

这是聪明的部分。因为 $ scope 继承的结构大致遵循 DOM 的结构,所以元素可以无缝地访问它们自己的范围,任何包含范围,一直到全局 $ scope(与全局范围不同)。

这使得传递数据和在适当级别存储数据变得更加容易。如果展开下拉列表,则只有下拉列表 $ scope 需要知道它。如果用户更新其首选项,您可能希望更新全局 $ scope,并且将自动提醒监听用户首选项的任何嵌套作用域。

这可能听起来很复杂,事实上,一旦你放松它,就像飞行一样。您不需要创建 $ scope 对象,AngularJS 会根据您的模板层次结构正确且适当地为您实例化和配置它。然后,AngularJS 使用依赖注入的魔力使其可用于您的组件(稍后将详细介绍)。

手动 DOM 更改与数据绑定

在 jQuery 中,您可以手动完成所有 DOM 更改。您以编程方式构造新的 DOM 元素。如果您有一个 JSON 数组并且想要将它放到 DOM 中,则必须编写一个函数来生成 HTML 并插入它。

在 AngularJS 中,您也可以这样做,但我们鼓励您使用数据绑定。更改您的模型,并且因为 DOM 通过模板绑定到它,您的 DOM 将自动更新,无需干预。

因为数据绑定是从模板完成的,所以使用属性或大括号语法,这非常容易。与之相关的认知开销很少,所以你会发现自己一直在做这件事。

<input ng-model="user.name" />

将 input 元素绑定到$scope.user.name 。更新输入将更新当前范围中的值,反之亦然。

同样:

<p>
  {{user.name}}
</p>

将在段落中输出用户名。它是一个实时绑定,因此如果$scope.user.name值更新,模板也会更新。

Ajax 一直都是

在 jQuery 中进行 Ajax 调用非常简单,但它仍然可以考虑三次。考虑增加的复杂性,以及维护的一大块脚本。

在 AngularJS 中,Ajax 是您的默认首选解决方案,它一直在发生,几乎没有您注意到。您可以包含 ng-include 模板。您可以使用最简单的自定义指令应用模板。您可以在服务中包装 Ajax 调用,并创建一个GitHub服务或Flickr服务,您可以非常轻松地访问它。

服务对象与助手功能

在 jQuery 中,如果我们想完成一个小的非 dom 相关任务,比如从 API 中提取一个 feed,我们可能会在我们的闭包中编写一些函数来完成它。这是一个有效的解决方案,但如果我们想要经常访问该 Feed,该怎么办?如果我们想在另一个应用程序中重用该代码怎么办?

AngularJS 为我们提供服务对象。

服务是包含函数和数据的简单对象。他们总是单身,这意味着他们永远不会超过一个。假设我们想要访问 Stack Overflow API,我们可能会编写一个StackOverflowService来定义这样做的方法。

假设我们有一个购物车。我们可以定义一个 ShoppingCartService 来维护我们的购物车,并包含添加和删除项目的方法。因为服务是单例,并且由所有其他组件共享,所以任何需要的对象都可以写入购物车并从中提取数据。它始终是相同的购物车。

服务对象是自包含的 AngularJS 组件,我们可以根据需要使用和重用它们。它们是包含函数和数据的简单 JSON 对象。它们总是单例,因此如果您将数据存储在一个地方,只需通过请求相同的服务就可以将数据输出到其他地方。

依赖注入 (DI)与 Instatiation - 也称为 de-spaghettification

AngularJS 为您管理您的依赖项。如果你想要一个对象,只需引用它,AngularJS 就会为你得到它。

在你开始使用它之前,很难解释这是多么大的时间。在 jQuery 中没有像 AngularJS DI 那样的东西。

DI 意味着您不是编写应用程序并将它们连接在一起,而是定义一个组件库,每个组件都由一个字符串标识。

假设我有一个名为'FlickrService' 的组件,它定义了从 Flickr 中提取 JSON 提要的方法。现在,如果我想编写一个可以访问 Flickr 的控制器,我只需要在声明控制器时按名称引用'FlickrService'。 AngularJS 将负责实例化组件并使其可供我的控制器使用。

例如,我在这里定义一个服务:

myApp.service('FlickrService', function() {
  return {
    getFeed: function() { // do something here }
  }
});

现在,当我想使用该服务时,我只是按名称引用它:

myApp.controller('myController', ['FlickrService', function(FlickrService) {
  FlickrService.getFeed()
}]);

AngularJS 将认识到需要一个 FlickrService 对象来实例化控制器,并将为我们提供一个。

这使得连接在一起非常容易,并且几乎消除了任何倾向于 spagettification 的趋势。我们有一个扁平的组件列表,当我们需要时,AngularJS 将它们逐个交给我们。

模块化服务架构

jQuery 对如何组织代码几乎没有说明。 AngularJS 有意见。

AngularJS 为您提供了可以放置代码的模块。例如,如果您正在编写一个与 Flickr 对话的脚本,您可能需要创建一个 Flickr 模块来包装所有与 Flickr 相关的函数。模块可以包含其他模块(DI)。您的主应用程序通常是一个模块,这应该包括您的应用程序将依赖的所有其他模块。

您可以获得简单的代码重用,如果您想基于 Flickr 编写另一个应用程序,您可以只包含 Flickr 模块,您可以在新应用程序中访问所有与 Flickr 相关的功能。

模块包含 AngularJS 组件。 当我们包含一个模块时,该模块中的所有组件都可以作为由其唯一字符串标识的简单列表提供给我们 。然后我们可以使用 AngularJS 的依赖注入机制将这些组件相互注入。

总结一下

AngularJS 和 jQuery 不是敌人。可以非常好地在 AngularJS 中使用 jQuery。如果您使用 AngularJS 以及(模板,数据绑定,$ 范围,指令等),你会发现你需要比否则你可能需要少了很多 jQuery 的。

要实现的主要是您的模板驱动您的应用程序。停止尝试编写可以完成所有操作的大插件。而是写一些做一件事的指令,然后编写一个简单的模板将它们连接在一起。

少考虑不引人注目的 JavaScript,而是考虑 HTML 扩展。

我的小书

我对 AngularJS 感到非常兴奋,我写了一本关于它的简短书籍,非常欢迎您在线阅读http://nicholasjohnson.com/angular-book/ 。我希望它有用。