0

I've run into a couple weird change detection issues recently with Angular 7.1 where some components are only partially updating.

I am working on a decision tree for example. The decision tree component determines which decision is current, then passes it to a decision component. This works as expected for the first decision, but on the second step it does update the question, but the bound values in the decision component are not updated.

decision-tree.component.ts

private tree: any[] = [
  {
    id: 'understand_story',
    question: 'Do you understand the intended functionality?',
    type: 'boolean',
    yes: [
      {
        action: 'go-to',
        params: {decision: 'is_story_possible'}
      }
    ],
    no: [
      {
        action: 'update-story',
        params: {blocked: true}
      }
    ]
  },
  {
    id: 'is_story_possible',
    question: 'Is this feature going to be possible?',
    type: 'boolean',
    yes: [
      {
        action: 'go-to',
        params: {decision: 'estimate_story'}
      }
    ],
    no: [
      {
        action: 'update-story',
        params: {blocked: true}
      }
    ]
  },
  {
    id: 'estimate_story',
    question: 'Enter an estimate to complete this story',
    input: 'estimate',
    type: 'text',
    change: [
      {
        action: 'input-value',
        params: {field: 'estimate'}
      },
      {
        action: 'move-story',
        params: {board: 'develop'}
      }
    ]
  }
];

private index = 'understand_story';

get currentDecision() {
  return this.tree.find(d => d.id === this.index);
}

makeDecision(answer) {
  this.decisions.push(answer);
  const decision = this.tree.find(d => d.id === answer.id);

  switch (decision.type) {
    case 'boolean':
      const actions = answer.answer ? decision.yes : decision.no;
      this.processActions(actions);
      break;
    case 'input':
      const actions = answer.change;
      this.processActions(actions);
      break;
  }
}

processActions(actions) {
  actions.forEach(action => {
    switch (action.action) {
      case 'go-to':
        this.index = action.params.decision;
        break;
    }
  });
}

decision-tree.component.html

<app-decision [decision]="currentDecision" (complete)="makeDecision($event)"></app-decision>

When the current decision index is updated the question updates as expected, but the bound values do not. These values are private properties of the decision.component.ts file, so it looks like angular is recycling the component instance.

decision.component.ts

@Input() decision;
@Input() disabled: boolean;
@Input() data;
@Output() complete: EventEmitter<any> = new EventEmitter();

private answer = null;
private comment: string;

decision.component.html

<div class="decision">
  <p class="question">{{decision.question}}</p>
  <div class="answer">
    <mat-radio-group aria-label="Select an option" [(ngModel)]="answer" [disabled]="disabled">
      <mat-radio-button [value]="true">Yes</mat-radio-button>
      <mat-radio-button [value]="false">No</mat-radio-button>
    </mat-radio-group>
    <mat-form-field appearance="outline">
      <textarea matInput placeholder="Add a comment" [(ngModel)]="comment" [disabled]="disabled"></textarea>
    </mat-form-field>
    <button *ngIf="! disabled" mat-flat-button color="primary" (click)="completeDecision()" [disabled]="! valid">Go to next step</button>
  </div>
</div>

Here's a quick screencast of the issue in action: https://www.loom.com/share/301dad2b15294f73892db0788d43b337

ForrestLyman
  • 1,544
  • 2
  • 17
  • 24
  • Are the objects changing at top level? Or is the change occurring at a nested level? I don't believe Angular will recognize a nested property as changed. You may need to manage change detection manually. See this related (possibly duplicate answer) https://stackoverflow.com/questions/44941635/how-to-trigger-a-change-event-manually-angular2?rq=1 – khollenbeck Jun 03 '19 at 17:16
  • @KrisHollenbeck the current decision is changed on the top level decision tree component, which i assume should trigger a re-render of the decision component. I'll update the example to include this code. – ForrestLyman Jun 03 '19 at 17:18
  • Is your component using `onPush`? https://angular.io/api/core/ChangeDetectionStrategy – khollenbeck Jun 03 '19 at 17:21
  • I'm using the default strategy – ForrestLyman Jun 03 '19 at 17:23
  • Looks like the change is triggered via an Array. This -> `[decision]="currentDecision"`. Is that correct? If so maybe try returning a new copy of `currentDecision`. For example `return [].concat(yourNewArray)` – khollenbeck Jun 03 '19 at 17:33
  • Can you check the console? It seems like angular errors out and doesn't update the dom. If that's the case can you paste the error message as well.. – hunterTR Jun 03 '19 at 17:42
  • @hunterTR Those code snippets will not run properly. OP will need to set up a Minimal Reproducible Example (https://stackoverflow.com/help/minimal-reproducible-example) via plunker or some other source. https://plnkr.co/ – khollenbeck Jun 03 '19 at 17:47
  • I am not running the code snippets. I've made an assumption from the screencast and his question. But yes reproducible example would be nice. – hunterTR Jun 03 '19 at 17:50
  • @hunterTR, Apologies. Bad assumption on my part. – khollenbeck Jun 03 '19 at 17:50
  • @KrisHollenbeck It is returning the currentDecision as an object. I did try creating a new object, but that had identical results. I had assumed that it might be related to deep change detection, but its definitely updating the decision component since the question is properly changing. this is an easy fix by manually handling the changes with `ngOnChanges`, but i really want to figure out the underlying issue and avoid manual workarounds. i'll share a full example later today if i can't get it working. – ForrestLyman Jun 03 '19 at 17:59

0 Answers0