1

I recently upgraded my app from angular 7 to angular 11. Everything was working fine and then I implemented angular universal to enable server side rendering.

After implementing server side rendering i got lots of error saying "Can't change readonly 'xyz' member of object [Object Object]"

All these members are from object that i have paaased to child component from parent component via @input()

My Questions

  1. Is it wrong to manipulate objects passed as Input
  2. Why is it breaking for Angular universal (server side rendering) and not for client side rendering

Here's one such component

export class BannerComponent {

  @Input() banners : Offer[]
  constructor(private analyticService : AnalyticService) { }

  ngOnChanges() {
    if(this.banners) {
      this.banners.forEach(banner => {
        if(!banner.bannerImage.startsWith("http"))
          banner.bannerImage = environment.imageHost + banner.bannerImage;
      })
    }    
  }

  recordEvent(banner : Offer) {
    this.analyticService.eventEmitter(banner.category.name, "Click on banner", banner.offerDetail + "-" + banner.merchant.name, AnalyticService.AVG_AFFILIATE_CLICK_VALUE);
  }

}

Here's my offer class

import { Store } from "./store";
import { Category } from "./category";

export class Offer {
    
    id: number;
   
    merchant: Store;
   
    offerDetail: string;
   
    link: string;
   
    openExternal: boolean;
   
    logoPath: string;
   
    lastDate: Date;
   
    banner: boolean;
   
    bannerImage: string;

    category : Category;
    offerDescription?: string;
}

Store and Category are another two models

6
  • Are you able to run ng build --aot --prod ? Commented Feb 8, 2021 at 5:28
  • Can you show the definition for the Offer class? Commented Feb 8, 2021 at 8:46
  • @OwenKelvin will let you know by today evening Commented Feb 8, 2021 at 10:45
  • @David see edit Commented Feb 8, 2021 at 10:51
  • Is the banners attribute in the template provided by an Observable? Commented Feb 8, 2021 at 16:45

1 Answer 1

4
+50

Regarding question 1:

The short answer is yes, you should avoid manipulating data from the parent component in child components.

This is because in Angular, data flows from parent to child components, and that data flows unidirectionally, which is somewhat hinted at in the angular docs.

In your case, you are modifying the parent component's data.

Consider this example:

@Component({
   template: `<app-banner-component [banners]="parentBanners"></app-banner-component>`
})
class ParentComponent {
    parentBanners: Offer[] = {...};
}

After ngOnChanges was called, the parentBanners values will also have their bannerImage changed. This is because the array is not copied, but rather passed in by reference, so any changes made to the array in the child component will also apply to the parent component.

Because change detection runs from parent components to child components, this may lead to the dreaded ExpressionChangedAfterItHasBeenChecked error, as explained in this video.

To work around this, make a copy of your array, for example, like this:

@Input() set banners (values: Offer[]) {
    this._banners = values.map(offer => ({...offer}));
}
get banners(): Offer[] {
    return this._banners;
}

private _banners : Offer[];

Regarding 2, I have no experience with Angular Universal. My speculation would be that it enforces data flow much more strictly, and refactoring might resolve the issue.

Sign up to request clarification or add additional context in comments.

1 Comment

Haven't tried it, but found it much convincing

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.