Skip to content

Cannot assign to read only property ‘winner’ of object ‘[object Object]’

I’m encountering the error

ContestDetailsModalComponent.html:21 ERROR TypeError: Cannot assign to read only property 'winner' of object '[object Object]'
    at ContestDetailsModalComponent.onWinner (contest-details-modal.component.ts:88:24)
    at Object.eval [as handleEvent] (ContestDetailsModalComponent.html:21:17)
    at handleEvent (core.js:43993:77)
    at callWithDebugContext (core.js:45632:1)
    at Object.debugHandleEvent [as handleEvent] (core.js:45247:1)
    at dispatchEvent (core.js:29804:1)
    at core.js:42925:1
    at HTMLButtonElement.<anonymous> (platform-browser.js:2668:1)
    at ZoneDelegate.invokeTask (zone-evergreen.js:399:1)
    at Object.onInvokeTask (core.js:39680:1)

when I try to make the assignment

this.contest.winner = winnerId;

you see in the following code snippet:

public onWinner(winnerId: number): void {
     const post: Post = new Post();
     post.title = `The winner of the contest has been elected: ${this.contest.title}`;
     post.description = `Tizio has been elected winner of the contest and ${this.user.name} gives him the most welcome compliments`;
     post.user = this.user;
     post.contest = this.contest;
     post.type = Type.WINNER;
     post.level = Level.SUCCESS;
     this.contest.winner = winnerId;
     this.store.dispatch(PostActions.savePost({post}));
     this.store.dispatch(ContestActions.saveContest({contest: this.contest}));
}

I’ve done this kind of assignment with other classes around the project and they never bothered me.

I enclose the contest class and post if it is useful:

export class Contest {
  public id: number;
  public title: string;
  public description: string;
  public rules: string;
  public startDate: Date;
  public endDate: Date;
  public status: Status;
  public winner: number;
  public bannedUser: number[];
}


export class Post {
  public id: number;
  public title: string;
  public description: string;
  public level: Level;
  public type: Type;
  public user: User;
  public publishDate: Date;
  public contest: Contest;

}

I also tried some solutions always found on this forum such as:

Object.assign(target, source);

But he rightly tells me that this.contest.winner is null or undefined.

I hope for your help. Thank you

Edit:

This is the entire component.ts where onWinner() is present.

@Component({
  selector: 'app-contest-details-modal',
  templateUrl: './contest-details-modal.component.html',
  styleUrls: ['./contest-details-modal.component.scss'],
})
export class ContestDetailsModalComponent implements OnInit, OnDestroy, AfterViewChecked {
  private readonly subscriptions: Subscription = new Subscription();
  public id: number;
  public user: User;
  public contest: Contest;
  public images: Image[];
  public hasVoted: boolean;

  constructor(
    private readonly bsModalRef: BsModalRef,
    private readonly modalService: BsModalService,
    private readonly store: Store<AppState>,
    private cdRef: ChangeDetectorRef
  ) { }

  public ngOnInit(): void {

    this.subscriptions.add(this.store.pipe(select(AuthSelectors.getUser)).subscribe((user: User) => {
      if (user) {
        this.user = user;
      }
    }));

    this.subscriptions.add(this.store.pipe(select(ContestSelectors.getById)).subscribe((contest: Contest) => {
      if (contest) {
        this.contest = contest;
      }
    }));

    this.subscriptions.add(this.store.pipe(select(ImageSelectors.getImages)).subscribe((images: Image[]) => {
      if (images.length) {
        this.images = images;
      }
    }));

    this.subscriptions.add(this.store.pipe(select(UserSelectors.check)).subscribe((ack: Ack) => {
      if (ack) {
        this.store.dispatch(AuthActions.updatedUser({userId: this.user.id}));
        this.modalService.show(UploadImageModalComponent);
        this.bsModalRef.hide();
      }
    }));

    this.subscriptions.add(this.store.pipe(select(ImageSelectors.check)).subscribe((ack: Ack) => {
      if (ack) {
        this.bsModalRef.hide();
      }
    }));

  }

  public ngOnDestroy(): void {
    this.store.dispatch(UserActions.clean());
    this.store.dispatch(ContestActions.clean());
    this.subscriptions.unsubscribe();
  }

  public onWinner(winnerId: number): void {
    const post: Post = new Post();
    post.title = `The winner of the contest has been elected: ${this.contest.title}`;
    post.description = `Tizio has been elected winner of the contest and ${this.user.name} gives him the most welcome compliments`;
    post.user = this.user;
    post.contest = this.contest;
    post.type = Type.WINNER;
    post.level = Level.SUCCESS;
    this.contest.winner = winnerId;
    this.store.dispatch(PostActions.savePost({post}));
    this.store.dispatch(ContestActions.saveContest({contest: this.contest}));
  }

  public onJoin(): void {
    this.store.dispatch(UserActions.saveUser({idUser: this.user.id, id: this.contest.id}));
  }

  public onVote(image: Image): void {
    let vote: number = image.vote;
    vote = vote + 1;
    this.store.dispatch(ImageActions.updateImage({photoId: image.id, votes: vote, userId: this.user.id}));
    this.store.dispatch(AuthActions.updatedUser({userId: this.user.id}));
  }

  public isContain(contestId: number): boolean {
    if (this.user.myContest) {
      for (const i of this.user.myContest) {
        if (contestId === i) {
          return true;
        }
      }
    }
    return false;
  }

  public isImageVoted(id: number): boolean {
    if (this.user.favouritePhoto) {
      for (const imageVoted of this.user.favouritePhoto) {
            if (id === imageVoted) {
              this.hasVoted = true;
              return true;
            }
      }
      return false;
      }
   }

  public onBan(image: Image): void {
    this.modalService.show(BanModalComponent, {initialState : image});
    this.bsModalRef.hide();
  }

  public isBan(contestId: number): boolean {
    if (this.user.whereBanned) {
      for (const contestBanned of this.user.whereBanned) {
        if (contestId === contestBanned) {
          return true;
        }
      }
      return false;
    }
  }

  public ngAfterViewChecked(): void {
      this.cdRef.detectChanges();
  }
}

Answer

contest contains a reference to an Observable emission, and all references emitted by Observables are readonly. Either clone contest:

this.contest = { ...contest };

Or, better, leave it as an Observable and consume it as such, generally via the async pipe. Also, if you’re using NgRx, you want to be using store.select():

this.contest$ = this.store.select(ContestSelectors.getById);