Angular v5 抛出共享数据的 ExpressionChangedAfterItHasBeenCheckedError

Angular v5 throws an ExpressionChangedAfterItHasBeenCheckedError with shared data

我遇到了一个我无法解决的非常烦人的错误。单击 details btn 后,出现“ExpressionChangedAfterItHasBeenCheckedError”。

属性 个列表

属性 详情

=========================================== =

routing.module

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { PropertyComponent } from './property/property.component';
import { ListingComponent } from './property/listing/listing.component';
import { DetailsComponent } from './property/listing/details/details.component';

import { DetailsResolve } from './property/listing/details/details.resolve';

const routes: Routes = [
    {
        path: '',
        redirectTo: '/',
        pathMatch: 'full'
    },
    {
        path: '',
        component: PropertyComponent,
        children: [
            {
                path: '',
                component: ListingComponent
            },
            {
                path: 'listing/:address',
                component: DetailsComponent,
                resolve: {
                    DetailsResolve
                }
            }
        ]
    }
    //{ path: '**', component: PageNotFoundComponent }
];

@NgModule({
    imports: [RouterModule.forRoot(routes)],
    exports: [RouterModule],
    providers: [
        DetailsResolve
    ]
})
export class AppRoutingModule { }

属性 html

<div fxFlex="70" fxLayout fxFill>
    <agm-map class="property__map" [latitude]="map.latitude" [longitude]="map.longitude" [zoom]="map.zoom">
        <agm-marker [latitude]="map.latitude" [longitude]="map.longitude"></agm-marker>
    </agm-map>
</div>
<div class="property__sidenav mat-elevation-z10" fxFlex="30" fxLayout fxFill>
    <router-outlet></router-outlet>
</div>

属性分量

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription }   from 'rxjs/Subscription';
import { UtilityService } from '../shared/utility.service';

@Component({
    selector: 'app-property',
    templateUrl: './property.component.html',
    styleUrls: ['./property.component.scss'],
    host: { 'class': 'property' }
})
export class PropertyComponent implements OnInit, OnDestroy {

    map: object;
    mapSubscription: Subscription;
    latitude: number;
    longitude: number;
    zoom: number;

    constructor(
        private utilityService: UtilityService
    ) {}

    ngOnInit(): void  {
        this.mapSubscription = this.utilityService.defaultMapMarker.subscribe(map => this.map = map);
        console.log('parent - property', this)
    }

    ngOnDestroy() {
        this.mapSubscription.unsubscribe();
    }
}

上市html

<mat-card class="listing" *ngFor="let property of properties.properties">
    <div class="listing__image">
        <img class="listing__image-background" mat-card-image src="../assets/images/properties/1327_s_colorado_st_philadelphia_pa_19146_picture_01.jpg" alt="Photo of a Shiba Inu">
    </div>
    <!--<div class="listing__image">
        <div class="listing__image-background" [style.backgroundImage]="'url('+ property.image[0].url +')'"></div>
    </div>-->
    <mat-card-title>
        {{property.type}} &mdash;
        {{property.price}} / mo.
    </mat-card-title>
    <mat-card-subtitle>
        {{property.address.street}}
        {{property.address.city}}
        {{property.address.state}}
    </mat-card-subtitle>
    <mat-card-content>
        <mat-list fxLayout>
            <mat-list-item fxFlex="20">
                <mat-icon matListIcon>hotel</mat-icon>
                <h4 class="listing__icon-title" matLine>{{property.bed}} beds</h4>
            </mat-list-item>
            <mat-list-item fxFlex="20">
                <mat-icon matListIcon>hot_tub</mat-icon>
                <h4 class="listing__icon-title" matLine>{{property.bath}} bath</h4>
            </mat-list-item>
            <mat-list-item fxFlex="20">
                <mat-icon matListIcon>view_compact</mat-icon>
                <h4 class="listing__icon-title" matLine>{{property.sqft}} sqft.</h4>
            </mat-list-item>
            <mat-list-item fxFlex>
                <mat-icon matListIcon>directions_walk</mat-icon>
                <h4 class="listing__icon-title" matLine>{{property.walkscore}} (Walker's Paradise)</h4>
            </mat-list-item>
        </mat-list>
        <p>{{property.description.short}}</p>
    </mat-card-content>
    <mat-card-actions>
        <button mat-button [routerLink]="['/listing', property.url]">DETAILS</button>
        <button mat-button>SHARE</button>
    </mat-card-actions>
</mat-card>

列出组件

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription }   from 'rxjs/Subscription';
import { UtilityService } from '../../shared/utility.service';
import { PropertyService } from '../property.service';

@Component({
    selector: 'app-listing',
    templateUrl: './listing.component.html',
    styleUrls: ['./listing.component.scss']
})
export class ListingComponent implements OnInit, OnDestroy {

    properties: any;
    propertiesSubscription: Subscription;
    map: object;

    constructor(
        private utilityService: UtilityService,
        private propertyService: PropertyService
    ) {}

    ngOnInit() {
        this.getProperties();
        console.log('child - listing', this)
    }

    ngOnDestroy() {
        this.propertiesSubscription.unsubscribe();
    }

    getProperties() {
        this.properties = [];
        this.propertiesSubscription = this.propertyService.getProperties().subscribe(properties => this.properties = properties);
    }
}

详情html

<p>
  details works!
</p>

详情组件

import { Component, OnInit, AfterViewInit } from '@angular/core';
import { ActivatedRoute } from "@angular/router";
import { Meta, Title } from '@angular/platform-browser';
import { UtilityService } from '../../../shared/utility.service';

@Component({
    selector: 'app-details',
    templateUrl: './details.component.html',
    styleUrls: ['./details.component.sass']
})
export class DetailsComponent implements OnInit, AfterViewInit  {

    propertyDetails: any;

    constructor(
        private meta: Meta,
        private title: Title,
        private route: ActivatedRoute,
        private utilityService: UtilityService
    ) {
        title.setTitle('Davis');
        meta.addTags([
            { name: 'author', content: '' },
            { name: 'keywords', content: '' },
            { name: 'description', content: '' }
        ]);
    }

    ngOnInit() {
        this.propertyDetails = this.route.snapshot.data
        this.mapCoordinates();
        //console.log('details - child', this)
    }

    mapCoordinates() {
        let map = this.propertyDetails.DetailsResolve.map;

        //property listing location
        let coordinates = {
            latitude: map.latitude,
            longitude: map.longitude,
            zoom: map.zoom
        };

        //update map in parent
        return this.utilityService.onUpdateMapMarker(coordinates)
    }
}

详情解析

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Resolve } from '@angular/router';
import { ActivatedRouteSnapshot } from '@angular/router';
import { DetailsService } from './details.service';

@Injectable()
export class DetailsResolve implements Resolve<any> {

    propertyDetails: any;

    constructor(
        private detailsService: DetailsService
    ) { }

    resolve(route: ActivatedRouteSnapshot) {
        let propertyUrl = route.params.address;

        return this.detailsService.getPropertyDetails().then(details => {
            let propertyDetails = details['details'];

            for (let index = 0, len = propertyDetails.length; index < len; index++) {
                let property = propertyDetails[index];

                //check which property listing 
                if (property.url === propertyUrl) {
                    this.propertyDetails = property;
                }
            }

            return this.propertyDetails;
        });
    }
}

属性 服务

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable()
export class PropertyService {

    constructor(
        private http: HttpClient
    ) { }

    getProperties() {
       return this.http.get('api/mock-property.json');
    }

}

公用事业服务

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';

@Injectable()
export class UtilityService {
    private mapMarker = new BehaviorSubject<object>({
        latitude: 39.9525839,
        longitude: -75.16522150000003,
        zoom: 10
    });
    defaultMapMarker = this.mapMarker.asObservable();

    onUpdateMapMarker(coordinates: object) {
        console.log('UtilityService - coordinates', coordinates)
        this.mapMarker.next(coordinates);
    }
}

想办法!两件事解决了我的问题。

  1. ChangeDetectorRef
  2. listing component 中为默认地图坐标放置另一个 emit 方法反对 UtilityService

这是我的更改:

公用事业服务

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';

@Injectable()
export class UtilityService {
    private mapMarker = new BehaviorSubject<object>({});
    defaultMapMarker = this.mapMarker.asObservable();

    onUpdateMapMarker(coordinates: object) {
        this.mapMarker.next(coordinates);
    }
}

属性 分量

import { Component, OnInit, OnDestroy, OnChanges, ChangeDetectorRef } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
import { UtilityService } from '../shared/utility.service';

@Component({
    selector: 'app-property',
    templateUrl: './property.component.html',
    styleUrls: ['./property.component.scss'],
    host: { 'class': 'property' }
})
export class PropertyComponent implements OnInit, OnDestroy {

    map: object;
    mapSubscription: Subscription;

    constructor(
        private utilityService: UtilityService,
        private changeDetectorRef: ChangeDetectorRef
    ) { }

    ngOnInit() {
        this.mapCoordinates();
    }

    mapCoordinates() {
        return this.mapSubscription = this.utilityService.defaultMapMarker.subscribe(map => {
            this.map = map;
            this.changeDetectorRef.detectChanges();
        });
    }

    ngOnDestroy() {
        this.mapSubscription.unsubscribe();
    }
}

列表组件

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
import { UtilityService } from '../../shared/utility.service';
import { PropertyService } from '../property.service';

@Component({
    selector: 'app-listing',
    templateUrl: './listing.component.html',
    styleUrls: ['./listing.component.scss']
})
export class ListingComponent implements OnInit, OnDestroy {

    properties: any;
    propertiesSubscription: Subscription;
    map: object;
    mapSubscription: Subscription

    constructor(
        private utilityService: UtilityService,
        private propertyService: PropertyService
    ) { }

    ngOnInit() {
        //listing all properties 
        this.getProperties();

        //Set default map coordinates
        this.defaultMapCoordinates();

        //Subscribe to latest map coordinates 
        this.mapSubscription = this.utilityService.defaultMapMarker.subscribe((map: object) => this.map = map);
    }

    ngOnDestroy() {
        this.propertiesSubscription.unsubscribe();
        this.mapSubscription.unsubscribe();
    }

    defaultMapCoordinates() {
        let defaultMapCoordinates = {
            latitude: 39.9525839,
            longitude: -75.16522150000003,
            zoom: 16
        };

        //emit new map coordinates
        return this.utilityService.onUpdateMapMarker(defaultMapCoordinates);
    }

    getProperties() {
        this.properties = [];
        this.propertiesSubscription = this.propertyService.getProperties().subscribe(properties => this.properties = properties);
    }
}

详情组件

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from "@angular/router";
import { Meta, Title } from '@angular/platform-browser';
import { UtilityService } from '../../../shared/utility.service';

@Component({
    selector: 'app-details',
    templateUrl: './details.component.html',
    styleUrls: ['./details.component.sass']
})
export class DetailsComponent implements OnInit {

    propertyDetails: any;
    map: object;

    constructor(
        private meta: Meta,
        private title: Title,
        private route: ActivatedRoute,
        private utilityService: UtilityService
    ) {
        title.setTitle('Davis');
        meta.addTags([
            { name: 'author', content: '' },
            { name: 'keywords', content: '' },
            { name: 'description', content: '' }
        ]);
    }

    ngOnInit() {
        this.propertyDetails = this.route.snapshot.data
        this.emitNewMapCoordinates();
    }

    emitNewMapCoordinates() {
        let map = this.propertyDetails.DetailsResolve.map;

        //property listing location
        let coordinates = {
            latitude: map.latitude,
            longitude: map.longitude,
            zoom: map.zoom
        };

        //emit new map coordinates
        return this.utilityService.onUpdateMapMarker(coordinates);        
    }
}

您可以使用 OnPush 变化检测

import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: 'app-listing',
  templateUrl: './listing.component.html',
  styles: ['./property.component.scss']
})

所以你可以避免这个陷阱,请试试这个。 谢谢