如何在 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 小部件?的主要内容,如果未能解决你的问题,请参考以下文章

如何将动态外部组件加载到 Angular 应用程序中

如何在 Angular 组件中正确使用 Cloud Functions?

Angular如何在组件中使用嵌入脚本

存在安全漏洞的 Spring Cloud 嵌入式 netty 服务器

如何将 Firebase Cloud Storage 中返回项目的 URL 推送到 Angular 中的数组中?

如何直接从您的 Angular 项目中调用 Firebase Cloud Function