简体   繁体   中英

Angular Change Detection not happening on Lazy loaded pages on initial navigation

I am currently using Angular 13 and Ionic v6 with AngularFire v7.

After logging in to the application with firebase authentication. I navigate the user to the lazy-loaded home page. In the background, I retrieve some required company data and share them through the service using a behaviour subject. If the user has never logged in before the page gets stuck on loading.

If I resize the window the content appears or if I navigate away and come back the content appears. This happens with each lazy-loaded page after initial login. I use the async pipe to access the observables so all that is managed by angular.

I have tried to manually detect changes and tried multiple versions of observables and strategies but nothing works.

Here is a demo of the issue

Ts file

import { Component, OnInit } from '@angular/core';
import { IonRouterOutlet } from '@ionic/angular';
import { Observable, of } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { AddEstimatePage } from './add-estimate/add-estimate.component';
import { Company } from '../models/company.model';
import { MasterService } from '../services/master.service';
import { Estimate } from '../models/estimate.model';
@Component({
  selector: 'app-estimates',
  templateUrl: './estimates.page.html',
})
export class EstimatesPage implements OnInit {
  estimates$: Observable<Estimate[] | any>;
  company$: Observable<Company>;
  user$: Observable<any>;
  isLoading = true;
  constructor(private masterSvc: MasterService) {
    this.company$ = this.masterSvc.auth().company$;
    this.user$ = this.masterSvc.auth().user$;
  }

  ngOnInit() {
    this.init();
  }

  async editEstimate(
    estimate: Estimate,
    data: { company: Company; user: any }
  ) {
    const modal = await this.masterSvc.modal().create({
      component: AddEstimatePage,
      componentProps: {
        company: data.company,
        user: data.user,
        estimate,
        isEdit: true,
      },
      showBackdrop: false,
      id: 'editEstimate',
      cssClass: 'fullscreen',
    });
    return await modal.present();
  }

  async addEstimate(data: { company: Company; user: any }) {
    const modal = await this.masterSvc.modal().create({
      component: AddEstimatePage,
      componentProps: {
        company: data.company,
        user: data.user,
      },
      cssClass: 'fullscreen',
      showBackdrop: false,
      id: 'addEstimate',
    });
    return await modal.present();
  }

  init() {
    this.estimates$ = this.company$.pipe(
      switchMap((company) => {
        if (company) {
          return this.masterSvc
            .edit()
            .getDocsByCompanyIdOrdered(
              `company/${company.id}/estimates`,
              'date',
              'desc'
            );
        } else {
          return of(false);
        }
      })
    ) as Observable<any>;
  }
}

html file

<app-header title="Estimates" btnName="refresh" (updated)="init()"></app-header>
<ion-content fullscreen="true">
  <app-header-condensed title="Estimates"></app-header-condensed>
  <ng-container *ngIf="{company: company$ |async, user: user$ | async} as data">
    <app-estimate-table
      [value]="estimates$"
      *ngIf="estimates$ | async else loading"
      (selectedItem)="editEstimate($event,data)"
    ></app-estimate-table>

    <ion-fab class="m-2" vertical="bottom" horizontal="end" slot="fixed">
      <ion-fab-button (click)="addEstimate(data)">
        <ion-icon name="add"></ion-icon>
      </ion-fab-button>
    </ion-fab>
    <ng-template #loading>
      <div class="ion-padding">
        <app-skeleton-text></app-skeleton-text>
        <app-skeleton-text></app-skeleton-text>
        <app-skeleton-text></app-skeleton-text>
        <app-skeleton-text></app-skeleton-text>
      </div>
    </ng-template>
  </ng-container>
</ion-content>

Update

I got it working with the following. It's a bit questionable but it works:)

Ts file

import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { Observable, of, timer } from 'rxjs';
import { concatMap, filter, switchMap, tap } from 'rxjs/operators';
import { SiteFormComponent } from '../components/site-form/site-form.component';
import { AddEstimatePage } from '../estimates/add-estimate/add-estimate.component';
import { Company } from '../models/company.model';
import { Estimate } from '../models/estimate.model';
import { Site } from '../models/site.model';
import { MasterService } from '../services/master.service';
import { AddSiteComponent } from './add-site/add-site.component';

@Component({
  selector: 'app-sites',
  templateUrl: './sites.page.html',
})
export class SitesPage implements OnInit {
  sites$: Observable<Site[] | any>;
  company$: Observable<Company>;
  user$: Observable<any>;
  isLoading = true;
  constructor(
    private masterSvc: MasterService,
    private change: ChangeDetectorRef
  ) {
    this.company$ = this.masterSvc.auth().company$;
    this.user$ = this.masterSvc.auth().user$;
  }

  ngOnInit() {
    this.init();
  }

  async viewSite(siteData: Site, data: { company: Company; user: any }) {
    const modal = await this.masterSvc.modal().create({
      component: AddSiteComponent,
      componentProps: {
        company: data.company,
        user: data.user,
        siteData,
        isEdit: true,
      },
      showBackdrop: false,
      id: 'editSite',
      cssClass: 'fullscreen',
    });
    return await modal.present();
  }

  async addSite(data: { company: Company; user: any }) {
    const modal = await this.masterSvc.modal().create({
      component: AddSiteComponent,
      componentProps: {
        company: data.company,
        user: data.user,
        isCreate: true,
      },
      cssClass: 'fullscreen',
      showBackdrop: false,
      id: 'addSite',
    });
    return await modal.present();
  }

  init() {
    this.sites$ = this.company$.pipe(
      switchMap((company) => {
        if (company) {
          return this.masterSvc
            .edit()
            .getDocsByCompanyIdOrdered(
              `company/${company.id}/sites`,
              'code',
              'desc'
            )
            .pipe(
              tap(() => {
                this.isLoading = false;
                this.change.detectChanges();
              })
            );
        } else {
          return timer(1);
        }
      })
    ) as Observable<any>;
  }
}

html file

<app-header title="Sites" btnName="refresh" (updated)="init()"></app-header>
<ion-content fullscreen="true">
  <app-header-condensed title="Sites"></app-header-condensed>
  <ng-container
    *ngIf="{company: company$ |async, user: user$ | async,sites: sites$ | async} as data"
  >
    <app-site-table
      [value]="sites$"
      *ngIf="!isLoading else loading"
      (selectedItem)="viewSite($event,data)"
    ></app-site-table>

    <ion-fab class="m-2" vertical="bottom" horizontal="end" slot="fixed">
      <ion-fab-button (click)="addSite(data)">
        <ion-icon name="add"></ion-icon>
      </ion-fab-button>
    </ion-fab>
    <ng-template #loading>
      <div class="ion-padding">
        <app-skeleton-text></app-skeleton-text>
        <app-skeleton-text></app-skeleton-text>
        <app-skeleton-text></app-skeleton-text>
        <app-skeleton-text></app-skeleton-text>
      </div>
    </ng-template>
  </ng-container>
</ion-content>

Yesterday I have handled this problem and you can find a complete description on github.com/ionic-team

Resume: There was a bug in @ionic/angular and you have to update it:

npm i @ionic/angular@latest

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM