原本是打算把 redoc-cli 產生出來的文件直接嵌進去的,可是這樣子在文件有變動時,就會又要再做一次產生、嵌入,這樣不太好。最好還是可以自動依據寫好的 OpenAPI specification 來自動產出,這才是比較好的作法。
第一個待解決的問題是怎麼把 OpenAPI specification JSON 放到 Angular 專案裡,並且可以讀出來使用。關於這個,我是找到 How To Read Local JSON Files In Angular 這篇文章,方法挺簡單的,就把 json 檔案丟到 src/assets 下,然後直接在程式裡用 import 。
// src/app/xxx/xxx.component.ts import SampleJson from '../../assets/SampleJson.json’; // 後續程式碼就直接引用 SampleJSON 即可。
但修改完,TypeScript compiler 會有錯誤訊息,這得要改 tsconfig.json 在 compilerOptions 裡加入 resolveJsonModule 跟 esModuleInterop
{ "compilerOptions": { "resolveJsonModule": true, "esModuleInterop": true } }
第二個問題是,angular template 裡不能直接寫 script tag,這個可以實作 AfterViewInit ,然後用 DOMElement 來動態插入 (參考來源:https://stackoverflow.com/questions/38088996/adding-script-tags-in-angular-component-template/43559644)。細節的說明,我就直接寫在下面程式的註解裡。
import {
Component,
OnInit,
ViewEncapsulation,
ElementRef,
AfterViewInit } from '@angular/core’;
// (1) 剛剛前面提到的,引用 OpenAPI specification JSON
import APIJson from '../../assets/api.json';
@Component({
selector: 'app-documentation',
templateUrl: './documentation.component.html',
styleUrls: ['./documentation.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class DocumentationComponent implements OnInit, AfterViewInit { // (2) Component 要實作 AfterViewInit 這介面
constructor(private elementRef: ElementRef) { } // (3) 引入 ElementRef ,Angular 會幫忙作動態注入
ngAfterViewInit(): void {
// (4) 這裡就是在頁面載入後要作的事情
this.insertSpec();
this.insertScript();
}
insertScript(): void {
// (5) 插入 script element,這裡要載入 redoc 的腳本
const redocScript = document.createElement('script');
redocScript.type = 'text/javascript';
redocScript.src = 'https://unpkg.com/redoc@next/bundles/redoc.standalone.js’; // (6) 這腳本的網址是閱讀 redoc-cli 的原始碼以後取得的。
this.elementRef.nativeElement.appendChild(redocScript);
// (7) 插入另外一個 script element,這裡主要是執行 redoc,讓 redoc 能解析 json 並顯示出文件。
const initScript = document.createElement('script');
initScript.type = 'text/javascript';
initScript.textContent = `
const __redoc_state=${JSON.stringify(APIJson)};
var container = document.getElementById('redoc');
Redoc.hydrate(__redoc_state, container);
`; // (8) 字串要允許多行,可以使用 backquote ` https://stackoverflow.com/questions/35225399/multiline-string
this.elementRef.nativeElement.appendChild(initScript);
}
insertSpec(): void {
// (9) 插入 element ,讓腳本知道要根據哪個 element 來作處理。
let s = document.createElement('redoc');
s.setAttribute('spec-url', 'assets/api.yml');
this.elementRef.nativeElement.appendChild(s);
}
ngOnInit(): void {
}
}
至此就大功告成啦。
P.S. redoc 在 1.x 時,是有支援 Angular 的,但到了 2.x ,就改用 react 了。