如何在 Angular 中动态嵌入来自 Cloudinary 的第三方 javascript 小部件?

How to embed third party javascript widgets from Cloudinary dynamically in Angular?

当我尝试通过 Angular HttpClient 下载和使用 Cloudinary 小部件 javascript 资源时出现 CORS 错误。

这是怎么回事?我不是 HttpClient 或 CORS 的新手,但从未见过这个。

Access to XMLHttpRequest at 'https://widget.cloudinary.com/v2.0/global/all.js' from origin 'http://127.0.0.1:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

  1. 服务器CORS与本题无关。它是公开可用的代码,可以在任何浏览器中轻松检索。

  2. 请求的脚本确实到达在我的Chrome开发工具/网络选项卡 XHR 部分。所以服务器发送了它并且Chrome愉快地接收了它。

  3. 我在 Angular 开发环境中。

  4. 问题出在 Angular 上,我认为是 HttpClient。它认为存在不存在的 CORS 问题。

  5. 在开发工具中请求headers:Sec-Fetch-Mode: cors

我查看了很多其他 SO 帖子,包括一个看起来相似但没有帮助的帖子。

我的代码。

export class CloudinaryComponent implements OnInit {

  private url = 'https://widget.cloudinary.com/v2.0/global/all.js';

  constructor(
    private http: HttpClient
  ) {}

  ngOnInit() {
    this.loadWidget;
  }


  // Load the Cloudinary Upload Widget code, not the widget GUI.
  private loadWidget() {
    return this.http.get(this.url);
  };

  // Button click calls the Upload Widget GUI.

  private callPopup() {
    this.loadWidget().subscribe( result => {
        // this.uploadWidget.open();
    });
  }
}

如何正确嵌入 Cloudinary 小部件?

问题

不要关闭错误消息。 CORS 问题与服务器没有提供正确的 header 允许从您的网站 javascript 代码进行跨源访问有关!正如错误所述

No 'Access-Control-Allow-Origin' header is present on the requested resource.

问题不在您的客户端。 Angular 并未造成此错误。出于安全原因,您的浏览器正在阻止该请求。服务器刚刚决定不允许此类请求。您可以直接从浏览器访问资源这一事实可能会欺骗您,但在这种情况下,您以不同的方式访问资源(即不是从 javascript XMLHttpRequest)。

如果您想完全理解 CORS,请阅读它:

https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

XMLHttpRequest cannot load XXX No 'Access-Control-Allow-Origin' header

解决方案

使用 HttpClient 下载 javascript 文件将无法使用该小部件!您必须在 html 中嵌入 cloudinary js 文件才能使用它。

动态嵌入和创建 javascript 小部件的服务可能如下所示:

Demo

import { Injectable, RendererFactory2, Renderer2 } from '@angular/core';
import { Observable, of, fromEvent } from 'rxjs';
import { map } from 'rxjs/operators';
declare let cloudinary: any; // declare js widget variable

const widgetUrl = 'https://widget.cloudinary.com/v2.0/global/all.js';

@Injectable({
  providedIn: 'root'
})
export class CloudinaryService {
  private renderer: Renderer2;

  constructor(rendererFactory: RendererFactory2) {
    this.renderer = rendererFactory.createRenderer(null, null);
  }

  // create the upload widget
  createUploadWidget(data: any, callback: (error: any, result: any) => void): Observable<any> {
    return this.skriptExists(widgetUrl)
      // js is embeded -> call js function directly
      ? of(cloudinary.createUploadWidget(data, callback))
      // js isn't embeded -> embed js file and wait for it to load
      : fromEvent(this.addJsToElement(widgetUrl), 'load').pipe(
        // map to call of js function
        map(e => cloudinary.createUploadWidget(data, callback))
      );
  }

  // check if js file is already embeded
  private skriptExists(jsUrl: string): boolean {
    return document.querySelector(`script[src="${jsUrl}"]`) ? true : false;
  }

  // embed external js file in html
  private addJsToElement(jsUrl: string): HTMLScriptElement {
    const script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = jsUrl;
    this.renderer.appendChild(document.body, script);
    return script;
  }
}

使用组件中的服务创建小部件:

export class AppComponent implements OnInit {

  widget: any;

  constructor(private cloudinary: CloudinaryService) { }

  ngOnInit() {
     this.cloudinary.createUploadWidget(
      {
        cloudName: 'my_cloud_name',
        uploadPreset: 'my_preset'
      },
      (error, result) => {
        if (!error && result && result.event === "success") {
          console.log('Done! Here is the image info: ', result.info);
        }
      }
    ).subscribe(widget => this.widget = widget);
  }

  openWidget() {
    if (this.widget) {
      console.log('open')
      this.widget.open();
    }
  }
}