如何使用Angular、Bootstrap和APIXUAPI构建天气应用程序

来自菜鸟教程
跳转至:导航、​搜索

作为 Write for DOnations 计划的一部分,作者选择了 NPower 来接受捐赠。

介绍

Angular 是由 Google 构建的前端 Web 框架。 它允许开发人员围绕 model-view-controller (MVC) 或 model-view-viewmodel (MVVM) 软件架构模式构建单页应用程序。 这种架构将应用程序划分为不同但相互连接的部分,从而允许并行开发。 按照这种模式,Angular 将其不同的组件拆分为 Web 应用程序的各个部分。 它的组件管理与该组件相关的数据和逻辑,在其各自的视图中显示数据,并根据从应用程序的其余部分接收到的不同消息来调整或控制视图。

Bootstrap 是一个前端库,可帮助开发人员快速有效地构建响应式网站(适应不同设备的网站)。 它利用网格系统将每个页面分为十二列,以确保无论在何种设备上查看页面,页面都保持正确的大小和比例。

APIXU 通过 API 向用户提供全球天气数据。 使用 APIXU,用户可以检索世界上任何位置的最新天气和未来天气预报。

在本教程中,您将使用 Angular、Bootstrap 和 APIXU API 创建一个天气应用程序。 您将能够在搜索表单中输入一个位置,并在提交该表单后,查看您的应用程序中显示的该位置的当前天气详细信息。 本教程中使用的 Angular 版本是 7.2.0,使用的 Bootstrap 版本是 4.2.1。

先决条件

在开始本教程之前,您需要以下内容:

第 1 步 — 安装 Angular

在开始创建应用程序之前,您需要安装 Angular。 打开您的终端并运行以下命令以在您的机器上全局安装 Angular CLI:

npm install -g @angular/cli

Angular CLI 是 Angular 的命令行界面。 它是创建新 Angular 项目以及构成 Angular 项目的不同子元素的主要方式。 使用 -g 参数将全局安装它。

片刻之后,您将看到以下输出:

安装 Angular 的输出

...
+ @angular/cli@7.2.2
...

您现在已经在本地机器上安装了 Angular。 接下来,您将创建 Angular 应用程序。

第 2 步——创建你的 Angular 应用程序

在这一步中,您将创建和配置新的 Angular 应用程序,安装其所有依赖项,例如 Bootstrap 和 jQuery,然后最后检查默认应用程序是否按预期工作。

首先,使用 ng 命令创建一个 Angular 应用程序,您可以从终端运行它。

注意: 如果您在 Windows 上,即使您已正确安装 Node.js 和 npm,尝试从命令提示符运行 ng 命令也可能会遇到问题。 例如,您可能会收到如下错误:ng is not recognized as an internal or external command。 为了解决这个问题,请在 Windows 上 Node.js 文件夹中已安装的 Node.js 命令提示符中运行 ng 命令。


ng 命令是使用 Angular 从命令行运行任何操作的先决条件。 例如,无论您是在构建新项目、创建组件还是创建测试,都可以在每个所需功能的前面加上 ng 命令。 在本教程中,您将要创建一个新应用程序; 您将通过执行 ng new 命令来实现此目的。 ng new 命令创建一个新的 Angular 应用程序,导入必要的库,并创建应用程序所需的所有默认代码脚手架。

首先创建一个新应用程序,在本教程中它将被称为 weather-app,但您可以根据需要更改名称:

ng new weather-app

ng new 命令将提示您输入有关要添加到新应用程序中的功能的附加信息。

OutputWould you like to add Angular routing? (y/N)

Angular routing 允许您使用路由和组件构建具有不同视图的单页应用程序。 继续输入 y 或点击 ENTER 接受默认值。

OutputWhich stylesheet format would you like to use? (Use arrow keys)

点击 ENTER 接受默认的 CSS 选项。

该应用程序将继续其创建过程,稍后您将看到以下消息:

Output...
CREATE weather-app/e2e/src/app.e2e-spec.ts (623 bytes)
CREATE weather-app/e2e/src/app.po.ts (204 bytes)
...
Successfully initialized git.

接下来,在您的文本编辑器中,打开 weather-app 文件夹。

查看目录的结构,您会看到几个不同的文件夹和文件。 您可以在 此处 阅读所有这些文件的功能的完整说明,但出于本教程的目的,这些是需要理解的最重要的文件:

  • package.json 文件。 它位于根 weather-app 文件夹中,它的执行方式与任何其他 Node.js 应用程序一样,包含您的应用程序将使用的所有库、应用程序的名称、测试时要运行的命令等等。 首先,该文件包含有关 Angular 应用程序正常运行所需的外部库的详细信息。
  • app.module.ts 文件。 该文件位于 weather-app/src 文件夹中的 app 文件夹中,它告诉 Angular 如何组装您的应用程序,并保存有关应用程序中组件、模块和提供程序的详细信息。 您的 imports 数组中已经有一个导入的模块 BrowserModuleBrowserModule 为您的应用程序提供必要的服务和指令,并且应该始终是您的 imports 数组中的第一个导入模块。
  • angular.json 文件。 位于应用程序的根 weather-app 文件夹中,这是 Angular CLI 的配置文件。 该文件包含您的 Angular 应用程序需要运行的内部配置设置。 它为您的整个应用程序设置默认值,并具有诸如测试时使用的配置文件、应用程序中使用的全局样式或将构建文件输出到哪个文件夹等选项。 您可以在官方 Angular-CLI 文档 中找到有关这些选项的更多信息。

您可以暂时保留所有这些文件,因为接下来您将安装 Bootstrap。

Bootstrap 有两个依赖项需要安装才能在 Angular 中正常工作 - jQuerypopper.jsjQuery 是一个专注于客户端脚本的 JavaScript 库,而 popper.js 是一个定位库,主要管理工具提示和弹出框。

在您的终端中,移动到您的根 weather-app 目录:

cd weather-app

然后执行以下命令安装所有依赖项并将引用保存到 package.json 文件:

npm install --save jquery popper.js bootstrap

--save 选项会自动将您的引用导入到 package.json 文件中,这样您就不必在安装后手动添加它们。

您将看到显示已安装版本号的输出,如下所示:

Output+ popper.js@1.14.6
+ bootstrap@4.2.1
+ jquery@3.3.1
...

您现在已经成功安装了 Bootstrap 及其依赖项。 但是,您还需要在应用程序中包含这些库。 您的 weather-app 还不知道它需要这些库,因此您需要将路径添加到 jquerypopper.jsbootstrap.js 和 [ X142X] 到您的 angular.json 文件中。

对于 popper.js,您需要包含的文件是 node_modules/popper.js/dist/umd/popper.js。 jQuery 需要 node_modules/jquery/dist/jquery.slim.js 文件。 最后,对于 Bootstrap,您需要两个文件(JavaScript 文件和 CSS 文件)。 它们分别是 node_modules/bootstrap/dist/js/bootstrap.jsnode_modules/bootstrap/dist/css/bootstrap.css

现在您已拥有所有必需的文件路径,请在文本编辑器中打开 angular.json 文件。 styles 数组用于添加对 CSS 文件的引用,而 scripts 数组将引用所有脚本。 您会在 "options": JSON 对象内的 angular.json 文件顶部附近找到这两个数组。 将以下突出显示的内容添加到文件中:

角.json

...
"options:" {
...
"styles": [
    "node_modules/bootstrap/dist/css/bootstrap.css",
     "src/styles.css"
],
"scripts": [
    "node_modules/jquery/dist/jquery.slim.js",
    "node_modules/popper.js/dist/umd/popper.js",
    "node_modules/bootstrap/dist/js/bootstrap.js"
]},
...

您现在已经导入了 Bootstrap 正常工作所需的主要 .js.css 文件。 您已经从 angular.json 文件中指定了这些文件的相对路径:在样式数组中添加 .css 文件,在 [ 的脚本数组中添加 .js 文件X173X]。 添加此内容后,请确保您已保存 angular.json 文件。

现在,使用 ng serve 命令启动您的应用程序,以检查一切是否正常。 从终端的 weather-app 目录中,运行:

ng serve --o

--o 参数将自动打开一个浏览器窗口,显示您的应用程序。 该应用程序将需要几秒钟的时间来构建,然后将显示在您的浏览器中。

您将在终端中看到以下输出:

Output** Angular Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ **
...

浏览器打开后,您将看到一个默认的 Angular 应用页面。

如果您没有看到这些输出,请再次运行此步骤并确保一切正确。 如果您看到如下错误:Port 4200 is already in use. Use '--port' to specify a different port 那么您可以通过键入以下内容来更改端口号:

ng serve --o --port <different-port-number>

此潜在错误消息的原因是因为您计算机上的端口 4200 正被另一个程序或进程使用。 如果您知道该进程是什么,您可以终止它,或者您可以按照上述步骤指定不同的端口号。

您现在已经设置了应用程序脚手架。 接下来,您将创建一个天气组件,该组件将包含主窗体和搜索位置的相关天气详细信息。

第三步——创建你的天气组件

Angular 应用程序主要由 组件 组成,这些组件是在应用程序中具有特定功能的逻辑片段。 该组件由一些 logic 组成,用于管理应用程序中的部分屏幕——这称为 view

例如,在本教程中,您将创建一个 Weather Component 来负责处理两个任务:

  • 搜索位置
  • 显示该位置的相关天气数据

为了实现第一个目标,您将创建一个允许您搜索位置的表单。 当您单击表单上的搜索按钮时,它将触发一个搜索该位置的功能。

为了实现第二个目标,您将拥有一个带有嵌套 <p> 标记的 <div>,它将整齐地显示您检索到的数据。

当您的应用程序从您的终端窗口运行时,您不能在该特定窗口中输入任何其他内容。 因此,如果要执行其他 ng 命令,请在新的终端窗口中打开 weather-app 目录。 或者,您可以通过按 CTRL + C 停止应用程序在原始终端窗口中运行。 然后您可以安装新组件,然后通过键入 ng serve --o 再次启动应用程序。

执行以下命令将创建您的 Weather Component 并自动将其导入您的 app.module.ts 文件。 请记住,您的 app.module.ts 文件包含有关应用程序中所有组件、模块和提供程序的详细信息。

ng generate component weather

你会看到这样的输出(确切的字节大小可能会有所不同):

OutputCREATE src/app/weather/weather.component.css (0 bytes)
CREATE src/app/weather/weather.component.html (26 bytes)
CREATE src/app/weather/weather.component.spec.ts (635bytes)
CREATE src/app/weather/weather.component.ts (273 bytes)
UPDATE src/app/app.module.ts (400 bytes)
...

此输出显示 Angular 已创建组件所需的四个文件:

  • 供您查看的 .css.html 文件
  • 用于测试组件的 .spec.ts 文件
  • A.component.ts 文件保存组件的功能

Angular 还更新了 src/app/app.module.ts 文件以添加对新创建组件的引用。 您将始终在 src/app/name-of-component 目录下找到组件文件。

现在您已经安装了新组件,返回浏览器查看应用程序。 如果您停止运行应用程序以安装新组件,请键入以下内容重新启动它:

ng serve --o

您会注意到您仍然可以看到“欢迎使用应用程序!” (默认组件)显示在页面上。 您看不到新创建的组件。 在下一节中,您将对此进行更改,以便每当您转到 localhost:4200 时,您将访问新创建的天气组件,而不是 Angular 的默认组件。

第 4 步 - 访问您的天气组件

在标准 HTML 中,每当您想创建一个新页面时,您都会创建一个新的 .html 文件。 例如,如果您已经有一个预先存在的 HTML 页面,您希望从该页面导航到新创建的页面,那么您将有一个带有 anchor 标记的 href 属性指向该页面新页面。 例如:

预先存在的.html

<a href="/newpage.html">Go to New Page</a>

然而,在 Angular 中,它的工作方式略有不同。 您不能以这种方式使用 href 属性导航到新组件。 当你想链接到一个组件时,你需要使用 Angular 的 Router 库并在一个文件中声明一个所需的 URL 路径,该文件将直接映射到一个组件。

在 Angular 中,您将此文件称为 routes.ts。 这包含您的路线(链接)的所有详细信息。 为使该文件正常工作,您将从 @angular/router 库中导入 Routes 类型,并将所需链接列出为 Routes 类型。 这将向 Angular 传达这些是在您的应用程序中导航的路线列表。

在文本编辑器中创建文件 routes.ts 并将其保存在 src/app 目录中。 接下来,将以下内容添加到 routes.ts 文件中:

src/app/routes.ts

import { Routes } from '@angular/router'

现在,在 src/app/routes.ts 中声明 URL 路径和组件。 您希望使您的应用程序在您转到主页 (http://localhost:4200) 时访问新创建的天气组件。 将这些行添加到文件中,这会将根 URL 映射到您刚刚创建的天气组件:

src/app/routes.ts

import { Routes } from '@angular/router'
import { WeatherComponent } from './weather/weather.component';

export const allAppRoutes: Routes = [
  { path: '', component: WeatherComponent }
];

您已经导入了 WeatherComponent,然后创建了一个变量 allAppRoutes,它是一个类型为 Routes 的数组。 allAppRoutes 数组包含路由定义对象,每个对象都包含一个 URL 路径和要映射到的组件。 您已指定无论何时访问根 URL (),它都应导航到 WeatherComponent

您最终的 routes.ts 文件将如下所示:

src/app/routes.ts

import { Routes } from "@angular/router";
import { WeatherComponent } from "./weather/weather.component";

export const allAppRoutes: Routes = [
  { path: '', component: WeatherComponent }
];

您现在需要将这些路由添加到您的主 app.module.ts 文件中。 您需要将刚刚创建的数组 — allAppRoutes — 传递到一个名为 RouterModule 的 Angular 模块中。 RouterModule 将初始化和配置路由器(负责执行所有应用程序导航)并为其提供来自 allAppRoutes 的路由数据。 添加以下突出显示的内容:

src/app/app.module.ts

...
import {WeatherComponent} from './weather/weather.component';
import {RouterModule} from '@angular/router';
import {allAppRoutes} from './routes';
...
@NgModule({
    declarations:[
      ...
    ],
    imports: [
        BrowserModule,
        RouterModule.forRoot(allAppRoutes)
    ]
    ...
})
...

在此文件中,您已导入路线对象的 RouterModuleallAppRoutes 数组。 然后,您将 allAppRoutes 数组传递到 RouterModule 中,以便您的路由器知道将您的 URL 路由到哪里。

最后,您需要启用路由本身。 打开 app.component.ts 文件。 有一个 templateUrl 属性指定该特定组件的 HTML:./app.component.html。 打开这个文件,src/app/app.component.html,你会看到它包含了你的 localhost:4200 页面的所有 HTML。

删除 app.component.html 中包含的所有 HTML 并将其替换为:

src/app/app.component.html

<router-outlet></router-outlet>

router-outlet 标记激活路由并将用户在浏览器中键入的 URL 与您之前在 allAppRoutes 变量下的 routes.ts 文件中创建的路由定义相匹配。 然后路由器以 HTML 格式显示视图。 在本教程中,您将在 <router-outlet></router-outlet> 标记之后直接显示 weather.component.html 代码。

现在,如果您导航到 http://localhost:4200,您将看到 weather works! 出现在您的页面上。

您已经在应用程序中设置了路由。 接下来,您将创建表单和详细信息部分,使您能够搜索位置并显示其相关详细信息。

第五步——定义用户界面

您将使用 Bootstrap 作为应用程序视图的脚手架。 Bootstrap 对于创建适用于任何设备(移动设备、平板电脑或台式机)的现成响应式网站非常有用。 它通过将网页上的每一行视为十二列宽来实现这一点。 在网页上,一行只是从页面一端到另一端的一条线。 这意味着每一页的内容都必须包含在该行中,并且必须等于十二列。 如果它不等于十二列,它将被下推到另一行。 例如,在 Bootstrap 的网格系统中,将有一个 12 列的行分成两部分,每列六列,接下来的 12 列行分成三部分,每列四列。

Bootstrap 文档 中,您可以阅读有关此网格系统的更多信息。

您将把页面分成两部分,每列六列,左列包含搜索表单,右列显示天气详细信息。

打开 src/app/weather/weather.component.html 以访问您的 WeatherComponent HTML 代码。 删除文件中当前的段落,然后添加以下代码:

src/app/weather/weather.component.html

<div class="container">
  <div class="row">
    <div class="col-md-6"><h3 class="text-center">Search for Weather:</h3></div>
    <div class="col-md-6"><h3 class="text-center">Weather Details:</h3></div>
  </div>
</div>

您创建了一个具有 container 类的 <div> 来保存您的所有内容。 然后,您创建了一行,将其分成两部分,每部分六列。 左侧将保存您的搜索表单,右侧将保存您的天气数据。

接下来,要构建表单,您将在第一个 col-md-6 列中工作。 您还将添加一个按钮,将您在表单中输入的内容提交给 APIXU,然后 APIXU 将返回请求的天气详细信息。 为此,请识别第一个 col-md-6 类,并在 <h3> 标记下添加以下突出显示的内容:

src/app/weather/weather.component.html

...
<div class="col-md-6">
  <h3 class="text-center">Search for Weather:</h3>
  <form>
    <div class="form-group">
      <input
        class="form-control"
        type="text"
        id="weatherLocation"
        aria-describedby="weatherLocation"
        placeholder="Please input a Location"
      />
     </div>
     <div class="text-center"> 
      <button type="submit" class="btn btn-success btn-md">
        Search for the weather</button>
     </div>
   </form> 
</div>
...

您已经添加了表单并添加了包含搜索栏的 form-group 类。 您还创建了用于搜索天气的按钮。 在您的浏览器中,您的天气应用页面将如下所示:

这看起来有点紧凑,因此您可以添加一些 CSS 以便以更好的间距设置页面样式。 Bootstrap 的主要优点是它带有间距类,您可以将其添加到 HTML 中,而无需自己编写任何额外的 CSS。 但是,如果您想合并 Bootstrap 的标准类未涵盖的任何额外 CSS,您可以根据需要编写自己的 CSS。 对于本教程,您将使用 Bootstrap 的标准类。

对于每个 <h3> 标签,您将添加 .my-4 Boostrap CSS 类。 m 设置元素的边距,y 设置元素的 margin-topmargin-bottom,最后 4 指定数量要添加的边距。 您可以在 此处 找到有关不同间距类型和大小的更多详细信息。 在您的 weather.component.html 文件中,添加以下突出显示的内容以替换当前的 <h3> 标签:

src/app/weather/weather.component.html

<div class="col-md-6">
  <h3 class="text-center my-4">Search for Weather:</h3>
  <form>
    <div class="form-group">
      <input
        class="form-control"
        type="text"
        id="weatherLocation"
        aria-describedby="weatherLocation"
        placeholder="Please input a Location"
      />
    </div>
    <div class="text-center">
      <button type="submit" class="btn btn-success btn-md">
        Search for the weather
      </button>
    </div>
  </form>
</div>
<div class="col-md-6">
  <h3 class="text-center my-4">Weather Details:</h3>
</div>

在浏览器中重新加载页面,您会发现间距更大。

您已经创建了表单以及要显示从 APIXU API 收到的信息的部分。 接下来,您将连接您的表单,以便能够正确输入您的位置。

第 6 步 - 连接表单

在 Angular 中,有两种方法可以在应用程序中为用户输入创建表单——reactivetemplate-driven。 尽管它们实现了相同的结果,但每种表单类型处理用户输入数据的方式不同。

使用反应式表单,您可以在 .component.ts 文件中创建表单中不同元素的列表。 然后将它们连接到相应 .component.html 文件中创建的 HTML 表单。 这严格来说是单向的; 也就是说,数据从您的 HTML 流向您的 .component.ts 文件,没有双向数据流。

使用模板驱动的表单,您可以像在普通 HTML 中一样创建表单。 然后,使用诸如 ngModel 之类的指令,您可以从 HTML 创建单向或双向数据绑定,返回到组件中的数据模型,反之亦然。

每种方法都有优点和缺点,但总的来说,反应形式更可取,因为:

  • 灵活地创建各种复杂的形式。
  • 通过检查组件的 .component.ts 文件中每个表单控件的状态来简化单元测试。
  • 能够 订阅 表单中的值。 开发人员可以订阅表单的值流,从而允许他们对实时输入到表单中的值执行一些操作。

尽管有这些优势,但反应形式有时可能更难以实现。 与模板驱动的表单相比,这可能导致开发人员编写更多的代码。 要全面了解表单类型和最佳用例,Angular 的官方指南 提供了一个很好的起点。 在本教程中,您将使用反应式表单。

要使用反应形式,请打开文件 app.module.ts。 接下来,通过在文件顶部声明导入来导入 ReactiveFormsModule

src/app/app.module.ts

...
import { ReactiveFormsModule } from '@angular/forms';
@NgModule({
    ...
})
...

最后,将 ReactiveFormsModule 添加到您的导入列表中。

src/app/app.module.ts

...
@NgModule({
    ...
    imports: [
        BrowserModule,
        RouterModule.forRoot(allAppRoutes),
        ReactiveFormsModule
    ]
    ...
})
...

添加这些代码后,您的 app.module.ts 将如下所示:

src/app/app.module.ts

import { BrowserModule } from "@angular/platform-browser";
import { NgModule } from "@angular/core";

import { AppComponent } from "./app.component";
import { WeatherComponent } from "./weather/weather.component";
import { RouterModule } from "@angular/router";
import { allAppRoutes } from "./routes";
import { ReactiveFormsModule } from "@angular/forms";

@NgModule({
  declarations: [AppComponent, WeatherComponent],
  imports: [
    BrowserModule,
    RouterModule.forRoot(allAppRoutes),
    ReactiveFormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {}

添加这两行后,打开 weather.component.ts 文件并导入 FormBuilderFormGroup 类。

src/app/weather/weather.component.ts

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';

现在在您的 weather.component.ts 文件中创建一个变量,该变量将引用您的 FormGroup

天气.component.ts

export class WeatherComponent implements OnInit {
   public weatherSearchForm: FormGroup;
   constructor() { }
...

每次您想对表单执行操作时,您都将通过 weatherSearchForm 变量引用它。 您现在将 FormBuilder 导入添加到 constructor 中,以便您可以在组件中使用它。

天气.component.ts

...
public weatherSearchForm: FormGroup;
constructor(private formBuilder: FormBuilder) {}
...

通过将 formBuilder 添加到 constructor,它会创建 FormBuilder 类的实例,允许您在组件中使用它。

您现在已准备好在 weather.component.ts 文件中创建 FormGroup 及其各自的值。 如果表单中有多个输入选项,最好将其括在 FormGroup 中。 在本教程中,您将只有一个(您的位置输入),但无论如何您将使用 FormGroup 进行练习。

当您导航到您的组件时,您的表单已准备好使用,这一点很重要。 因为您使用的是响应式表单,所以必须先在表单内创建元素树,然后才能将其绑定到 HTML。 为此,您需要确保在 WeatherComponent 内的 ngOnInit 挂钩中创建表单元素。 ngOnInit 方法在组件初始化时运行一次,在组件准备好使用之前执行您指定需要运行的任何逻辑。

因此,您必须先创建表单,然后才能完成与 HTML 的绑定过程。

WeatherComponent 中,您将在 ngOnInit 挂钩中初始化表单:

src/app/weather/weather.component.ts

...
constructor(private formBuilder: FormBuilder) {}
ngOnInit() {
    this.weatherSearchForm = this.formBuilder.group({
      location: ['']
    });
  }

您已经根据响应式表单样式创建了表单的第一部分:在 weather.component.ts 文件中定义表单组件。 您已经创建了一组表单的复合元素(目前,您有一个元素,location)。 [] 数组允许您为表单输入指定一些额外的选项,例如:用一些数据预先填充它并使用验证器来验证您的输入。 对于本教程,您不需要任何这些,因此您可以将其留空。 您可以在 此处 了解有关可以传递给元素属性的更多信息。

在表格完成之前,您还有两件事要做。 首先打开您的 weather.component.html 文件。 您需要为表单分配一个属性 [formGroup]。 此属性将等于您刚刚在 weather.component.ts 文件中声明的变量:weatherSearchForm。 其次,您必须将 location 元素(在 weather.component.ts 文件中声明)绑定到 HTML。 在weather.component.html中,添加如下高亮内容:

src/app/weather/weather.component.html

...
<form
  [formGroup]="weatherSearchForm" >
  <div class="form-group">
    <input
      class="form-control"
      type="text"
      id="weatherLocation"
      aria-describedby="weatherLocation"
      placeholder="Please input a Location"
    />formControlName="location" />
  </div>
  <div class="text-center">
    <button type="submit" class="btn btn-success btn-md">
      Search for the weather
    </button>
  </div>
</form>
...

您已添加 [formGroup] 属性,将表单绑定到 HTML。 您还添加了 formControlName 属性,该属性声明此特定 input 元素绑定到 weather.component.ts 文件中的 location 元素。

保存文件并返回浏览器,您会看到您的应用看起来完全一样。 这意味着您的表单已正确连接。 如果您在此阶段发现任何错误,请返回之前的步骤以确保文件中的所有内容均正确无误。

接下来,您将连接您的按钮,以便能够接受输入数据到您的表单中。

第 7 步 — 连接您的按钮

在这一步中,您要将搜索按钮连接到表单,以便能够接受用户的输入数据。 您还将为最终将用户输入数据发送到 APIXU 天气 API 的方法创建脚手架。

如果您回顾一下 weather.component.html 中的代码,您会发现您的按钮有一个类型 submit

src/app/weather/weather.component.html

<form>
...
<div class="text-center">
    <button type="submit" class="btn btn-success btn-md">Search for the weather</button>
</div>
</form>

这是一个标准的 HTML 值,它将您的表单值提交给某个函数以执行操作。

在 Angular 中,您在 (ngSubmit) 事件中指定该函数。 当您单击表单中的按钮时,只要它的类型为 submit,它将触发 (ngSubmit) 事件,该事件随后将调用您分配给它的任何方法。 在这种情况下,您希望能够获取用户输入的位置并将其发送到 APIXU API。

您将首先创建一个方法来处理这个问题。 在您的 weather.component.ts 中,创建一个方法 sendToAPIXU(),该方法将采用一个参数:您在表单中输入的值。 将以下突出显示的内容添加到文件中:

src/app/weather/weather.component.ts

...
ngOnInit() {
    this.weatherSearchForm = this.formBuilder.group({
      location: [""]
    });
  }

sendToAPIXU(formValues) {

}
...

接下来,将 ngSubmit 事件添加到您的 HTML 中,并将您提交的表单的值传递给 sendToAPIXU() 方法:

天气.component.html

...
<form [formGroup]="weatherSearchForm" (ngSubmit)="sendToAPIXU(weatherSearchForm.value)">
  ...
</form>
...

您已将 ngSubmit 事件添加到表单中,连接了您在提交表单时要运行的方法,并将 weatherSearchForm 的值作为参数传递给处理程序方法( weatherSearchForm.value)。 您现在可以通过使用 console.log 打印出您的 formValues 来测试此作品,在您的 sendToAPIXU() 方法中,将以下突出显示的内容添加到 weather.component.ts

天气.component.ts

...
sendToAPIXU(formValues){
    console.log(formValues);
}

转到浏览器并通过右键单击网站页面上的任意位置打开控制台,然后单击 Inspect Element。 在弹出的窗口中会出现一个选项卡,名为 Console。 在表单中输入 London。 当您单击 搜索天气 按钮时,您将看到一个包含您所在位置的对象。

您的控制台输出是一个 JSON 对象 {location: "London"}。 如果您想访问您的位置值,您可以通过访问 formValues.location 来实现。 同样,如果您的表单中有任何其他输入,您可以将 .location 替换为您拥有的任何其他元素名称。

注意: 反应形式的所有值都存储在一个对象中——其中的键是您传递给 formBuilder.group({}) 的值的名称。


该按钮现在已连接,可以正确接收输入。 接下来,您将使 sendToAPIXU() 方法向 APIXU API 发出 HTTP 请求。

第 8 步 — 调用 APIXU API

APIXU API 接受位置信息,搜索该位置的当前天气详细信息,并将它们返回给客户端。 您现在将修改您的应用程序,以便它将位置数据发送到 API,获取响应,然后在您的页面上显示结果。

为了在 Angular 中发出 HTTP 请求,您必须导入 HttpClientModule。 打开 src/app/app.module.ts 并添加以下突出显示的行:

src/app/app.module.ts

...
import { ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
@NgModule({
    ...
    imports: [
        BrowserModule,
        RouterModule.forRoot(allAppRoutes),
        ReactiveFormsModule,
        HttpClientModule
    ]
    ...
})
...

接下来,您需要编写代码以对 APIXU API 进行 HTTP 调用。 最佳实践是创建一个 Angular 服务 来发出 HTTP 请求。 关注点分离是您构建的任何应用程序的关键。 一项服务允许您将应用程序发出的所有这些 HTTP 请求移动到一个文件中,然后您可以在您创建的任何 .component.ts 文件中调用该文件。 您可以“合法地”将这些 HTTP 请求写入特定的 .component.ts 文件,但这不是最佳实践。 例如,您可能会发现您的某些请求很复杂,需要您在收到数据后执行一些后处理操作。 您的应用程序中的几个不同组件可能会使用您的一些 HTTP 请求,并且您不希望多次编写相同的方法。

从新的终端窗口或通过在当前终端会话中停止服务器,执行以下命令以创建名为 apixu 的服务:

ng g service apixu

您将看到类似于以下内容的输出:

Outputcreate src/app/apixu.service.spec.ts (328 bytes)
create src/app/apixu.service.ts (134 bytes)
...

该命令创建了服务文件 (apixu.service.ts) 和一个测试文件 (apixu.service.spec.ts)。

您现在需要将此服务作为提供程序添加到您的 app.module.ts 文件中。 这使它可以在您的应用程序中使用。 打开这个文件,首先导入ApixuService

src/app/app.module.ts

...
import { HttpClientModule } "@angular/common/http";
import { ApixuService } from "./apixu.service";
...

接下来将新导入的 ApixuService 作为提供程序添加到 providers 块中:

src/app/app.module.ts 文件

...
@NgModule({
    ...
    providers: [ApixuService],
    ...
})
...

在 Angular 中,如果你想使用你创建的服务,你需要在你的 module.ts 文件中将该服务指定为提供者。 在这种情况下,您已在 app.module.ts 中将其指定为整个应用程序中的提供程序。

最后,打开 src/app/apixu.service.ts 文件。 您将看到创建服务所需的样板代码:首先从 Angular 导入 Injectable 接口; 然后该服务应该与 providedIn 根注入器一起使用(对于整个应用程序); 然后将您的服务的 decorating(这实际上意味着指定)为 @Injectable

src/app/apixu.service.ts

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class ApixuService {

  constructor() { }
}

将服务装饰为 @Injectable 允许您在 weather.component.ts 的构造函数中注入此服务,以便您可以在组件中使用它。

如果您停止了应用程序,请运行以下命令重新启动它:

ng serve --o

如前所述,您的服务需要向 APIXU API 发出 HTTP 请求,并在 app.module.ts 文件中导入 HttpClientModule 以在整个应用程序中发出 HTTP 请求。 您还需要将 HttpClient 库导入 apixu.service.ts 文件,以从 apixu.service.ts 文件本身向 APIXU API 发出 HTTP 请求。 打开apixu.service.ts文件,添加如下高亮内容:

src/app/apixu.service.ts

...
import { HttpClient } from '@angular/common/http';
...

现在你需要编写一个方法,getWeather(),它接受一个参数:位置。 此方法将向 APIXU 发出 API 请求并返回检索到的位置数据。

为此,您在注册 APIXU API 时需要提供的 API 密钥。 如果您登录到 APIXU,您将来到仪表板:

您将看到您的密钥,并在其下方链接到 API URL,其中您的密钥已为 Current WeatherForecast Weather 预填。 复制 Current Weather 详细信息的 HTTPS 链接,它将类似于:

https://api.apixu.com/v1/current.json?key=YOUR_API_KEY&q=Paris

此 URL 将为您提供巴黎当前的天气详细信息。 您希望能够将表单中的 location 传递给 &q= 参数。 因此,在将 URL 添加到 apixu.service.ts 文件时,从 URL 中删除 Paris

src/app/apixu.service.ts

...
export class ApixuService {

  constructor(private http: HttpClient) {}

  getWeather(location){
      return this.http.get(
          'https://api.apixu.com/v1/current.json?key=YOUR_API_KEY&q=' + location
      );
  }
}

注意: 您已经在代码中直接使用了 API 密钥。 在生产环境中,您应该将其安全地存储在服务器端,并以安全的方式检索此密钥并在您的应用程序中使用它。 您可以将其安全地存储在服务器端,或者使用密钥管理应用程序,例如 Hashicorp VaultAzure Key Vault,仅举几例。


您现在已将 HttpClient 导入并注入到构造函数中,以便您可以使用它。 您还创建了一个方法 getWeather(),它采用 location 参数并向您提供的 URL 发出 GET 请求。 您将 &q= 参数留空,因为您将直接从方法中的 location 参数提供此位置。 最后,您已将数据返回给调用该方法的人。

您的服务现已完成。 您需要将您的服务导入您的 WeatherComponent,将其注入您的构造函数以使用它,然后更新您的 sendToAPIXU() 方法以将您的位置发送到您新创建的服务。 打开 weather.component.ts 文件,通过添加突出显示的内容来完成这些任务:

src/app/weather.component.ts

...
import { FormBuilder, FormGroup } from "@angular/forms";
import { ApixuService } from "../apixu.service";
...
constructor(
    private formBuilder: FormBuilder,
    private apixuService: ApixuService
  ) {}
...
ngOnInit(){...}
sendToAPIXU(formValues){
    this.apixuService
      .getWeather(formValues.location)
      .subscribe(data => console.log(data));
}

您已在 sendToAPIXU() 方法中删除了以前的 console.log 语句,并使用此内容对其进行了更新。 您现在正在将您的位置从表单传递到您之前创建的 sendToAPIXU() 方法。 然后,您将该数据传递给 ApixuServicegetWeather() 方法,该方法随后向具有该位置的 API 发出 HTTP 请求。 然后,您订阅了返回的响应,并在本示例中将该数据记录到控制台。 您总是必须在 HTTP 请求上调用 subscribe 方法,因为在您有办法读取返回的 Observable 响应之前,请求不会开始。 Observables 是一种在发布者和订阅者之间发送消息的方式,允许您来回传递任何类型的数据。 在订阅者订阅它之前,您将无法从 observable 接收数据,因为在此之前它不会执行。

再次在浏览器中打开控制台。 现在,输入 London, UK 并单击 搜索天气 。 如果单击选项卡箭头,您将在控制台中看到天气详细信息列表。

输出显示包含所需的所有天气信息的 JSON 对象。 您返回了两个对象:一个 current 对象和一个 location 对象。 前者提供所需的天气详细信息,后者提供有关您所在位置的详细信息。

现在,您的天气数据已成功显示在控制台中。 要完成本教程,您将在 HTML 中显示这些天气详细信息。

第 9 步 — 在您的应用中显示您的天气数据

在控制台中显示结果是检查一切是否正常的一个很好的初始步骤。 但是,您希望最终以 HTML 格式为您的用户显示天气数据。 为此,您将创建一个变量来保存返回的天气数据,然后在 HTML 中使用 interpolation 显示该数据。

插值允许您在视图中显示数据。 为此,它需要您通过 {{ }} 样式绑定属性,以在 HTML 中显示该属性。

打开 weather.component.ts 文件并创建一个名为 weatherData 的变量,您将从 API 检索到的 JSON 数据分配给该变量。 此外,删除之前在 .subscribe() 括号中的代码,并将其替换为以下突出显示的代码:

src/app/weather/weather.component.ts

...
export class WeatherComponent implements OnInit {
public weatherSearchForm: FormGroup;
public weatherData: any;
...
sendToAPIXU(formValues){
    this.apixuService
    .getWeather(formValues.location)
    .subscribe(data => this.weatherData = data)
      console.log(this.weatherData);
    }
}

您已经创建了变量 weatherData 并声明它可以保存 any 类型的数据。 然后,您将从 API 调用返回的数据分配给该变量。 最后,您添加了一个 console.log() 语句来仔细检查 weatherData 是否包含您检索到的所有信息。

您的 weather.component.ts 文件在此阶段应如下所示:

src/app/weather/weather.component.ts

import { Component, OnInit } from "@angular/core";
import { FormBuilder, FormGroup } from "@angular/forms";
import { ApixuService } from "../apixu.service";

@Component({
  selector: "app-weather",
  templateUrl: "./weather.component.html",
  styleUrls: ["./weather.component.css"]
})
export class WeatherComponent implements OnInit {
  public weatherSearchForm: FormGroup;
  public weatherData: any;

  constructor(
    private formBuilder: FormBuilder,
    private apixuService: ApixuService
  ) {}

  ngOnInit() {
    this.weatherSearchForm = this.formBuilder.group({
      location: [""]
    });
  }

  sendToAPIXU(formValues) {
    this.apixuService.getWeather(formValues.location).subscribe(data => {
      this.weatherData = data;
      console.log(this.weatherData);
    });
  }
}

如果您返回并再次搜索 London, UK,您将看到您的对象正常打印到控制台。 现在,您想在 HTML 中显示这些数据。 如果您在控制台中从检索到的天气数据中检查 current 对象,您将看到诸如 conditionfeelslike_cfeelslike_ftemp_ctemp_f 等等 您将使用所有这五个属性。

再次打开您的 weather.component.html 文件,并在要显示的数据中添加字幕。 您将在第二个 col-md-6 中添加这些 <p> 标签:

src/app/weather/weather.component.html

...
<div class="col-md-6">
  <h3 class="text-center my-4">Weather Details:</h3>
  <p class="text-center">Current weather conditions:</p>
  <p class="text-center">Temperature in Degrees Celsius:</p>
  <p class="text-center">Temperature in Degrees Farenheit:</p>
  <p class="text-center">Feels like in Degrees Celsius:</p>
  <p class="text-center">Feels like in Degrees Farenheit:</p>
  <p class="text-center">Location Searched:</p>
</div>

接下来,您将从 JSON 对象收到的数据添加到 HTML:

天气.component.html

...
<h3 class="text-center my-4 ">Weather Details:</h3>
<p class="text-center">
  Current weather conditions: {{this.weatherData?.current.condition.text}}
</p>
<p class="text-center">
  Temperature in Degrees Celsius: {{this.weatherData?.current.temp_c}}
</p>
<p class="text-center">
  Temperature in Degrees Farenheit: {{this.weatherData?.current.temp_f}}
</p>
<p class="text-center">
  Feels like in Degrees Celsius: {{this.weatherData?.current.feelslike_c}}
</p>
<p class="text-center">
  Feels like in Degrees Farenheit:
  {{this.weatherData?.current.feelslike_f}}
</p>
<p class="text-center">
  Location Searched: {{this.weatherData?.location.name}},
  {{this.weatherData?.location.country}}
</p>

当您从 HTML 中的 weatherData 变量中检索数据时,您使用了运算符 ?。 此运算符称为 Elvis 运算符

因为您正在进行 HTTP 调用,所以您正在发出 异步 请求。 您会在某个时候取回该数据,但这不会是立即响应。 然而,Angular 仍将继续使用您从 weatherData 变量中指定的数据填充您的 HTML。 如果在 Angular 开始填充您的段落时您还没有收到数据,则会出现一个错误,指出 Angular 找不到该数据。 例如,.current.location 将显示为未定义。

Elvis Operator 是 安全导航器 并防止这种情况发生。 它告诉 Angular 等待并检查是否首先定义了 weatherData,然后继续在 HTML 中显示该数据。 一旦 weatherData 获得所有信息,Angular 就会更新您的绑定并照常显示您的数据。

您最终的 weather.component.ts 文件将如下所示:

天气.component.html

<div class="container">
  <div class="row">
    <div class="col-md-6">
      <h3 class="text-center my-4">Search for Weather:</h3>
      <form
        [formGroup]="weatherSearchForm"
        (ngSubmit)="sendToAPIXU(weatherSearchForm.value)"
      >
        <div class="form-group">
          <input
            class="form-control"
            type="text"
            id="weatherLocation"
            aria-describedby="weatherLocation"
            placeholder="Please input a Location"
            formControlName="location"
          />
        </div>
        <div class="text-center">
          <button type="submit" class="btn btn-success btn-md">
            Search for the weather
          </button>
        </div>
      </form>
    </div>
    <div class="col-md-6">
      <h3 class="text-center my-4">Weather Details:</h3>
      <p class="text-center">
        Current weather conditions: {{ this.weatherData?.current.condition.text
        }}.
      </p>
      <p class="text-center">
        Temperature in Degrees Celsius: {{ this.weatherData?.current.temp_c }}
      </p>
      <p class="text-center">
        Temperature in Degrees Farenheit: {{ this.weatherData?.current.temp_f }}
      </p>
      <p class="text-center">
        Feels like in Degrees Celsius: {{ this.weatherData?.current.feelslike_c
        }}
      </p>
      <p class="text-center">
        Feels like in Degrees Farenheit: {{
        this.weatherData?.current.feelslike_f }}
      </p>
      <p class="text-center">
        Location Searched: {{ this.weatherData?.location.name }}, {{
        this.weatherData?.location.country }}.
      </p>
    </div>
  </div>
</div>

您已按照返回的 JSON 天气对象的模式来输出所需的数据。 保存文件,返回浏览器,输入 London, UK,您会看到天气数据出现在右侧。

在不同的位置尝试,例如:旧金山,美国达喀尔,塞内加尔,和檀香山,夏威夷。 您将看到所有这些位置的相应天气数据。

结论

您已经使用 Angular、Bootstrap 和 APIXU API 创建了一个天气应用程序。 您已经从头开始设置了一个 Angular 项目,遵循 Angular 最佳实践,同时确保您的应用程序设计良好并正确设置。

Angular 是一个高级框架,可让您轻松创建从小型 Web 应用程序到大型复杂应用程序的任何东西。 与任何框架一样,Angular 确实有一个学习曲线,但是像这样的小项目可以帮助您快速学习并开始有效地使用它。

另一个要考虑添加到您的应用程序的功能是 处理来自您的 HTTP 请求的错误; 例如,如果您要输入无效的位置。 如果温度在特定阈值之间,另一个增强功能将是显示不同的图像。 您还可以使用其他 API 使用 Angular 创建不同的应用程序。

您可能还想使用 NgBootstrap,它是为 Angular 构建的一种特殊类型的 Bootstrap。 这允许您使用所有标准的 Bootstrap JavaScript 小部件以及一些未包含在专门为 Angular 适配的标准安装中的特殊小部件。

本教程的完整代码可在 GitHub 上找到。