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

Posted

技术标签:

【中文标题】如何在 Angular 中动态嵌入来自 Cloudinary 的第三方 javascript 小部件?【英文标题】:How to embed third party javascript widgets from Cloudinary dynamically in Angular? 【发布时间】:2020-01-13 19:09:26 【问题描述】:

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

发生了什么事?我对 HttpClient 或 CORS 并不陌生,但从未见过。

从源“http://127.0.0.1:4200”访问“https://widget.cloudinary.com/v2.0/global/all.js”处的 XMLHttpRequest 已被 CORS 策略阻止:请求的资源上不存在“Access-Control-Allow-Origin”标头。

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

    请求的脚本确实到达在我的 Chrome 开发工具/网络选项卡 XHR 部分。于是服务器发送出去,Chrome 愉快地收到了。

    我在 Angular 开发环境中。

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

    开发工具中的请求标头: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 小部件?

【问题讨论】:

【参考方案1】:

问题

不要忽略错误消息。 CORS 问题与服务器未提供正确的标头以允许从您的网站 javascript 代码进行跨源访问有关!正如错误所说

请求的资源上不存在“Access-Control-Allow-Origin”标头。

问题不在于您的客户端。 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 文件不会使您能够使用小部件!您必须将 cloudinary js 文件嵌入到您的 html 中才能使用它。

动态嵌入和创建 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();
    
  

【讨论】:

谢谢!我以前与 CORS 合作过,并认为我了解所有这些。他们的服务器似乎允许有限的请求。我正在通过 html 脚本设置获取信息,但这对我来说似乎很古怪。看来我应该只用脚本创建一个对象,然后用代码从那里开始。误导的是脚本下载,我可以在开发工具中看到它,所以服务器似乎没有问题。我可以看到我的实现是。 @Preston 如果您的整个应用程序都在使用 Cloudinary 小部件,并且您不介意在启动时加载 js,您显然可以将&lt;script&gt; 标签添加到您的index.html。我假设您希望在初始化特定组件时根据特定请求动态加载脚本,这就是我回答中的 Service 所做的。 是的,你是对的。只有几个组件需要小部件。它是应用程序的一小部分。再次感谢您,我相信这个 Angular / Cloudinary 代码将来会对许多编码人员有用。网上没有这样的东西。

以上是关于如何在 Angular 中动态嵌入来自 Cloudinary 的第三方 javascript 小部件?的主要内容,如果未能解决你的问题,请参考以下文章