Change detection doesn't run when loading auth2 from Google API

不想你离开。 提交于 2019-12-24 00:09:34

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!