问题
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.
回答1:
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.
来源:https://stackoverflow.com/questions/42660643/change-detection-doesnt-run-when-loading-auth2-from-google-api