Angular v5使用共享数据抛出ExpressionChangedAfterItHasBeenCheckedError

时间:2017-12-17 22:38:18

标签: angular typescript angular5

我遇到了一个非常恼人的错误,我无法弄清楚。单击details btn后,将出现“ExpressionChangedAfterItHasBeenCheckedError”。

房产列表

property listing

属性详情

property listing details

============================================== < / p>

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 { }

property 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);
    }
}

2 个答案:

答案 0 :(得分:4)

搞清楚!解决我的问题的两件事。

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

    以下是我的更改:

    公用事业服务

    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);        
        }
    }
    

答案 1 :(得分:4)

您可以使用OnPush更改检测

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

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