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://:8000/api/jsonp/expense', 'callback');
jsonp_req.subscribe(data => this.expenses = data);

這裡:

  • this.http 是 HttpClient 例項。

  • jsonp() 是用於請求伺服器的方法。它不會直接請求伺服器。相反,它返回一個 Observable,可以透過訂閱它來請求伺服器,並在訂閱函式中獲取實際的響應。

  • https:///api/jsonp/expense 是資源的 URI(統一資源識別符號)

  • Angular 將自動生成一個函式,例如 ng_jsonp_callback_0,並使用 jsonp() 函式的第二個引數將其附加到請求 url。

https://: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://: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://: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://: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://:4200/ url 並檢查輸出

myhttpapp

在這裡,輸出顯示我們的支出作為專案列表。

結論

Angular 提供了一種簡單的方法來透過 HttpClient 物件請求伺服器。jsonp() 是一個特定方法,用於即使伺服器不支援跨域 API 呼叫 (CORS) 也能從伺服器獲取資源。

廣告