【问题标题】:Change detection doesn't run when loading auth2 from Google API从 Google API 加载 auth2 时不运行更改检测
【发布时间】:2017-07-28 09:21:27
【问题描述】:

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

我正在使用EventEmitter,它在gapi.load()gapi.client.init 完成时触发,并且在组件中订阅了可观察对象。异步管道似乎不想读取该值,直到我单击同一组件中的一个按钮(并且可能触发更改检测)。当我使用来自ApplicationReftick() 时,强制组件内的更改检测不起作用。

加载 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

有谁知道当authEmitter 触发事件时,我如何使用异步管道让hello world 打印到页面。

【问题讨论】:

  • 您是否尝试将其作为吸气剂直接返回get api$() { return this.service.gapi }。您也可以尝试使用ChangeDetectorRef 来强制进行更改检测。
  • 直接返回是不好的,因为我不希望组件在它准备好之前使用它,它需要做2个网络请求并运行init才能使用它,因此我为什么我'正在使用一个可观察的。我会尝试强制进行更改检测,但我认为这不是一个实用的长期解决方案,因为这通常是我希望可观察对象能够处理的问题。
  • 你知道你可以在async管道准备好之前订阅它,组件将在它发出数据后立即开始使用它。 (正如我所见,您只是在加载后才发出新项目)
  • @cyrix 我知道,但这不是我真正的用例,只是重现问题所需的最少代码量,因为我的实际实现比这更复杂。
  • 我遇到了同样的问题,并得出了与这个问题的唯一答案相同的解决方案。我很想知道这个问题背后的原因。

标签: angular google-api angular2-observables angular2-changedetection google-api-js-client


【解决方案1】:

对于其他为此苦苦挣扎的人,您还需要包装任何其他 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))

【讨论】:

    【解决方案2】:

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

    首先,EventEmitter 应该只用于发射,而不应该被订阅,所以我将它换成了Subject。通常我会尝试使用 observable,但在这种情况下我发现了一个更合适的主题。

    以下是使用Subject 而不是EventEmitter 的修改后的plunker。我还注释掉了gapi.client.init... 并用不同的承诺替换了它,因为它实际上并不是问题的一部分。请注意,在单击按钮之前,plunker 更改检测仍然不会运行,这是不应该的。

    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.

    【讨论】:

      猜你喜欢
      • 2018-01-19
      • 1970-01-01
      • 2012-06-01
      • 2021-05-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多