如何使用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。
先决条件
在开始本教程之前,您需要以下内容:
- Node.js 和 npm 安装在你的本地机器上。 您可以从 Node.js 网站 安装这两个,或者,您可以按照本教程 安装 Node.js 并设置本地开发环境 。
- APIXU API 密钥。 注册一个免费的 APIXU 帐户并在此处 获取免费的 API 密钥 。
- 已安装文本编辑器,例如 Visual Studio Code、Atom 或 Sublime Text。
- 熟悉 JSON 及其格式。 您可以在 how to work with JSON in Javascript 中了解更多信息。
- 了解 Javascript 中的数组和对象,您可以分别在 Understanding arrays in Javascript 和 Understanding data types in Javascript 中了解更多。
第 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数组中已经有一个导入的模块BrowserModule。BrowserModule为您的应用程序提供必要的服务和指令,并且应该始终是您的imports数组中的第一个导入模块。angular.json文件。 位于应用程序的根weather-app文件夹中,这是 Angular CLI 的配置文件。 该文件包含您的 Angular 应用程序需要运行的内部配置设置。 它为您的整个应用程序设置默认值,并具有诸如测试时使用的配置文件、应用程序中使用的全局样式或将构建文件输出到哪个文件夹等选项。 您可以在官方 Angular-CLI 文档 中找到有关这些选项的更多信息。
您可以暂时保留所有这些文件,因为接下来您将安装 Bootstrap。
Bootstrap 有两个依赖项需要安装才能在 Angular 中正常工作 - jQuery 和 popper.js。 jQuery 是一个专注于客户端脚本的 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 还不知道它需要这些库,因此您需要将路径添加到 jquery、popper.js、bootstrap.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.js 和 node_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)
]
...
})
...
在此文件中,您已导入路线对象的 RouterModule 和 allAppRoutes 数组。 然后,您将 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-top 和 margin-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 中,有两种方法可以在应用程序中为用户输入创建表单——reactive 或 template-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 文件并导入 FormBuilder 和 FormGroup 类。
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 Weather 和 Forecast 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 Vault 或 Azure 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() 方法。 然后,您将该数据传递给 ApixuService 的 getWeather() 方法,该方法随后向具有该位置的 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 对象,您将看到诸如 condition、feelslike_c、feelslike_f、temp_c、temp_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 上找到。