无法设置回调函数名称时,如何使用Angular向静态文件发送JSONP请求? [英] How to do a JSONP request with Angular to a static file when you can't set the callback function name?
问题描述
您好,问题解决者!我希望向您学习一些东西.我遇到了一个挑战,该挑战可能会让我了解到更多有关Angular的知识,我将不胜感激任何人可能提供的任何投入.
背景
我的公司在许多网站中都有一个共同的页脚,我们使用JSONP将其延迟加载到每个网站中.页脚内容由生成静态HTML的系统管理,以实现高性能,低成本和高正常运行时间.当我们将该页脚延迟加载到页面中时,我们无法控制响应中包含的回调函数调用的名称.
我们的页脚可能类似于以下 footer.json
文件:
footerCallback([[< footer> FROM THE FOOTER</footer"]]);
我们使用如下简单的纯Javascript代码将其延迟加载到HTML页面中:
< div id =页脚"</div< script>函数footerCallback(json_data){document.getElementById('footer').outerHTML = json_data [0];}window.onload = function(){var script = document.createElement('script');script.src ='assets/static-footer.json'script.async = true;document.getElementsByTagName('head')[0] .appendChild(script);}</script>
我在GitHub上发布了一个有效的演示,此处.不幸的是,由于它使用JSONP请求,因此我无法将其发布在Stackblitz上以使其更易于运行.此版本完全按照我上面所述的方式包含共享页脚,此处.
挑战
我们需要在Angluar应用程序中包含相同的中央页脚,因此我试图创建一个 footer
组件,该组件会将懒加载的JSONP页脚插入到Angular应用程序中.
方法
我创建了
问题
无效的部分是回调.我无法从主 index.html
中删除原始的全局回调函数,因为我不知道如何将其替换为JSONP响应可以触发的函数.我能够 this.http.jsonp(this.footerURL,'callback')的回调函数名称,则它将发出如下请求:
https://...footer.min.json?callback = ng_jsonp_callback_0
这是一个不错的功能,但这是我需要自定义的 ng_jsonp_callback_0
,而不是URL参数.因为服务器无法调整其响应以包含对函数 ng_jsonp_callback_0
的引用.因为没有服务器,所以它只是一个静态文件.
丑陋的解决方案
我的解决方法是在全局范围内定义回调函数.这样,我可以支持API或静态文件所需的任何回调函数名称.
这至少使我能够将与延迟加载页脚相关的所有内容封装到具有自己服务的页脚组件中.但是,如果我正在污染全局范围,我觉得我并不是真的以Angular方式这样做.
问题
有没有办法让我自己手动指定该回调函数的名称,而不是让Angular选择名称 ng_jsonp_callback_0
或其他名称?
如果没有,那么还有其他一些优雅的方法可以解决这个问题吗?除了使用全局回调函数以外?
好吧..第一件事是Angular已对回调进行了硬编码,如果您检查源代码,将会看到类似以下内容:
/***通过增加全局`nextRequestId`获得下一个回调方法的名称.*/nextCallback(){返回`ng_jsonp_callback _ $ {nextRequestId ++}`;}
因此,当未调用 ng_jsonp_callback _
时,Angular会引发错误:
this.resolvedPromise.then(()=> {//清理页面.清理();//检查响应回调是否已运行.如果(!完成){//没有,请求出了点问题.通过返回错误//可观察的错误路径.所有JSONP错误的状态均为0.观察者错误(新的HttpErrorResponse({网址,状态:0,statusText:"JSONP错误",错误:新错误(JSONP_ERR_NO_CALLBACK),}));返回;}//成功.正文包含响应正文;如果没有响应正文,则为null//回.viewer.next(new HttpResponse({身体,状态:200,statusText:确定",网址,}));//完成流,响应结束.Observer.complete();});
Angular只是使用他们定义的回调来加载url,并使用window对象或空对象,如您在源代码中所见:
/***工厂函数,用于确定将JSONP回调存储在何处.**通常,JSONP回调存储在`window`对象上,但是可能不存在*在测试环境中.在这种情况下,回调将存储在匿名对象上.***/导出函数jsonpCallbackContext(){if(typeof window ==='object'){返回窗口;}返回 {};}
现在我们知道这一点,我们将执行以下操作:
- 调用
ng_jsonp_callback _
,以便我们跳过该错误并订阅响应.我们需要为此插入脚本. - 使用Renderer2来操作DOM.
- 将ElementRef注入组件
- 删除不需要的html中的内容.
- 创建带有json_data的元素并将其插入组件的elementRef
现在在组件中执行以下操作:
导出类FooterComponent实现AfterViewInit {//步骤2和3构造函数(私有footerService:FooterService,私人elementRef:ElementRef,私有渲染器:Renderer2){}ngAfterViewInit(){this.addScript();this.footerService.getFooter().subscribe((data:string [])=> {this.footerCallback(data)});}//步骤1私人addScript(){//用footerCallback包裹ng_jsonp_callback_0const scriptSrc =`window.footerCallback = function(json_data){window.ng_jsonp_callback_0(json_data);}`;const scriptElement:HTMLScriptElement = this.renderer.createElement('script');scriptElement.innerHTML = scriptSrc;this.renderer.appendChild(this.elementRef.nativeElement,scriptElement);}//使用json_data插入一个新元素私人footerCallback(json_data:string []){const footerElement:HTMLElement = this.renderer.createElement('div');footerElement.innerHTML = json_data [0];this.renderer.appendChild(this.elementRef.nativeElement,footerElement);}}
就是这样,希望对您有所帮助.
编辑不要忘记进行清理.
Hello fellow problem solvers! I'm hoping to learn something from you. I ran into a challenge that could probably teach me more about Angular, and I would appreciate any input anyone might have.
Background
My company shares a common footer among many web sites, and we lazy-load that into each site using JSONP. The footer content is managed by a system that generates static HTML, for high performance, low costs, and high uptime. When we lazy-load that footer into pages, we cannot control the name of the callback function call included in the response.
Our footer might look something like this footer.json
file:
footerCallback(["<footer>FROM THE FOOTER</footer>"]);
We lazy-load that into HTML pages with simple pure-Javascript code like this:
<div id="footer"></div>
<script>
function footerCallback(json_data){
document.getElementById('footer').outerHTML = json_data[0];
}
window.onload = function() {
var script = document.createElement('script');
script.src = 'assets/static-footer.json'
script.async = true;
document.getElementsByTagName('head')[0].appendChild(script);
}
</script>
I posted a working demo on GitHub, here. Unfortunately I couldn't post it on Stackblitz to make it easier to run, since it uses JSONP requests. This version includes the shared footer in exactly the way that I described above, right here.
Challenge
We need to include that same central footer in Angluar apps, so I'm trying to create a footer
component that will insert that lazy-loaded JSONP footer into an Angular app.
Approach
I created a footer
component and a footer
service, and I'm using HttpClientJsonpModule
to do a JSONP request. That part works. The service sends the JSONP request and I can see the response in my inspector in Chrome.
Problem
The part that does not work is the callback. I can't remove the original global callback function from the main index.html
because I don't know how to replace it with a function that the JSONP response can trigger. I was able to move the callback function into the component, but only by continuing to define it globally:
function footerCallback(json_data){
document.getElementById('footer').outerHTML = json_data[0];
}
const _global = (window) as any
_global.footerCallback = footerCallback
Angular expects to be able to tell the JSONP server the name of the callback function to include in the JSONP response. But there is no server, it's just a static file.
Angular is flexible enough that I can tell it the URL parameter to use to specify that callback function. If I specify "callback" as the callback function name with this.http.jsonp(this.footerURL,'callback')
then it will issue a request like this:
https://...footer.min.json?callback=ng_jsonp_callback_0
That's a nice feature and all, but it's the ng_jsonp_callback_0
that I need to customize, not the URL parameter. Because the server cannot adjust its response to include a reference to the function ng_jsonp_callback_0
. Because there is no server, it's just a static file.
Ugly solution
My workaround was to define the callback function in the global scope. That way, I can support whatever callback function names the API or static file requires.
This at least enables me to encapsulate everything related to the lazy-loading footer into a footer component with its own service. But I feel like I'm not really doing this in the Angular Way, if I'm polluting the global scope.
Question
Is there a way for me to manually specify the name of that callback function myself, rather than letting Angular pick the name ng_jsonp_callback_0
or whatever?
If not, then is there some other elegant way to handle this? Other than using a global callback function?
Well..the first thing is that Angular has hardcoded the callback, if you check the source code you will see something like this:
/**
* Get the name of the next callback method, by incrementing the global `nextRequestId`.
*/
nextCallback() {
return `ng_jsonp_callback_${nextRequestId++}`;
}
So when the ng_jsonp_callback_
is not called, Angular throws an error:
this.resolvedPromise.then(() => {
// Cleanup the page.
cleanup();
// Check whether the response callback has run.
if (!finished) {
// It hasn't, something went wrong with the request. Return an error via
// the Observable error path. All JSONP errors have status 0.
observer.error(new HttpErrorResponse({
url,
status: 0,
statusText: 'JSONP Error',
error: new Error(JSONP_ERR_NO_CALLBACK),
}));
return;
}
// Success. body either contains the response body or null if none was
// returned.
observer.next(new HttpResponse({
body,
status: 200,
statusText: 'OK',
url,
}));
// Complete the stream, the response is over.
observer.complete();
});
Angular is just loading the url with the callback that they define and uses the window object or an empty object, as you can see in the source code:
/**
* Factory function that determines where to store JSONP callbacks.
*
* Ordinarily JSONP callbacks are stored on the `window` object, but this may not exist
* in test environments. In that case, callbacks are stored on an anonymous object instead.
*
*
*/
export function jsonpCallbackContext() {
if (typeof window === 'object') {
return window;
}
return {};
}
Now that we know this, we will do the following:
- Invoke the
ng_jsonp_callback_
so we can skip the error and subscribe to the response. We need to insert the script for this. - Use Renderer2 to manipulate the DOM.
- Inject ElementRef in the component
- Remove the content in the html we dont need it.
- Create and insert an element with the json_data into the elementRef of the component
Now in the component do the following:
export class FooterComponent implements AfterViewInit {
// Steps 2 and 3
constructor(private footerService: FooterService,
private elementRef: ElementRef,
private renderer: Renderer2) { }
ngAfterViewInit() {
this.addScript();
this.footerService.getFooter().subscribe((data: string[]) => { this.footerCallback(data) });
}
// step 1
private addScript() {
// Wrap the ng_jsonp_callback_0 with your footerCallback
const scriptSrc = `window.footerCallback = function(json_data) {window.ng_jsonp_callback_0(json_data);}`;
const scriptElement: HTMLScriptElement = this.renderer.createElement('script');
scriptElement.innerHTML = scriptSrc;
this.renderer.appendChild(this.elementRef.nativeElement, scriptElement);
}
// Insert a new Element with the json_data
private footerCallback(json_data: string[]) {
const footerElement: HTMLElement = this.renderer.createElement('div');
footerElement.innerHTML = json_data[0];
this.renderer.appendChild(this.elementRef.nativeElement, footerElement);
}
}
That is it, hope it helps.
EDIT Don't forget to do cleanup.
这篇关于无法设置回调函数名称时,如何使用Angular向静态文件发送JSONP请求?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!