Результирующий ТИП Объекта отличается от указанного ТИПА после того, как HTTP-ответ от Firestore обработан Субъектом Поведения в Ionic - PullRequest
0 голосов
/ 12 июня 2019

Моя цель - вызвать строго типизированные данные профиля пользователя из firestore firebase и заполнить шаблон компонента профиля пользователя, используя угловую оболочку приложения, чтобы предварительно заполнить область просмотра, пока данные БД извлекаются и разрешаются с использованием метода углового маршрутизатора resol ()..

Проблема, с которой я столкнулся, заключается в том, что я не могу разобрать данные шаблона в конце процесса, потому что форма объекта данных отличается от ожидаемой.

Я ожидаю:

{
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 обработчику маршрутов и поставщику оболочки перед тем, как закончить пост-обработку в компоненте user-profile.page.В компоненте user-profile.page данные принимаются в виде активированного маршрута (от преобразователя) и проходят через ряд условий для обработки содержимого оболочки на основе обещаний и данных базы данных на основе наблюдаемых.Наконец, результирующая наблюдаемая подписывается и возвращает объект профиля типа UserProfileModel.

Resolver Route и поставщик оболочки. Определитель маршрутов принимает dataObservable и передает его в метод Angular Resolve ().Я не думаю, что здесь происходит что-то еще.

Поставщик оболочки использует объект поведения, который получает shellModel из обещания, которое содержит все предварительно заполненные данные и передается в представление компонента черезкеш оболочки (app-shell).Субъект поведения ожидает, а затем получает dataObservable, который используется для заполнения остальной части шаблона по мере завершения наблюдаемой.ПОЖАЛУЙСТА, ОБРАТИТЕ ВНИМАНИЕ ДЛЯ ЦЕЛЕЙ DEV. НАСТРОЙКА ЗАДЕРЖКИ 2 СЕКУНДЫ ДЛЯ МОДЕЛИРОВАНИЯ ЗАДЕРЖКИ СЕТИ.

Если бы все, что мне нужно было сделать, это создать другую переменную и повторно проанализировать ее в компоненте профиля пользователя, я бы почти былсчастлив в этот момент.Но поскольку данные строго типизированы с использованием типа, все мои параметры синтаксического анализа тесно связаны с тем, что находится в этой модели.ОДНАКО, когда я наблюдаю результирующий объект (в инструментах разработчика Chrome), он имеет тип в разделе «Но я получаю» выше.Я могу видеть этот тип в течение всего процесса, начинающегося с предметного объекта поведения.

Это приложение предназначено для приложений Ionic 4, использующих Angular 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;
```

---------------- распознаватель маршрута ----------------------------------

```
@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;
  }
}
```

---------------------- поставщик оболочки ---------------------------------

```
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'}
   ...
}
```

Я не получаю никаких сообщений об ошибках, что странно.Опять же, если кто-нибудь может помочь мне понять:

  1. Почему я не получаю ожидаемый формат объекта в компоненте user-profile.page, то есть в шаблоне?

  2. Почему я не могу получить доступ к полям: парам значений данных в формате «Но я получаю:»?

Я был бы очень признателен.

1 Ответ

0 голосов
/ 27 июня 2019

После долгих часов попыток решить эту проблему, я наконец-то сделал обновление AngularFire2 и внес несколько других изменений, чтобы оно заработало.Надеюсь, это поможет всем, у кого есть эта проблема:

  1. Сначала мне нужно было обновить до AngularFire ^ 5.2.1
  2. В user.service я внес следующие изменения:
  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) => {}
      );
})
`
...