【全栈开发】精通 MEAN: 当 MEAN 遇到 Meetup.com 和微数据

728 查看

首先,将来自 Meetup.com 的即将举办的活动信息添加到 UGLI 主页。

HTML 和微数据

如果访问 Meetup.com 上的 HTML5 DenverUsers Group,您会看到一个类似图 1 的网页。

图 1. 一场即将举办的 HTML5 Denver User Group 会议的 Meetup.com 信息

可以看到,使用即将举办的活动信息进一步自定义 UGLI 主页所需的所有原始材料,都在 Meetup.com 页面上(即将发表的演讲的标题、会议时间和地点,等等),但Meetup.com 页面的设计与该数据紧密耦合。您想要提取并在 MEAN 应用程序中重用的信息,与 HTML 元素杂乱地混在一起,如清单 1 所示。(为了清晰性和简洁性,我对HTML 进行了编辑。)

清单 1. Meetup.com HTML源代码

<ul>
    <li itemscope="" itemtype="http://data-vocabulary.org/Event">

        <span itemprop="eventType" 
              style="display:none;">Meetup</span>
        <h3>
            <a href="http://www.meetup.com/HTML5-Denver-Users-Group/events/160326502/" itemprop="url">
                <span itemprop="summary">"Developing Offline Applications" and "HTML 5 Animations"</span>
            </a>
        </h3>

        <!-- snip -->
    </li>
</ul>

活动的标题(“Developing Offline Applications” 和 “HTML 5 Animations”)深入嵌套在 HTML中的多层中。就 HTML 文档(和您的 Web 浏览器)而言,这个任意的字符串只是嵌套在一个无序列表(<ul>) 内的几个列表项(<li>) 之一。这个列表项有一个三级标题(<h3>),大概二级和一级标题都已在文档分层结构中定义。在标题内是一个超链接 (<ahref>),其中进而包含一个任意的文本 <span>

信息要按 Meetup.com 想要的格式显示,所有这些 HTML标记必不可少。您的任务是以一种完全不同的方式在您应用程序中显示信息:您需要找到一种方式来从显示中分离出信息。

需要使用两个不同的语义层,如清单 1 所示。最基本的语义层我刚才已讨论:一项是一个列表项,另一项是一个超链接。这就是文档 语义。另一层是事件语义,由 eventTypeurlsummary等关键字表示。这些关键字与文档的呈现方式毫无关系。它们向搜索引擎(和查看 HTML 源代码的初学者)暗示信息的 “更高级含义”。这些属性是 HTML Microdata 规范的一部分。(有关微数据及其元数据前身的更多信息,请参阅 Microformats、microdata 和 RDFa!哦天哪! 边栏。)

这个特定的列表项中仅包含一个 event 的信息。搜索引擎知道此事实,是因为设计该页面的人向该列表项添加了itemtype="http://data-vocabulary.org/Event"。该页面包含许多任意超链接,其中带有
itemprop="url" 的超链接是 event本身的链接。eventTypeMeetup— 一个任意字符串,但 Meetup.com会在其所有网页上一致地使用它。活动的 summary 由带 itemprop="summary" 属性的
<span> 来标识。

<span> 元素是浏览器在呈现页面时忽略的一些 HTML 元素之一。<b>元素内的文本呈现为加粗字体;<h1> 文本的显示字号比 <h2>文本更大;<a> 文本可单击,通常使用蓝色且带下划线。当然,所有这些默认样式规则都可使用 CSS 覆盖。但<span> 标记的存在仅用于添加您自己的 CSS 样式 — 或者对于 清单 1中的 Meetup.com HTML 代码段,用于将 "Developing Offline Applications" and "HTML5 Animations" 字符串包装在 itemprop="summary" 语义标签中。

有关可添加到 MEAN 标记中来进一步描述事件的完整元数据项列表,可首先访问 Meetup.com 用于定义事件的 URL:http://data-vocabulary.org/Event

向主页添加一个占位符事件

大体了解 Meetup.com 如何显示事件信息后,可对 uGLI 应用程序执行相同操作。

在测试应用程序的 root 中键入 mongod 来启动 MongoDB,然后键入 grunt 启动 Web 应用程序。在 Web浏览器中访问 http://localhost:3000 时,您应看到在上一期中自定义的主页(如图 2 所示)。

图 2. UGLI 主页

我想向主页添加即将举办的 HTML5 Denver User Group活动。本教程的剩余部分将迭代式地完成此工作。首先,添加一些静态占位符数据来描绘页面的基本外观的线框图。

在文本编辑器中打开 public/modules/core/views/home.client.view.html。在标语下,添加活动的新标记,如清单 2 所示。

清单 2. public/modules/core/views/home.client.view.html

<section data-ng-controller="HomeController">
  <div class="jumbotron text-center">
    <!-- snip -->
  </div>

  <div class="row">
    <div>Monday, September 22, 2014</div>
    <h3><a href="http://www.meetup.com/HTML5-Denver-Users-Group/events/160326502/">
    "Developing Offline Applications" and "HTML 5 Animations"</a></h3>
    <div class="col-md-4">
     <h4>When</h4>
     <p>6pm</p>
     <h4>Where</h4>
     <address>
       <span>Rally Software</span><br>
       1550 Wynkoop<br>     
       Denver, CO<br>
     </address>
    </div>

    <div class="col-md-8">
     <p><b>6 pm : "Developing Offline Applications with HTML 5" by Venkat Subramaniam</b></p> 
     <p><b>7 pm: Dinner and Networking</b></p> 
     <p><b>7:30 pm: "HTML 5 Animations - building true richness on the web" by Venkat Subramaniam</b></p>
    </div>
  </div>
</section>

在浏览器中查看更新的主页时,它应类似于图 3。

图 3. UGLI 主页线框图

有了基本的 HTML

默认样式更美观。为此,需要添加一些语义。

Bootstrap 提供了您使用的默认结构化 HTML 元素(比如 <div><h3>)的内置样式。它还提供了一些不属于默认 HTML 元素的附加的结构化类,比如 row
col

初学的 Web 开发人员常常会考虑网页的结构,而不是显示的信息。结果是,他们使用像 big-red-italicleft-column-header 这样的名称来编写自定义 CSS 类。从语法上讲该方法并没有错,但我发现使用像event-dateevent-location这样的语义名称时,网站的长期维护更容易。这样,当客户在一年后返回要求将所有分类汇总标为绿色而不是红色时,我可编写一个 CSS 类来识别显示了哪些内容
(subtotals),而不是 如何显示(green-body-text)。我也不太可能不经意地更改页面上其他恰好也使用了 green-body-text CSS规则的元素。

返回到您刚编写的 HTML,添加一些具有合适的语义的 CSS 类,比如 eventevent-date
event-title

<div class="row center-block event">
<div class="event-date">Monday, September 22, 2014</div>
<h3 class="event-title"><a href="http://www.meetup.com/HTML5-Denver-Users-Group/events/160326502/">
    "Developing Offline Applications" and "HTML 5 Animations"</a></h3>

center-block 结构类来自 上一期中介绍的 Bootstrap 库。稍后将会看到,将 event 类的 width 减小到 75%
时,center-block 将确保左侧和右侧的空白区域均等。

这非常适合在同一个元素上混合使用结构和语义类。事实上,从长远上来讲,它使我更容易快速识别哪些类是特定于应用程序的(event-*),哪些类是通用的(rowcenter-block)。

将这些类添加到 HTML 后,就可以定义一些自定义 CSS 规则了。在文本编辑器中打开 public/modules/core/css/core.css。因为每个模块都拥有自己的CSS,所以可保持 “面向组件” 的理念。而且通过抓取整个子目录树,更容易在项目间共享模块。

添加 CSS 样式规则,如清单 3 所示。

清单 3. public/modules/core/css/core.css

.event {
    width: 75%;
}

.event-date {
    font-style: italic;
}

.event-title {
    margin-top: 0;
}

现在您的主页看起来没那么粗糙,更加美观了,如图 4 所示。

图 4. 带 CSS 样式的 UGLI 主页

最后,再次返回添加微数据元数据。能否跳过一步,在 CSS 规则中使用微数据元素,而不定义自定义元素?当然可以。但我想将它们分开。毕竟,它们具有两种不同的用途。一个用于 CSS样式,另一个用于搜索引擎优化 (SEO)。假设您 5年前遇到一个用户案例,要求您将微数据迁移到另一个更新的规范。如果在满足案例要求的同时,带来了影响网站外观的副作用,就太遗憾了。这两个特性应是完全不同的。

再一次在文本编辑器中打开 public/modules/core/views/home.client.view.html。为该活动添加新的语义微数据标记,如清单 4所示。(有关使用微数据标记活动的范例,请参阅 富代码段 - 活动。)

清单 4. 添加微数据

<div class="row center-block event" 
   itemscope 
   itemtype="http://data-vocabulary.org/Event">
  <span itemprop="eventType" 
      style="display:none;">Meetup</span> 
  <time class="event-date" 
      itemprop="startDate" 
      datetime="2014-09-22T18:00-06:00">Monday, September 22, 2014</time>
  <h3 class="event-title"><a href="http://www.meetup.com/HTML5-Denver-Users-Group/events/160326502/" 
    itemprop="url"><span itemprop="summary">"Developing Offline Applications" and 
    "HTML 5 Animations"</span></a></h3>
  <div class="col-md-4">
    <h4>When</h4>
    <p>6pm</p>
    <h4>Where</h4>
    <address itemprop="location" 
         itemscope 
         itemtype="http://data-vocabulary.org/?Organization">
      <span itemprop="name">Rally Software</span><br>
      <span itemprop="address" 
          itemscope 
          itemtype="http://data-vocabulary.org/Address">
        <span itemprop="street-address">1550 Wynkoop</span><br>  
        <span itemprop="locality">Denver</span>, <span itemprop="region">CO</span><br>
      </span>
    </address>
  </div>

  <div class="col-md-8" itemprop="description">
    <p><b>6 pm : "Developing Offline Applications with HTML 5" by Venkat Subramaniam</b></p> 
    <p><b>7 pm: Dinner and Networking</b></p> 
    <p><b>7:30 pm: "HTML 5 Animations - building true richness on the web" by Venkat Subramaniam</b></p>
  </div>
</div>

肯定需要大量工作,最终才能在浏览器中得到与之前完全相同的显示结果,是不是?但值得高兴的是,由于您费力添加的所有语义数据,您的网站上升到了搜索结果的最前面。

我们不会逐行地详细解释清单 4,我仅指出一些重要的地方。

清单 1 中的 Meetup.com 示例一样,我们向一个没有显示的元素添加了eventType(因为它仅用于 SEO):

<span itemprop="eventType" 
      style="display:none;">Meetup</span>

接下来,向活动添加一个日期以供人和机器使用:

<time class="event-date" 
      itemprop="startDate" 
      datetime="2014-09-22T18:00-06:00">Monday, September 22, 2014</time>

作为人类,您可立即分析字符串 Monday, September 22, 2014,认识到它是一个日期。您还能够将 9/22/2014 和2014-09-22 识别为同一个日期。但计算机更加注重字面内容,像这样的细小的格式更改可能导致重大的故障。在这个示例中,您执行了多处更改来消除歧义:

  • 您将 HTML 元素从通用的 <div> 升级为更具体的 <time>(一个新的 HTML5元素)。
  • CSS event-date 类识别数据的内容,而不是外观。* itemprop="startDate" 微数据属性将此日期识别为 eventstartDate。* datetime 属性(HTML5 <time> 元素的一部分)清晰地表明该时间为 ISO 8601格式。这样,既可提供一种机器可使用的时间,又可提供一种用于显示和人类使用的格式良好的时间。

对 HTML 的最大更改包括活动的地址,如清单 5 所示。您在 Event 模式内嵌套了多种新模式—Organization and Address

清单 5. 添加组织和地址的微数据

<h4>Where</h4>
<address itemprop="location" 
         itemscope 
         itemtype="http://data-vocabulary.org/?Organization">
    <span itemprop="name">Rally Software</span><br>
    <span itemprop="address" 
          itemscope 
          itemtype="http://data-vocabulary.org/Address">
        <span itemprop="street-address">1550 Wynkoop</span><br>  
        <span itemprop="locality">Denver</span>, <span itemprop="region">CO</span><br>
    </span>
</address>

让您的浏览器能感知微数据

现在您已向网站添加一些微数据,如何确定您已正确添加?最简单的方法是为浏览器使用一种微数据扩展。在多种非官方的微数据扩展中,我喜欢在 Chrome 中使用的是 Semantic inspector

安装之后,此浏览器扩展通常会保持隐藏,直到您访问一个使用微数据的网站。Semantic inspector 在当前网页中找到微数据时,它会在地址栏显示一个红色的 m图标。您可能对这个小图标弹出的频率很吃惊;您会在许多流行、主流的网站上看到它,包括 Google、Time.com 和 Walmart.com 等。单击该图标可显示详细信息,如图 5
所示:

图 5. 使用 Semantic inspector 查看微数据

现在您已有标注了微数据属性的基本的 HTML 线框图,是时候从这些连线中填入全新的 JSON 了。为此,创建一个新的事件模块,其中包含控制器、视图、模型和服务。

创建一个 AngularJS 模块

上一期 中,您使用 Yeoman 搭建了一个完整的 CRUD 模块,包括 Express 路由和一个 Mongoose模型。对于此用户案例,不需要服务器端基础架构,因为原始 JSON 数据来自外部 Web 应用程序。幸运的是,MeanJS Yeoman生成器的创建者预见到了这一需求,单单为应用程序的客户端 AngularJS 部分提供了另一个生成器。

键入 yo meanjs:angular-module events 来创建一个名为 events 的新 AngularJS模块。AngularJS 模块是特定于您应用程序中一种特定数据类型的文件的逻辑分组。根据官方 AngularJS 文档的描述,“可将模块视为您应用程序的不同部分(控制器、服务、过滤器、指令等)的容器。”

收到提示时,选择列表中的所有元素,如清单 6 所示。

清单 6. 生成一个模块

[?] Which folders would you like your module to include? 
 ? config
 ? controllers
 ? css
 ? directives
 ? filters
 ? img
 ? services
 ? tests
 ? views

 create public/modules/events/events.client.module.js

根据刚才的描述,您的模块只是一个空目录集合。您不会在编写的每个 AngularJS
模块中使用每个目录,但知道有一个容易记住、容易理解的地方来在时机成熟时放入模块的各部分,也很不错。

下一步是向模块添加一个控制器。

创建一个 AngularJS 控制器

搭建一个模块后,搭建一个控制器也非常容易。键入 yo meanjs:angular-controller events 并选择
events 模块,如清单 7 所示。

清单 7. 生成一个控制器

[?] Which module does this controller belongs to? 
  articles 
  core 
? events 
  talks 
  users 

create public/modules/events/controllers/events.client.controller.js
create public/modules/events/tests/events.client.controller.test.js

可以看到,Yeoman 生成器将该控制器放在 controllers 目录中,将关联的测试放在您指定的模块的 tests 目录中。

此刻要问的一个合理的问题是 “为什么我创建了一个控制器,为什么我应该关注它?”回想一下,AngularJS 是一个客户端 模型-视图-控制器(MVC) 框架。您最终将得到一个视图(在本教程前面创建的 HTML <div> 元素),其中填入了模型数据(一个填入了来自Meetup.com 的活动数据的 JSON 结构)。该视图如何访问该模型?控制器的工作是将各部分集合起来,为视图提供它需要的模型数据。

以下这个简单示例演示了 MVC 的各部分如何融合在一起。在文本编辑器中打开
public/modules/events/controllers/events.client.controller.js,如清单 8 所示。

清单 8. 一个空的、无存根的 AngularJS控制器

'use strict';

angular.module('events').controller('EventsController', ['$scope',
  function($scope) {
    // Events controller logic
    // ...
  }
]);

稍后,我们会将此控制器绑定到一个特定的 DOM 元素。$scope 变量将负责将模型传递给视图的重要工作。

$scope 添加一个 title 变量(模型),如清单 9 所示。

清单 9. 将一个 $scope 变量添加到 AngularJS控制器

'use strict';

angular.module('events').controller('EventsController', ['$scope',
  function($scope) {
    $scope.title = 'High Performance WebSocket';

  }
]);

接下来,将 EventsController 添加到 public/modules/core/views/home.client.view.html 中的
Event DOM 元素(视图)中:

<div class="row center-block event" 
       itemscope 
       itemtype="http://data-vocabulary.org/Event"
       ng-controller="EventsController">

您可能已猜到,此代码将控制器绑定到 DOM 元素。$scope 变量仅对此 <div>和它的子元素有效。如果愿意,可将一个控制器绑定到许多不同的 DOM 元素。每个元素会获得一个新控制器的唯一实例和它自己的唯一 $scope

接下来,向您的线框 HTML 添加一个 {{title}} 占位符以取代硬编码的文本:

<h3 class="event-title"><a href="http://www.meetup.com/HTML5-Denver-Users-Group/events/160326502/" 
itemprop="url"><span itemprop="summary">{{title}}</span></a></h3>

在 Web 浏览器中查看结果时,您应看到 {{title}} 占位符替换为了通过 EventsController提供的文本,如图 6 所示。

图 6. 占位符替换为了实际文本

现在您已有一个简单、有效的示例,是时候详细分析分析它了。(换句话说,您的应用程序已能正常工作,是时候再次破坏它了。)如果愿意,可向 $scope添加许多变量,这些变量可以是简单的单个值,或者完整的 JSON 对象。

很快,您将会看到如何向 Meetup.com 发出一个 HTTP 请求,以检索下一个即将举办的活动的 JSON。到那时,在 $scope中添加一些简化的模拟数据,以模拟您将从实际的 Ajax 调用获取的数据,如清单 10 所示。

清单 10. 模拟的 JSON 响应

'use strict';

angular.module('events').controller('EventsController', ['$scope',
  function($scope) {
        $scope.title = 'High Performance WebSocket';
        $scope.event = {
          'name': '"Developing Offline Applications" and "HTML 5 Animations"',
          'time': 1411430400000,
          'event_url': 'http://www.meetup.com/HTML5-Denver-Users-Group/events/160326502/',
          'description': '<p><b>6 pm : "Developing Offline 
          Applications with HTML 5" by Venkat Subramaniam</b></p>',
          'venue': {
            'name': 'Rally Software',
            'address_1': '1550 Wynkoop',
            'city': 'Denver',
            'state': 'CO',
          }      
       }
    }
]);

可以看到,$scope.event 变量包含一个复杂、嵌套的 JSON 对象。编辑您的视图来利用这个新模型数据,如清单 11 所示。

清单 11. 向 HTML 添加更多占位符

<h3 class="event-title"><a href="{{event.event_url}}" itemprop="url"><span 
itemprop="summary">{{event.name}}</span></a></h3>
<div class="col-md-4">
  <h4>When</h4>
  <p>{{event.time}}</p>
  <h4>Where</h4>
  <address itemprop="location" 
           itemscope 
           itemtype="http://data-vocabulary.org/Organization">
    <span itemprop="name">{{event.venue.name}}</span><br>
    <span itemprop="address" 
          itemscope 
          itemtype="http://data-vocabulary.org/Address">
      <span itemprop="street-address">{{event.venue.address_1}}</span><br>   
      <span itemprop="locality">{{event.venue.city}}</span>, 
      <span itemprop="region">{{event.venue.state}}</span><br>
    </span>
  </address>
</div>

<div class="col-md-8" itemprop="description">
  {{event.description}}
</div>

在 Web 浏览器中查看结果时,只要向模板视图添加了占位符,就应显示来自 $scope.event 的值,如图 7 所示。

图 7. 包含模拟的 JSON 数据的 UGLI 主页

创建 AngularJS 服务来获取实际、实时的数据之前,必须完成两个与视图相关的简单任务:添加一些 AngularJS 过滤器来格式化日期,并在{{event.description}} 占位符中显示所呈现的 HTML — 而不是原始的、转义的 HTML 代码。 —

添加 AngularJS 过滤器

AngularJS 过滤器(与相机或 Instagram
滤镜很像)可改变数据的外观。向视图添加过滤器,因为它们会影响模型数据的外观,而不更改内容本身。

通过在数据元素后添加一个竖线 (|) 和一个过滤器名称,如 {{product_code | uppercase}}
所示,可向模板占位符应用一个过滤器。AngularJS 提供了许多内置的过滤器,包括
uppercaselowercasecurrency
number。您甚至可编写自己的自定义过滤器。

我一直使用的一个过滤器是 date过滤器,它使您能够使用自定义模式来格式化日期值的外观。

例如,向之前创建的 time 元素应用一个 date 过滤器:

<time class="event-date" 
  itemprop="startDate" 
  datetime="{{event.time | date:'yyyy-MM-ddTHH:mm:ss:Z'}}">{{event.time | 
  date:'EEEE, MMMM, d, yyyy'}}</time>

可以注意到,您为两个不同的过滤器使用了同一个 event.time 字段。EEEE 代码显示星期几的完整英文,比如Monday。EEE 代码将星期几缩写为 Mon;EE 缩写为 Mo;E 缩写为
M。M 代码同样适用于月份名称。d 代码适用于一月中的某一天,y 代码适用于年。

event.time 字段会在主页上出现多次。更改 When 的外观以显示小时和 AM/PM 后缀:

<h4>When</h4>
<p>{{event.time | date:'h a'}}</p>

MEAN 应用程序中对 AngularJS 过滤器的大量使用,突出了 MVC 设计模式的一个重要原则:模型数据应与任何视图内容独立。

有了正确的 event.time 格式之后,仅剩下 event.description 外观需要修复了。为此,必须让AngularJS 知道它可安全地显示此字段的未转义 HTML。

显示未转义的 HTML

目前为止使用的所有 JSON 数据都是纯数据,即没有嵌套的 HTML 元素。event.description 字段是一个例外。

任何时候您从外部来源收到 HTML(无论是否值得信任)时,都面临着一种潜在的安全风险。包含的 HTML 可带来不想要的 JavaScript库,它们可能向其他网站公开您的数据。

为了防御此风险,AngularJS 会自动清理模板化数据,将它遇到的任何 HTML 元素进行转义,将 “真实的” 尖括号替换为转义的等效表示&gt;&lt;。此行为不是像前一节中看到的那样的显式过滤,但是一种类似的理念。

对于 event.description 字段,必须告诉 AngularJS,一起显示外部 HTML 和本地 HTML是没有问题的。为此,调整您的模板,删除 {{event.description}} 占位符并将它替换为 ng-bind-html 属性:

<div class="col-md-8" itemprop="description" ng-bind-html="event.description"></div>

在浏览器中查看主页时,可见的、转义的 <b><p> HTML元素应消失,取而代之的是呈现的文本。

有了控制器、模型和视图,还剩最后一步:将控制器中的模拟 JSON 内容替换为从 Ajax 请求返回的实时数据。要执行这一步,需要向模块添加另一个元素:一个服务

创建一个服务

使用了一个 AngularJS 服务 来发出 Ajax请求,这是与外部 Meetup.com API 进行交互的完美解决方案。

您可能想要直接从控制器发出 Ajax请求,但这么做有点目光短浅。如果其他控制器中需要该活动数据,该怎么办?您肯定不想在控制器之间复制和粘贴源代码,对吧?为了方便跨多个控制器共享相同数据,可创建一个服务。

键入 yo meanjs:angular-service events 来创建一个 events 服务,如清单 12所示。在提示时选择 events 模块。

清单 12. 生成一个 AngularJS 服务

$ yo meanjs:angular-service events
[?] Which module does this service belongs to? 
  articles 
  core 
events 

  talks 
  users 

create public/modules/events/services/events.client.service.js

AngularJS 提供了一个名为 $http 的预构建服务来发出 HTTP/Ajax 请求。(所有 AngularJS 服务都有一个$ 前缀。)要使用 $http,需将它注入到您的服务中。在 events 服务能正常运行后,将它注入到EventsController 中。(AngularJS 到处都使用了依赖性注入。)

还记得您在 EventsController 中使用的 $scope 对象吗?$scope是一个注入到控制器中的服务。如清单 13 所示,注入 $scope 服务的方式是,声明它,然后将它以参数形式传递给函数。

清单 13. 注入 $scope 服务

'use strict';

angular.module('events').controller('EventsController', ['$scope',
  function($scope) {
    $scope.title = 'High Performance WebSocket';
  }
]);

两次键入服务名称似乎有点多余,但这么做方便了在准备将 MEAN 应用程序部署到生产环境中时发生的精简和串联过程。

您已看到如何注入一个服务,是时候应用这一知识了。打开 public/modules/events/services/events.client.service.js,如清单 14
所示。

清单 14. public/modules/events/services/events.client.service.js

'use strict';

angular.module('events').factory('Events', [
    function() {
        // Events service logic

        // ...

        // Public API
        return {
            someMethod: function() {
                return true;
            }
        };
    }
]);

注入 $http 服务,如清单 15 所示。

清单 15. 注入 $http 服务

angular.module('events').factory('Events', ['$http',
        function($http) {
            // Events service logic
            // ...

            // Public API
            return {
                someMethod: function() {
                    return true;
                }
            };
        }
    ]);

接下来,将 someMethod 更改为 getNextEvent 并删除一些基本功能的存根,如清单 16 所示。

清单 16. 从 Ajax 调用返回 JSON

'use strict';

angular.module('events').factory('Events', ['$http',
  function($http) {
    // Public API
    return {
      getNextEvent: function() {
        var url = 'http://api.meetup.com/2/events?status=upcoming&order=
        time&limited_events=False&group_urlname=HTML5-Denver-Users-Group&desc=
        false&offset=0&photo-host=public&format=json&page=1&fields=
        &sig_id=13848777&sig=7aa5d53f450ee5449945e8ee89b8cba8968d9e30&callback=JSON_CALLBACK';

        var request = $http.jsonp(url);
        return request;
      }
    };
  }
]);

(详细的)URL 将返回 HTML5 Denver User Group 的下一场即将举办的活动。(Meetup.com 提供了一个很好的 沙箱 来使用其 API。)如果将该 URL
复制到您的浏览器中,您将获得完整的 JSON 响应。为清楚起见,我编辑了该响应,如清单 17 所示。

清单 17. 来自 Meetup.com 的 API 的 JSON 响应

{
  "results": [
    {
      "status": "upcoming",
      "visibility": "public",
      "venue": {
        "id": 21506832,
        "name": "Rally Software",
        "state": "CO",
        "address_1": "1550 Wynkoop",
        "city": "Denver"
      },
      "id": "160326502",
      "time": 1411430400000,
      "event_url": "http:\/\/www.meetup.com\/HTML5-Denver-Users-Group\/events\/160326502\/",
      "description": "<p><b>6 pm : \"Developing Offline Applications with HTML 5\" 
      by Venkat Subramaniam<\/b><\/p> ",
      "name": "\"Developing Offline Applications\" and \"HTML 5 Animations\""
    }
  ],
  "meta": {
    "count": 1,
    "total_count": 3,
    "next": "http:\/\/api.meetup.com\/2\/events?status=upcoming&sig_id=13848777&
    order=time&limited_events=False&group_urlname=HTML5-Denver-Users-Group&
    desc=false&sig=7aa5d53f450ee5449945e8ee89b8cba8968d9e30&photo-host=public&offset=1&
    format=json&page=1&fields="
  }
}

结果数组中的 JSON 对象看起来应很熟悉。您删除了 EventsController 中的类似数据。但请注意,完整的 JSON响应包含其他对在主页上呈现数据没有必要的信息(比如 meta)。幸运的是,在传递 JSON 响应之前可对它执行转换。将转换逻辑添加到
events 服务,如清单 18 所示。

清单 18. 转换 JSON 响应

    // Public API
    return {
      getNextEvent: function() {
        var url = 'http://api.meetup.com/2/events?status=upcoming&order=
        time&limited_events=False&group_urlname=HTML5-Denver-Users-Group&desc=
        false&offset=0&photo-host=public&format=json&page=1&fields=
        &sig_id=13848777&sig=7aa5d53f450ee5449945e8ee89b8cba8968d9e30&callback=JSON_CALLBACK';

        var returnFirstElement = function (data, headers) {
                    return data.results[0];
                };

        var request = $http.jsonp(url, {transformResponse: returnFirstElement});
        return request;
      }
    };
  }

]);

有了转换逻辑,JSON 将仅包含来自结果数组的第一个元素。所有其他额外的 JSON 信息都会丢弃。

为了在开发过程中提供帮助,可添加 successerror 处理函数,如清单 19所示。此代码会将响应数据记录到控制台。您可自由地定义此代码,或者完全忽略它。

清单 19. 添加 successerror 处理函数

    // Public API
    return {
      getNextEvent: function() {
        var url = 'http://api.meetup.com/2/events?status=upcoming&order=
        time&limited_events=False&group_urlname=HTML5-Denver-Users-Group&desc=
        false&offset=0&photo-host=public&format=json&page=1&fields=
        &sig_id=13848777&sig=7aa5d53f450ee5449945e8ee89b8cba8968d9e30&callback=JSON_CALLBACK';


        var returnFirstElement = function (data, headers) {
                    return data.results[0];
                };

        var request = $http.jsonp(url, {transformResponse: returnFirstElement});

        request.success(function(data, status, headers, config) {
            console.log('SUCCESS');
            console.log(data);
        });
        request.error(function(data, status, headers, config) {
            console.log('ERROR');
            console.log(data);
        });

        return request;
      }
    };
  }
]);

现在 events 服务已完成,可以将它注入到 EventsController 中。修改EventsController,如清单 20 所示。

清单 20. 将 events 服务注入到EventsController

'use strict';

angular.module('events').controller('EventsController', ['$scope', 'Events',
  function($scope, Events) {
        $scope.event = undefined;

        Events.getNextEvent().success(function(data){
          $scope.event = data;          
        });
    }
]);

如果所有功能都按预期运行,您应在主页上看到一段完整的活动描述,如图 8 所示。如果在演讲描述中看到了比之前模拟的更多的细节,就会知道一切正常。

图 8. 完整、有效的示例的实际应用

隐藏闪烁的无样式内容

在主页首次呈现到它向 Meetup.com 发出 Ajax 请求的时间间隔里,您可能注意到了讨厌的 FOUC(Flash of Unstyled Content,无样式内容闪烁)。如果没有看到,刷新浏览器两次,就应该会看到。

FOUC 不是特别重大的错误,但它们无疑会使您的应用程序看起来不太专业。所幸,AngularJS 开发人员为这个常见问题提供了一个简洁的解决方案。

使用 ng-show 对 home.client.view.html 执行最后一次更改,以隐藏视图,直到模型数据就位:

<div class="row center-block event" 
     itemscope 
     itemtype="http://data-vocabulary.org/Event"
     ng-controller="EventsController"     ng-show="event">

ng-show 属性添加到 <div> 中,会导致整个 <div>隐藏,直到填充了 $scope.event 变量。对 Meetup.com 的 Ajax 请求返回 JSON(模型)时,将显示
<div>(视图)。

结束语

UGLI 应用程序真正开始成形了。您从外部 API 拉入了 JSON 数据,并使用微数据格式化了得到的视图,以便搜索引擎和其他自动化流程可与查看网页的人访问到相同的信息。

下载

示例代码 wa-mean4.zip

全栈开发,精通MEAN系列前3篇文章:

【全栈开发】精通 MEAN: MEAN 堆栈
【全栈开发】精通 MEAN: 了解一个 MEAN 应用程序
【全栈开发】精通 MEAN: 使用 MEAN 和 UGLI CRUD 实现响应式 Web 设计

原文出处:精通 MEAN: 当 MEAN 遇到 Meetup.com 和微数据