从Google API加载auth2时,更改检测功能无法运行 [英] Change detection doesn't run when loading auth2 from Google API
问题描述
我试图从Angular 2服务中加载脚本 https://apis.google.com/js/api.js?onload=initGapi
为了调用Google API,但每当脚本完成加载和初始化时尝试返回一个值时, async
管道都不想呈现该值
我使用 EventEmitter
,当 gapi时触发。 load()
和 gapi.client.init
已完成,并且observable将订阅到组件中。异步管道似乎不想读取值,直到我单击相同组件内的按钮(并可能触发更改检测)。当我在 ApplicationRef
中使用 tick()
时,在组件内强制更改检测不起作用。
加载Google API的服务如下所示:
google-api.service.ts
从'@ angular / core'导入{Injectable,EventEmitter};
从'rxjs / Observable'导入{Observable};
从'../../+home/models'导入{User,Client};
声明var gapi:any;
@Injectable()
export class GoogleApiService {
CLIENT_ID:string ='.... apps.googleusercontent.com';
DISCOVERY_DOCS:string [] = [https://www.googleapis.com/discovery/v1/apis/admin/directory_v1/rest];
SCOPES ='https://www.googleapis.com/auth/admin.directory.user.readonly';
authEmitter:EventEmitter< boolean> = new EventEmitter();
构造函数(){
window ['initGapi'] =(ev)=> {
this.handleClientLoad();
};
this.loadScript();
loadScript(){
let script = document.createElement('script');
script.src ='https://apis.google.com/js/api.js?onload=initGapi';
script.type ='text / javascript';
document.getElementsByTagName('head')[0] .appendChild(script);
handleClientLoad(){
gapi.load('client:auth2',this.initClient.bind(this));
initClient(){
gapi.client.init({
discoveryDocs:this.DISCOVERY_DOCS,
clientId:this.CLIENT_ID,$ b $(b)范围:this.SCOPES
})。then(()=> {
this.authEmitter.emit(true);
});
}
get gapi():Observable< any> {
return this.authEmitter.map(()=>'hello world');
从服务中读取组件
app.component.ts
import {Component ,来自'@ angular / core'的OnInit};
从'rxjs / Observable'导入{Observable};
从'../../+core/services'导入{GoogleApiService};
@Component({
template:`
服务说什么?
{{api $ | async}}
})
export class InviteEmployeesContainer implements OnInit {
api $:Observable< string> ;;
构造函数(private gApiService:GoogleApiService){}
ngOnInit(){
this.api $ = this.gApiService.gapi;
this.gApiService.gapi.subscribe((result)=> {
console.log(result);
});
}
点击(){
console.log('clicked');
结果页面打印出字符串服务说什么?
并且有一个按钮,但是不会打印文本 hello world
,直到我点击按钮'b
$ b
另外,当我使用订阅并登录到控制台时,this.gApiService .gapi.subscribe
,它会记录 hello world
,当我期待它。
有谁知道如何使用异步管道获取 hello world
以在 authEmitter
触发时打印到页面一个事件。
解决方案我最终找到了解决这个问题的办法,这个问题有几个问题。
首先, EventEmitter
应该只用于发出,并且不应该订阅,所以我换成了一个主题
。通常我会尝试使用可观察的,但在这种情况下,我发现一个主题更合适。
下面是使用 Subject
而不是 EventEmitter
。我还评论了 gapi.client.init ...
,并用不同的承诺替换它,因为它实际上不是问题的一部分。请注意,在检测到更改后,检测仍然不会运行,直到按钮被点击,这并不是预期的结果。
https://plnkr.co/edit/YaFP07N6A4CQ3Zz1SbUi?p=preview
我遇到的问题是因为 gapi.load('client:auth2',this.initClient.bind(this));
runs initClient
在Angular 2区域之外,它将它排除在变更检测之外。
为了捕获更改检测中的主题,我们必须运行在Angular 2区域内调用 next
和完成
调用。这是通过导入 NgZone
然后修改行来完成的:
new Promise((res)=> res())。then(()=> {
this.zone.run(()=> {
this.authSubject.next(true);
this.authSubject.complete(true);
});
});
I'm trying to load the script https://apis.google.com/js/api.js?onload=initGapi
from within an Angular 2 service in order to make calls to the Google API, but whenever I try to return a value when the script is done loading and initializing, the async
pipe doesn't want to render the value to the template.
I'm using an EventEmitter
that fires when the gapi.load()
and gapi.client.init
is complete, and the observable is subscribed to it in a component. The async pipe doesn't seem to want to read the value, until I click a button within the same component (and possibly fire change detection). Forcing change detection within the component didn't work when I used tick()
from ApplicationRef
.
The service that's loading the Google API is as follows:
google-api.service.ts
import { Injectable, EventEmitter } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { User, Client } from '../../+home/models';
declare var gapi: any;
@Injectable()
export class GoogleApiService {
CLIENT_ID: string = '....apps.googleusercontent.com';
DISCOVERY_DOCS: string[] = ["https://www.googleapis.com/discovery/v1/apis/admin/directory_v1/rest"];
SCOPES = 'https://www.googleapis.com/auth/admin.directory.user.readonly';
authEmitter: EventEmitter<boolean> = new EventEmitter();
constructor() {
window['initGapi'] = (ev) => {
this.handleClientLoad();
};
this.loadScript();
}
loadScript() {
let script = document.createElement('script');
script.src = 'https://apis.google.com/js/api.js?onload=initGapi';
script.type = 'text/javascript';
document.getElementsByTagName('head')[0].appendChild(script);
}
handleClientLoad() {
gapi.load('client:auth2', this.initClient.bind(this));
}
initClient() {
gapi.client.init({
discoveryDocs: this.DISCOVERY_DOCS,
clientId: this.CLIENT_ID,
scope: this.SCOPES
}).then(() => {
this.authEmitter.emit(true);
});
}
get gapi(): Observable<any> {
return this.authEmitter.map(() => 'hello world');
}
}
And the component reading from the service
app.component.ts
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { GoogleApiService } from '../../+core/services';
@Component({
template: `
What does the service say?
{{ api$ | async }}
<button (click)="click()">Button</button>`
})
export class InviteEmployeesContainer implements OnInit {
api$: Observable<string>;
constructor(private gApiService: GoogleApiService) {}
ngOnInit() {
this.api$ = this.gApiService.gapi;
this.gApiService.gapi.subscribe((result) => {
console.log(result);
});
}
click() {
console.log('clicked');
}
}
The resulting page prints out the string What does the service say?
and has a button, but doesn't print the text hello world
until I click the button, which isn't the desired behaviour, it should be visible on the page immediately.
Additionally, when I subscribe and log to the console using this.gApiService.gapi.subscribe
, it logs hello world
when I would expect it to.
Does anyone know how I can use the async pipe to get hello world
to print to the page when the authEmitter
fires an event.
解决方案 I ended up finding a solution to this problem, the question has a few issues.
First of all, EventEmitter
should only be used to emit, and should never be subscribed to, so instead I swapped it out with a Subject
. Normally I'd try to use an observable, but in this case I found a subject more appropriate.
Below is a revised plunker using Subject
instead of EventEmitter
. I also commented out gapi.client.init...
and replaced it with a different promise, since it's not actually part of the problem. Note that in the plunker change detection still isn't run until the button is clicked, which is not expected.
https://plnkr.co/edit/YaFP07N6A4CQ3Zz1SbUi?p=preview
The problem I'm encountering is because gapi.load('client:auth2', this.initClient.bind(this));
runs initClient
outside of the Angular 2 zone, which excludes it from change detection.
In order to capture the subject within change detection, we have to run the subject's next
and complete
call within the Angular 2 zone. This is done by importing NgZone
and then modifying the lines:
new Promise((res) => res()).then(() => {
this.zone.run(() => {
this.authSubject.next(true);
this.authSubject.complete(true);
});
});
See the final (resolved) plunker here.
这篇关于从Google API加载auth2时,更改检测功能无法运行的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!