Angularfire 2。无法读取未定义的属性“订阅”

时间:2019-03-05 13:48:01

标签: angular angularfire2

我正在尝试将组件中的逻辑与服务分离,它可以工作,但是仍然出现控制台错误。

我正在构建此服务并将其用于我的组件中

@Injectable({
  providedIn: 'root'
})
export class DatabaseService {
  public businessDoc: AngularFirestoreDocument<Business>;
  public business: Observable<Business>;

  constructor(private auth: AngularFireAuth, private db: AngularFirestore) {
    this.auth.authState.subscribe(user => {
      this.uid = user.uid;
      this.businessDoc = this.db.doc<Business>(this.businessAddress);
      this.business = this.businessDoc.valueChanges()
    }
  }
  private get businessAddress() {
    return 'users/' + this.uid
  }
}

这是我的组成部分

export class ConfigComponent implements OnInit {

  config: Config;

  constructor(public db: DatabaseService) {
    this.db.business.subscribe(res => {
      this.config = res.config;
    });
  }

最后,在我的模板中

<input [ngModel]="config?.regular" (ngModelChange)="onConfigChange($event)">

它可以毫无问题地编译,因为您甚至可以在视图中看到它的正确渲染,但是随后在浏览器控制台中我得到了:

Console error

如果我在服务中初始化可观察到的业务,例如public business: Observable<Business> = new Observable(),就不会再出现错误,但是组件不会显示任何内容

据我了解,business在服务中尚不存在,因为它正在等待businessDoc连接或它自己的“ valueChanges”,因此当组件尝试访问它时,它的确是未定义的;这就是为什么初始化它可以解决错误日志,但又弄乱了视图的原因。

执行此操作的正确方法是什么?谢谢!

编辑#1:

当我将subscribe从组件构造函数移动到ngOnInit时,它将停止渲染

enter image description here

编辑#2:

我开始尝试一些事情,包括打开Firestore,因此删除了我订阅authState的行,它开始起作用。这不适用于生产环境,但我认为问题出在我的auth订阅中,而不是我最初遇到的困难

constructor(private auth: AngularFireAuth, private db: AngularFirestore) {
// this.auth.authState.subscribe(user => {
    this.uid = "twCTRUpXvYRiYGknXn6j7fe0mvj2";
    this.businessDoc = db.doc<Business>(this.businessAddress);
    this.business = this.businessDoc.valueChanges()
 // });
}

private get businessAddress() {
    return 'users/' + this.uid
}

2 个答案:

答案 0 :(得分:1)

问题在于编写的代码期望authState同步解析。该调用是异步的,因此,调用在解析后设置为之后的状态的调用将返回undefined(或任何默认值/初始值)。

有很多方法可以解决此问题,我选择的一种方法是向服务中添加initialize方法,该方法返回可被组件预订的可观察对象。 authState解决后,此问题即可解决。我使用了shareReplay,因此该调用只能成功解析一次,然后将结果重播到任何后续订阅。 tap运算符允许在服务上设置状态,而无需直接预订可观察对象。


DatabaseService.ts

import { tap, shareReplay } from 'rxjs/operators';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class DatabaseService {
  public businessDoc: AngularFirestoreDocument<Business>;
  public business: Observable<Business>;
  private readonly _initialize: Observable<any>;

  constructor(private auth: AngularFireAuth, private db: AngularFirestore) {
    this._initialize = this.auth.authState.pipe(tap(user => {
        this.uid = user.uid;
        this.businessDoc = this.db.doc<Business>(this.businessAddress);
        this.business = this.businessDoc.valueChanges()
    }), shareReplay());
  }

  initialize() : Observable<any> {
    return this._initialize;
  }

  private get businessAddress() {
    return 'users/' + this.uid
  }
}

ConfigComponent.ts

export class ConfigComponent implements OnInit {

  config: Config;

  constructor(private readonly db: DatabaseService){ }

  ngOnInit() {
    this.db.initialize().subscribe(() => {
      this.db.business.subscribe(res => {
        this.config = res.config;
      });
    });
  }

答案 1 :(得分:0)

不知道出了什么问题,但让我向您建议另一种解决方法,也许可行:

服务:

@Injectable({
  providedIn: 'root'
})
export class DatabaseService {
  private businessAddress: string = "business"

  public businessDoc = this.af.doc<Business>(this.businessAddress);
  public business = this.businessDoc.valueChanges();

  constructor(readonly af: AngularFirestore) {}
}

组件:

export class ConfigComponent implements OnInit {

  config$: Observable<Config> = this.db.business.pipe(
    map((res) => res && res.config)
  );

  constructor(readonly db: DatabaseService) {}
}

模板:

<input [ngModel]="(config$ | async)?.regular" (ngModelChange)="onConfigChange($event)">