从 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,但每当我尝试return 脚本完成加载和初始化后的值,async
管道不想将值呈现给模板。
我正在使用 EventEmitter
,它在 gapi.load()
和 gapi.client.init
完成时触发,并且在组件中订阅了可观察对象。异步管道似乎不想读取该值,直到我单击同一组件内的一个按钮(并且可能触发更改检测)。当我使用 ApplicationRef
.
中的 tick()
时,在组件内强制更改检测不起作用
正在加载 Google API 的服务如下:
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');
}
}
以及从服务中读取的组件
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');
}
}
生成的页面打印出字符串 What does the service say?
并有一个按钮,但在我单击按钮之前不会打印文本 hello world
,这不是所需的行为,它应该立即在页面上可见。
此外,当我使用 this.gApiService.gapi.subscribe
订阅并登录到控制台时,它会在我期望的时候记录 hello world
。
有谁知道我如何使用异步管道让 hello world
在 authEmitter
触发事件时打印到页面。
我最终找到了这个问题的解决方案,这个问题有几个问题。
首先,EventEmitter
应该只用于发射,不应该被订阅,所以我用 Subject
换掉了它。通常我会尝试使用可观察对象,但在这种情况下,我发现一个主题更合适。
下面是一个使用 Subject
而不是 EventEmitter
的修改后的 plunker。我还注释掉 gapi.client.init...
并用不同的承诺替换它,因为它实际上不是问题的一部分。请注意,在点击按钮之前,plunker 更改检测仍然不会 运行,这不是预期的。
https://plnkr.co/edit/YaFP07N6A4CQ3Zz1SbUi?p=preview
我遇到的问题是因为 gapi.load('client:auth2', this.initClient.bind(this));
运行s initClient
在 Angular 2 区域之外,这将它排除在变化检测之外。
为了在变化检测中捕获主体,我们必须 运行 主体的 next
和 complete
在 Angular 2 区域内调用。这是通过导入 NgZone
然后修改行来完成的:
new Promise((res) => res()).then(() => {
this.zone.run(() => {
this.authSubject.next(true);
this.authSubject.complete(true);
});
});
对于其他为此苦苦挣扎的人,您还需要包装任何其他 gapi 承诺结果。
与上述解决方案类似,这是我一直在使用的解决方案:
export function nonZoneAwarePromiseToObservable(promise: Promise<T>, zone: NgZone): Observable<T> {
return new Observable<T>(subscriber => {
promise
.then((v) => {
zone.run(() => {
subscriber.next(v)
subscriber.complete()
})
})
.catch((error) => {
zone.run(() => {
subscriber.error(error)
})
})
})
}
我是如何使用它的:
const calendar: typeof gapi.client.calendar = _gapi.client.calendar
const $timezone = calendar.settings.get({setting: 'timezone'})
return nonZoneAwarePromiseToObservable($timezone, this.zone)
.pipe(map((timezoneResponse) => timezoneResponse?.result?.value))
我正在尝试从 Angular 2 服务中加载脚本 https://apis.google.com/js/api.js?onload=initGapi
以便调用 Google API,但每当我尝试return 脚本完成加载和初始化后的值,async
管道不想将值呈现给模板。
我正在使用 EventEmitter
,它在 gapi.load()
和 gapi.client.init
完成时触发,并且在组件中订阅了可观察对象。异步管道似乎不想读取该值,直到我单击同一组件内的一个按钮(并且可能触发更改检测)。当我使用 ApplicationRef
.
tick()
时,在组件内强制更改检测不起作用
正在加载 Google API 的服务如下:
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');
}
}
以及从服务中读取的组件
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');
}
}
生成的页面打印出字符串 What does the service say?
并有一个按钮,但在我单击按钮之前不会打印文本 hello world
,这不是所需的行为,它应该立即在页面上可见。
此外,当我使用 this.gApiService.gapi.subscribe
订阅并登录到控制台时,它会在我期望的时候记录 hello world
。
有谁知道我如何使用异步管道让 hello world
在 authEmitter
触发事件时打印到页面。
我最终找到了这个问题的解决方案,这个问题有几个问题。
首先,EventEmitter
应该只用于发射,不应该被订阅,所以我用 Subject
换掉了它。通常我会尝试使用可观察对象,但在这种情况下,我发现一个主题更合适。
下面是一个使用 Subject
而不是 EventEmitter
的修改后的 plunker。我还注释掉 gapi.client.init...
并用不同的承诺替换它,因为它实际上不是问题的一部分。请注意,在点击按钮之前,plunker 更改检测仍然不会 运行,这不是预期的。
https://plnkr.co/edit/YaFP07N6A4CQ3Zz1SbUi?p=preview
我遇到的问题是因为 gapi.load('client:auth2', this.initClient.bind(this));
运行s initClient
在 Angular 2 区域之外,这将它排除在变化检测之外。
为了在变化检测中捕获主体,我们必须 运行 主体的 next
和 complete
在 Angular 2 区域内调用。这是通过导入 NgZone
然后修改行来完成的:
new Promise((res) => res()).then(() => {
this.zone.run(() => {
this.authSubject.next(true);
this.authSubject.complete(true);
});
});
对于其他为此苦苦挣扎的人,您还需要包装任何其他 gapi 承诺结果。
与上述解决方案类似,这是我一直在使用的解决方案:
export function nonZoneAwarePromiseToObservable(promise: Promise<T>, zone: NgZone): Observable<T> {
return new Observable<T>(subscriber => {
promise
.then((v) => {
zone.run(() => {
subscriber.next(v)
subscriber.complete()
})
})
.catch((error) => {
zone.run(() => {
subscriber.error(error)
})
})
})
}
我是如何使用它的:
const calendar: typeof gapi.client.calendar = _gapi.client.calendar
const $timezone = calendar.settings.get({setting: 'timezone'})
return nonZoneAwarePromiseToObservable($timezone, this.zone)
.pipe(map((timezoneResponse) => timezoneResponse?.result?.value))