Maximilian Ebel
Maximilian Ebel

Reputation: 11

Angular change detection triggered by dummy method?

Hello, I try to understand the change detection of angular.

I have a parent component my-editable for inline text edit and a child component my-input with a styled input element. I'm working with reactive forms which is piped from parent to child component.
Now if the output property status from child component emit a new event, my parent component call its method dummy() without any expression.

What me confused is that the change detection in parent obviously triggered!
If I remove the line (status)="dummy()" from my-editable.component.html the error message (<small class="error">{{ formControl.errors.msg }}</small>) in template wouldn't be shown.
In my opinion, the change detection should be triggered by (status)="cdr.detectChanges()" or something similar.

Can anyone explain it to me?

my-input.component.ts
@Component({
  selector: 'my-input',
  ...
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class MyInput implements OnInit, OnDestroy {

  ...

  @Input('control')
  set control(control: AbstractControl) {
    this.formControl = control as FormControl;
    this.formGroup = control.parent;
  }

  @Output('status') statusOutput = new EventEmitter<string>();
  set status(status: string) {
    if (status) {
      this.statusOutput.emit(status);
      this.statusClass = status.toLowerCase();
      this.cdr.detectChanges();
    } else {
      this.statusClass = false;
    }
  }

  public statusClass: false | string;

  public formGroup: FormGroup | FormArray;
  public formControl: FormControl;

  ...

  constructor(
    ...
    private cdr: ChangeDetectorRef
  ) {...}

  public ngOnInit(): void {
    this.status = this.formControl.status;
    this.updateOnStatusChange();
    ...
  }

  ...

  private updateOnStatusChange(): void {
    this.formControl.statusChanges
      .pipe(pairwise(), untilDestroyed(this))
      .subscribe(([lastStatus, status]) => {
        if (status !== lastStatus) {
          this.status = status;
        }
      });
  }
}
my-input.component.html
<i [ngClass]="statusClass" *ngIf="statusClass"></i>
<input [formControl]="formControl" [placeholder]="placeholder">
my-editable.component.ts
@Component({
  selector: 'my-editable',
  ...
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class MyEditableComponent {

  ...

  @Input('control')
  set control(control: AbstractControl) {
    this.formControl = control as FormControl;
  }

  ...

  public formControl: FormControl;

  ...

  public dummy() {} // <<< MAGIC

  ...

}
my-editable.component.html
...

<ng-container [ngSwitch]="currentMode">

  <ng-template [ngSwitchCase]="EditModes.READONLY">
    <p>{{ formControl.value }}</p>
  </ng-template>

  <ng-template [ngSwitchCase]="EditModes.EDITABLE">
    <div class="wrapper">
      <p>{{ formControl.value }}</p>
      <button (click)="mode = EditModes.EDIT">
        <i class="edit"></i>
      </button>
    </div>
  </ng-template>

  <ng-template [ngSwitchCase]="EditModes.EDIT">
    <my-input
      (status)="dummy()" <!-- MAGIC -->
      [control]="formControl"
      ...
    ></my-input>
  </ng-template>

</ng-container>

<ng-container [ngSwitch]="formControl.invalid && formControl.touched">

  <ng-template [ngSwitchCase]="true">
    <small class="error">{{ formControl.errors.msg }}</small>
  </ng-template>

  <ng-template [ngSwitchCase]="false">
    <small class="description">{{ description }}</small>
  </ng-template>

</ng-container>

Upvotes: 0

Views: 423

Answers (1)

Andrei Gătej
Andrei Gătej

Reputation: 11979

Just watched this awesome talk yesterday.

You should find your answer somewhere between 14:30 and 17:00.

TLDR: when you have OnPush, change detection will trigger when @Input data changes, but also when internal events occur.

Upvotes: 0

Related Questions