Angular - JSON-P



HttpClient jsonp() 方法

JSONP 是一种特殊的技术,用于绕过 Web 浏览器强制执行的跨域 (CORS) 策略。通常情况下,浏览器只支持同一个域内网站的 AJAX 调用。要支持对另一个域的 AJAX 调用,CORS 策略必须在服务器端和客户端(浏览器)都启用。

与其启用 CORS 策略,服务器可以发送 JSONP 格式的响应。JSONP 格式基本上是用回调函数包装的 JSON。浏览器将从服务器获取响应并将其作为脚本执行。回调函数将获取响应并执行必要的业务逻辑。

服务器可以通过用回调函数(例如 mycallback)包装 JSON 格式的响应将其转换为 JSONP 格式,如下所示:

mycallback( { ... json data ... })

这里:

  • mycallback 是浏览器(客户端)发送的函数名称。

jsonp() 是 HttpClient 类中可用的方法,用于使用 jsonp 技术请求服务器。它类似于 get() 方法,但增加了一个选项,用于设置服务器用来获取回调函数的查询参数的名称。Angular 将自动生成一个函数来解析客户端的 JSON。然后,它将向 url 添加新的查询参数。查询参数的名称将是在 jsonp 调用中设置的名称。查询参数的值是 Angular 自动生成的函数的名称。

jsonp() 方法的签名

jsonp() 方法的签名如下:

jsonp(<url as string>, <callback>)
  • url 表示要请求的资源的 URI。

  • callback 表示在 jsonp 服务器调用后要调用的回调函数名称(将自动生成)。

演示 jsonp 方法的简单代码如下:

let jsonp_req = this.http.jsonp<Expense[]>('https://127.0.0.1:8000/api/jsonp/expense', 'callback');
jsonp_req.subscribe(data => this.expenses = data);

这里:

  • this.http 是 HttpClient 实例。

  • jsonp() 是用于请求服务器的方法。它不会直接请求服务器。相反,它返回一个 Observable,可以通过订阅它来请求服务器,并在订阅函数中获取实际的响应。

  • https://127.0.0.1/api/jsonp/expense 是资源的 URI(统一资源标识符)

  • Angular 将自动生成一个函数,例如 ng_jsonp_callback_0,并使用 jsonp() 函数的第二个参数将其附加到请求 url。

https://127.0.0.1:8000/api/jsonp/expense?callback=ng_jsonp_callback_0

工作示例

为了实现 HTTP 客户端-服务器通信,我们需要设置一个 Web 应用程序并公开一组 Web API。客户端可以请求 Web API。让我们创建一个示例服务器应用程序,Expense API App,为支出提供 CRUD REST API(主要是 jsonp 请求)。

步骤 1:转到您喜欢的 workspace,如下所示:

cd /go/to/your/favorite/workspace

步骤 2:创建一个新文件夹 expense-rest-api 并进入该文件夹

mkdir expense-rest-api && cd expense-rest-api

步骤 3:使用 npm 命令提供的 init 子命令创建一个新应用程序,如下所示:

npm init

上述命令会提出一些问题,请使用默认答案回答所有问题。

步骤 4:安装 express 和 cors 包以创建基于节点的 Web 应用程序。

npm install express cors --save

步骤 5:安装 sqlite 包以将支出存储在基于 sqlite 的数据库中

npm install sqlite3 --save

步骤 6:创建一个新文件 sqlitedb.js,并添加以下代码以使用支出表和示例支出条目初始化数据库。支出表将用于存储支出项目

var sqlite3 = require('sqlite3').verbose()
const DBSOURCE = "expensedb.sqlite"

let db = new sqlite3.Database(DBSOURCE, (err) => {
   if (err) {
      console.error(err.message)
      throw err
   }else{
      console.log('Connected to the SQLite database.')
      db.run(`CREATE TABLE IF NOT EXISTS expense (
         id INTEGER PRIMARY KEY AUTOINCREMENT,
         item text, 
         amount real, 
         category text, 
         location text, 
         spendOn text, 
         createdOn text 
      )`,
         (err) => {
            if (err) {
               console.log(err);
            }else{
               var insert = 'INSERT INTO expense (item, amount, category, location, spendOn, createdOn) VALUES (?,?,?,?,?,?)'

               db.run(insert, ['Pizza', 10, 'Food', 'KFC', '2020-05-26 10:10', '2020-05-26 10:10'])
               db.run(insert, ['Pizza', 9, 'Food', 'Mcdonald', '2020-05-28 11:10', '2020-05-28 11:10'])
               db.run(insert, ['Pizza', 12, 'Food', 'Mcdonald', '2020-05-29 09:22', '2020-05-29 09:22'])
               db.run(insert, ['Pizza', 15, 'Food', 'KFC', '2020-06-06 16:18', '2020-06-06 16:18'])
               db.run(insert, ['Pizza', 14, 'Food', 'Mcdonald', '2020-06-01 18:14', '2020-05-01 18:14'])
            }
         }
      );  
   }
});

module.exports = db

步骤 7:打开 index.js 并更新以下代码:

var express = require("express")
var cors = require('cors')
var db = require("./sqlitedb.js")

var app = express()
app.use(cors());

var bodyParser = require("body-parser");
app.use(express.urlencoded({ extended: true }));
app.use(express.json());

var HTTP_PORT = 8000
app.listen(HTTP_PORT, () => {
   console.log("Server running on port %PORT%".replace("%PORT%", HTTP_PORT))
});

app.get("/", (req, res, next) => {
   res.json({ "message": "Ok" })
});

app.get("/api/jsonp/expense", (req, res, next) => {
   var sql = "select * from expense"
   var params = []
   db.all(sql, params, (err, rows) => {
      if (err) {
         res.status(400).json({ "error": err.message });
         return;
      }
      res.jsonp(rows)
  });

});

app.use(function (req, res) {
   res.status(404);
});

在这里,代码将创建以下六个提到的 REST API 端点。

  • / 端点返回 OK 消息,以确保应用程序正常工作。

  • /api/jsonp/expense 端点以 jsonp 格式返回数据库中所有可用的支出项目。

步骤 8:运行应用程序,如下所示:

node index.js

步骤 9:要测试应用程序并确保其正常工作,请打开浏览器并访问 https://127.0.0.1:8000/。如果应用程序正常工作,则应返回以下消息。

{ 
   "message": "Ok" 
}

让我们创建一个可工作的 Angular 示例,以使用 HttpClient 服务类和 get() 方法从上述服务器应用程序获取所有支出项目。

步骤 1:通过运行 ng new 命令创建一个新的 Angular 应用程序,如下所示:

ng new my-http-app

启用 Angular 路由和 CSS,如下所示:

? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? CSS

步骤 2:通过在模块配置文件 (app.module.ts) 中导入 HttpClientModule 来启用应用程序中的 HTTP 通信。

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

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

import { HttpClientModule, HttpClientJsonpModule} from '@angular/common/http';

@NgModule({
   declarations: [
      AppComponent
   ],
   imports: [
      BrowserModule,
      AppRoutingModule,
      HttpClientModule,
      HttpClientJsonpModule
   ],
   providers: [],
   bootstrap: [AppComponent]
})
export class AppModule { }

这里:

  • 从 @angular/common/http 模块导入 HttpClientModule 和 HttpClientJsonpModule。

  • 将 HttpClientModule 添加到 @NgModule 配置的 imports 部分。

步骤 3:创建一个新的接口 Expense 来表示我们的支出项目。

interface Expense {
   id: Number,
   item: String,
   amount: Number,
   category: String,
   location: String,
   spendOn: Date
}

export default Expense;

步骤 4:创建一个新的组件 ListExpenses 来显示来自服务器的支出项目。

ng generate component ListExpenses

它将创建如下所示的组件:

CREATE src/app/list-expenses/list-expenses.component.css (0 bytes)
CREATE src/app/list-expenses/list-expenses.component.html (28 bytes)
CREATE src/app/list-expenses/list-expenses.component.spec.ts (602 bytes)
CREATE src/app/list-expenses/list-expenses.component.ts (229 bytes)
UPDATE src/app/app.module.ts (581 bytes)

步骤 5:将我们的新组件包含到 App 根组件的视图 app.component.html 中,如下所示:

<app-list-expenses></app-list-expenses>

<router-outlet></router-outlet>

步骤 6:通过构造函数将 HttpClient 注入 ListExpenses 组件,如下所示:

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

@Component({
   selector: 'app-list-expenses',
   templateUrl: './list-expenses.component.html',
   styleUrls: ['./list-expenses.component.css']
})
export class ListExpensesComponent {

   constructor(private http: HttpClient) { }
}

步骤 7:实现 OnInit 生命周期钩子,以便在 ListExpenses 组件初始化后请求服务器获取支出。

export class ListExpensesComponent implements OnInit{
   constructor(private http: HttpClient) { }   
   ngOnInit(): void {
   }
}

步骤 8:创建一个局部变量 expenses 来保存来自服务器的支出。

export class ListExpensesComponent implements OnInit{
   expenses: Expense[] = [];   
   constructor(private http: HttpClient) { }   
   ngOnInit(): void {
   
   }
}

步骤 9:通过传递 url 和选项来调用 this.http(HttpClient 实例)对象的 get 方法,并从服务器获取支出对象。然后,将支出设置到我们的局部变量 expenses 中。

export class ListExpensesComponent implements OnInit{
   expenses: Expense[] = [];   
   constructor(private http: HttpClient) { }   
   ngOnInit(): void {
   
   this.http.jsonp<Expense[]>('https://127.0.0.1:8000/api/jsonp/expense', 'callback')
      .subscribe( data => {
         this.expenses = data as Expense[]
         console.log(this.expenses)
      })   
   }
}

这里:

  • 将 Expense[] 设置为服务器返回的对象的类型。服务器将在其主体中以 JSON 格式发送支出对象的数组。

  • 订阅请求 (this.http.jsonp) 对象。然后将订阅的数据解析为支出对象的数组,并将其设置为局部支出变量 (this.expenses)

步骤 10:ListExpensesComponent 的完整代码如下:

import { Component, OnInit } from '@angular/core';
import { HttpClient, HttpRequest, HttpResponse, HttpEvent } from '@angular/common/http';
import Expense from '../Expense';

@Component({
   selector: 'app-list-expenses',
   templateUrl: './list-expenses.component.html',
   styleUrls: ['./list-expenses.component.css']
})
export class ListExpensesComponent implements OnInit {
   expenses: Expense[] = [];   
   constructor(private http: HttpClient) { }
   
   ngOnInit(): void {
   
   this.http.jsonp<Expense[]>('https://127.0.0.1:8000/api/jsonp/expense', 'callback')
      .subscribe( data => {
      this.expenses = data as Expense[]
      console.log(this.expenses)
      })      
   }
}

步骤 11:接下来,从组件获取 expenses 对象并在我们的组件模板页面 (list-expenses.component.html) 中呈现它

<div><h3>Expenses</h3></div>
<ul>
   <li *ngFor="let expense of expenses">
      {{expense.item}} @ {{expense.location}} for {{expense.amount}} USD on {{expense.spendOn | date:'shortDate' }}
   </li>
</ul>

步骤 12:最后,使用以下命令运行应用程序:

ng serve

步骤 13:打开浏览器并导航到 https://127.0.0.1:4200/ url 并检查输出

myhttpapp

在这里,输出显示我们的支出作为项目列表。

结论

Angular 提供了一种简单的方法来通过 HttpClient 对象请求服务器。jsonp() 是一个特定方法,用于即使服务器不支持跨域 API 调用 (CORS) 也能从服务器获取资源。

广告