我的目标是从firebase firestore调用紧密类型的用户配置文件数据,并使用angular app-shell填充用户配置文件组件模板,以便在使用angular router resolve()方法检索和解析db数据时预填充视口。
我遇到的问题是我无法在流程结束时解析模板数据,因为数据对象的形状与预期的不同。
我期待:
{
displayName: 'some name',
photoUrl: 'some url',
uid: 'some user id'
...
}
但是我得到了:
{name: 'projects/projectName/database/(default)/documents/users/uid',
fields:
displayName: {stringValue: 'some name'}
photoUrl: {stringValue: 'some url'}
uid: {stringValue: 'some user id'}
...
}
我正在使用数据模型()来在用户注册并在用户访问其配置文件时检索数据时填充数据库。我正在使用“用户服务”从数据库获取数据并填充getProfileDataWithShell函数,该函数将dataObservable传递给路由解析器和shell提供程序,然后在user-profile.page组件上结束后处理。在user-profile.page组件中,数据作为激活路径(来自解析器)接收,并通过一系列条件来处理基于promise的shell东西和基于可观察的db数据。最后,生成的observable被订阅并返回UserProfileModel类型的profile对象。
Route Resolver和Shell Provider路由解析器正在获取dataObservable并将其传递给Angular Resolve()方法。我不认为这里会发生太多其他事情。
shell提供程序使用行为主题,该主题从包含所有预先填充的数据的promise获取shellModel,并通过shell缓存(app-shell)传递给组件视图。行为主体等待然后接收dataObservable,用于在observable完成时填充模板的其余部分。请注意,对于DEV目的,有一个可配置的2秒延迟来模拟网络延迟。
如果我所要做的就是创建另一个变量并在用户配置文件组件中重新解析它,我现在几乎感到高兴。但由于数据是使用类型紧密输入的,因此我所有的解析选项都与该模型中的内容密切相关。但是,当我观察生成的对象(在Chrome开发工具中)时,它就是上面“我正在获取”部分中的类型。我可以从行为主题对象开始的整个过程中看到这种类型。
这是和Ionic 4 App一起使用角度7,Firebase 6和RXJS 6坐在Cordova Android之上。在这个阶段,Android平台是不合适的,这意味着上述内容确实会影响Android版本,反之亦然。任何帮助将不胜感激。
------------------用户档案模型-----------------------
```export class UserProfileModel {
uid: string;
email: string;
photoUrl?: string;
displayName?: string;
membership?: string;
job?: string;
likes?: string;
followers?: string;
following?: string;
about?: string;
constructor(readonly isShell: boolean) { }
}
```
------------------- user.service.ts ------------------------
```
@Injectable()
export class UserService {
private _profileDataWithShellCache: ShellProvider<UserProfileModel>;
constructor(private http: HttpClient, private afAuth: AngularFireAuth) { }
public getProfileDataWithShell(): Observable<UserProfileModel> {
this.userId = this.afAuth.auth.currentUser.uid;
// Use cache if we have it.
if (!this._profileDataWithShellCache) {
// Initialize the model specifying that it is a shell model
const shellModel: UserProfileModel = new UserProfileModel(true);
const dataObservable = this.http.get<UserProfileModel>(this.baseFsAPIUrl + this.userId + apiKey);
this._profileDataWithShellCache = new ShellProvider(
shellModel,
dataObservable
);
}
return this._profileDataWithShellCache.observable;
```
---------------- route resolve -------------------------------- -
```
@Injectable()
export class UserProfileResolver implements Resolve<any> {
constructor(private userService: UserService) { }
resolve() {
// Get the Shell Provider from the service
const shellProviderObservable = this.userService.getProfileDataWithShell();
// Resolve with Shell Provider
const observablePromise = new Promise((resolve, reject) => {
resolve(shellProviderObservable);
});
return observablePromise;
}
}
```
---------------------- shell提供者-------------------------- -------
```
import { Observable, BehaviorSubject, forkJoin, of } from 'rxjs';
import {first, delay, finalize, take} from 'rxjs/operators';
import { environment } from '../../environments/environment';
export class ShellProvider<T> {
private _observable: Observable<T>;
private _subject: BehaviorSubject<T>;
private networkDelay = (environment && environment.shell && environment.shell.networkDelay) ? environment.shell.networkDelay : 0;
// To debug shell styles, change configuration in the environment.ts file
private debugMode = (environment && environment.shell && environment.shell.debug) ? environment.shell.debug : false;
constructor(shellModel: T, dataObservable: Observable<T>) {
// tslint:disable-next-line:max-line-length
const shellClassName = (shellModel && shellModel.constructor && shellModel.constructor.name) ? shellModel.constructor.name : 'No Class Name';
// tslint:disable-next-line:no-console
console.time('[' + shellClassName + '] ShellProvider roundtrip - first one on BS shellModel');
// Set the shell model as the initial value
this._subject = new BehaviorSubject<T>(shellModel);
dataObservable.pipe(
take(1), // Prevent the need to unsubscribe because .first() completes the observable
// finalize(() => console.log('dataObservable COMPLETED'))
);
const delayObservable = of(true).pipe(
delay(this.networkDelay),
// finalize(() => console.log('delayObservable COMPLETED'))
);
// Put both delay and data Observables in a forkJoin so they execute in parallel so that
// the delay caused (on purpose) by the delayObservable doesn't get added to the time the dataObservable takes to complete
const forkedObservables = forkJoin(
delayObservable,
dataObservable
)
.pipe(
// finalize(() => console.log('forkedObservables COMPLETED'))
)
.subscribe(([delayValue, dataValue]: [boolean, T]) => {
if (!this.debugMode) {
this._subject.next(dataValue);
// tslint:disable-next-line:no-console
console.timeEnd('[' + shellClassName + '] ShellProvider roundtrip');
}
});
this._observable = this._subject.asObservable();
}
public get observable(): Observable<T> {
return this._observable;
}
}
```
---------------------------- user-profile.page组件---------------- ----
```
import { Component, OnInit, HostBinding } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { UserProfileModel } from './user-profile.model';
@Component({
selector: 'app-user-profile',
templateUrl: './user-profile.page.html',
styleUrls: [
'./styles/user-profile.page.scss',
'./styles/user-profile.shell.scss',
'./styles/user-profile.ios.scss',
'./styles/user-profile.md.scss'
],
})
export class UserProfilePage implements OnInit {
profile: UserProfileModel;
@HostBinding('class.is-shell') get isShell() {
return (this.profile && this.profile.isShell) ? true : false;
}
constructor(private route: ActivatedRoute) { }
ngOnInit(): void {
if (this.route && this.route.data) {
// We resolved a promise for the data Observable
const promiseObservable = this.route.data;
console.log('Route Resolve Observable => promiseObservable: ', promiseObservable);
if (promiseObservable) {
promiseObservable.subscribe(promiseValue => {
const dataObservable = promiseValue['data'];
console.log('Subscribe to promiseObservable => dataObservable: ', dataObservable);
if (dataObservable) {
dataObservable.subscribe(observableValue => {
const pageData: UserProfileModel = observableValue;
// tslint:disable-next-line:max-line-length
console.log('Subscribe to dataObservable (can emmit multiple values) => PageData (' + ((pageData && pageData.isShell) ? 'SHELL' : 'REAL') + '): ', pageData);
// As we are implementing an App Shell architecture, pageData will be firstly an empty shell model,
// and the real remote data once it gets fetched
if (pageData) {
this.profile = pageData;
}
});
} else {
console.warn('No dataObservable coming from Route Resolver promiseObservable');
}
});
} else {
console.warn('No promiseObservable coming from Route Resolver data');
}
} else {
console.warn('No data coming from Route Resolver');
}
}
}
```
-------------------- user-profile.page模板------------------------ ----
```
<ion-header no-border>
<ion-toolbar>
<ion-buttons slot="start">
<ion-menu-button></ion-menu-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content class="user-profile-content">
<ion-row class="user-details-section">
<ion-col class="user-image-wrapper">
<app-aspect-ratio [ratio]="{w: 1, h: 1}">
<app-image-shell class="user-image" animation="spinner" [src]="profile.photoUrl"></app-image-shell>
</app-aspect-ratio>
</ion-col>
<ion-col class="user-info-wrapper">
<ion-row class="user-data-row">
<ion-col size="9">
<h3 class="user-name">
<app-text-shell [data]="profile.displayName"></app-text-shell>
</h3>
<h5 class="user-title">
<app-text-shell [data]="profile.job"></app-text-shell>
</h5>
</ion-col>
<ion-col class="membership-col">
<span class="user-membership">
<app-text-shell [data]="profile.membership"></app-text-shell>
</span>
</ion-col>
</ion-row>
<ion-row class="actions-row">
<ion-col class="main-actions">
<ion-button class="call-to-action-btn" size="small" color="primary">Follow</ion-button>
<ion-button class="call-to-action-btn" size="small" color="medium">Message</ion-button>
</ion-col>
<ion-col class="secondary-actions">
<ion-button class="more-btn" size="small" fill="clear" color="medium">
<ion-icon slot="icon-only" name="more"></ion-icon>
</ion-button>
</ion-col>
</ion-row>
</ion-col>
</ion-row>
<ion-row class="user-stats-section">
<ion-col class="user-stats-wrapper" size="4">
<span class="stat-value">
<app-text-shell [data]="profile.likes"></app-text-shell>
</span>
<span class="stat-name">Likes</span>
</ion-col>
<ion-col class="user-stats-wrapper" size="4">
<span class="stat-value">
<app-text-shell [data]="profile.followers"></app-text-shell>
</span>
<span class="stat-name">Followers</span>
</ion-col>
<ion-col class="user-stats-wrapper" size="4">
<span class="stat-value">
<app-text-shell [data]="profile.following"></app-text-shell>
</span>
<span class="stat-name">Following</span>
</ion-col>
</ion-row>
<div class="user-about-section">
<h3 class="details-section-title">About</h3>
<p class="user-description">
<app-text-shell animation="bouncing" lines="4" [data]="profile.about"></app-text-shell>
</p>
</div>
</ion-content>
```
```
I am expecting:
{
displayName: 'some name',
photoUrl: 'some url',
uid: 'some user id'
...
}
But I'm getting:
{name: 'projects/projectName/database/(default)/documents/users/uid',
fields:
displayName: {stringValue: 'some name'}
photoUrl: {stringValue: 'some url'}
uid: {stringValue: 'some user id'}
...
}
```
我没有得到任何奇怪的错误消息。如果有人能帮助我理解:
我将不胜感激。
发布于 2019-06-27 10:05:16
经过几个小时试图解决这个问题后,我终于做了一个AngularFire2更新,并进行了一些其他更改以使其正常工作。希望这将有助于解决此问题的任何人:
public getListingDataSource(): Observable<Array<UserProfileModel>> {
return this.afs.collection<UserProfileModel>('users').valueChanges({ idField: 'id' });
}`
3. In the route.resolver
`export class FirebaseListingResolver implements Resolve<any> {
constructor(private firebaseService: FirebaseService) {}
resolve() {
const dataSource: Observable<Array<UserProfileModel>> = this.userService.getListingDataSource();
const dataStore: DataStore<Array<UserProfileModel>> = this.userService.getListingStore(dataSource);
return dataStore;
}
}`
4. Finally in the user profile component
`
listing: Array<UserProfileModel>;
ngOnInit() {
this.route.data.subscribe((resolvedRouteData) => {
const listingDataStore = resolvedRouteData['data'];
listingDataStore.state.subscribe(
(state) => {
this.listing = state;
},
(error) => {}
);
})
`
https://stackoverflow.com/questions/-100007060
复制相似问题