从Google API加载auth2时,更改检测不会运行

时间:2017-03-08 00:04:48

标签: angular google-api angular2-observables angular2-changedetection gapi

我尝试从Angular 2服务中加载脚本https://apis.google.com/js/api.js?onload=initGapi以调用Google API,但每当我尝试在脚本完成加载和初始化时返回值,async管道并不想将值呈现给模板。

我使用的EventEmittergapi.load()gapi.client.init完成时触发,并且observable在组件中订阅了它。异步管道似乎不想读取值,直到我单击同一组件内的按钮(可能还有火灾变化检测)。当我使用tick()中的ApplicationRef时,强制在组件内进行变更检测并不起作用。

加载Google API的服务如下:

谷歌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触发事件时,是否有人知道如何使用异步管道让quickcutsystem.com/打印到页面。

2 个答案:

答案 0 :(得分:3)

我最终找到了解决这个问题的方法,这个问题有一些问题。

首先,EventEmitter应仅用于发出,而且永远不应该订阅,因此我将其与Subject交换出来。通常我会尝试使用一个可观察的,但在这种情况下,我发现一个主题更合适。

以下是使用Subject代替EventEmitter的修订版的plunker。我还评论了gapi.client.init...并用不同的承诺取而代之,因为它实际上并不是问题的一部分。请注意,在单击按钮时,仍然不会运行更换检测,这是不可取的。

https://plnkr.co/edit/YaFP07N6A4CQ3Zz1SbUi?p=preview

我遇到的问题是因为gapi.load('client:auth2', this.initClient.bind(this));在Angular 2区域之外运行initClient,因此将其从变更检测中排除。

为了在变更检测中捕获主题,我们必须在Angular 2区域内运行主题nextcomplete调用。这是通过导入NgZone然后修改行来完成的:

    new Promise((res) => res()).then(() => {
      this.zone.run(() => {
        this.authSubject.next(true);
        this.authSubject.complete(true);
      });
    });

See the final (resolved) plunker here.

答案 1 :(得分:2)

对于其他为此感到挣扎的人,您还需要包装其他所有空缺的承诺结果。

类似于上面的解决方案,这就是我一直在使用的:

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))