13

Edit: I just don't know why, but change detection stop at the first child in the hierarchy. If I manually invoke change detection one level deeper (in sch-job-detail), then the values are updated.

I've built a MatTable with expandable rows.
The "expandable" row part is as follow:

<!-- Hidden cell -->
<ng-container matColumnDef="expandedDetail">
    <td mat-cell *matCellDef="let jobModel" [attr.colspan]="displayedColumns.length">
        <div
            class="detail-cell"
            *ngIf="jobModel.isExpanded"
            [@detailExpand]
        >
            <sch-job-detail
                [jobModel]="jobModel"
                ...
            ></sch-job-detail>
        </div>
    </td>
</ng-container>

As you can see, the table owns an array of JobModel(s), and each row receives its own JobModel instance.
JobModel is a wrapper for a FormGroup, which is also a "transposition" of a simple interface object Job.

sch-job-detail has others children Components, for example:

<!-- Toolbar -->
<div class="col">
    <sch-job-row-toolbar
        [isNew]="jobModel.isNew"
        [isEdit]="jobModel.isEdit"
        [isError]="jobModel.isError"
        [isValid]="jobModel.isValid"
        ...
    ></sch-job-row-toolbar>
</div>

On the expandable row I have a button which lets the user enter a new Cron expression, and that Cron expression is then added to the FormGroup's FormControl.

Inside the TableComponent:

public addCronExpression(jobModel: JobModel): void {
    this.matDialog
        .open<CronDialogSmartComponent, any, string>(CronDialogSmartComponent)
        .afterClosed()
        .pipe(filter<string>(c => !!c))
        .subscribe(c => {
                jobModel.addCronExpression(c)
                this.changeDetector.detectChanges()
            }
        )
}

JobModel#addCronExpression:

public addCronExpression(cronExpression: string): void {
    const cronExpressions = this.formGroup.controls.cronExpressions
    cronExpressions.setValue([...cronExpressions.value, cronExpression])
}

As you can see, being that I do not change the JobModel instance, I run detectChanges to update the TableComponent and its children, sch-job-row-toolbar included I suppose!

The thing is sch-job-row-toolbar does not seem to recalculate its bindings (ngOnChanges doesn't run).

So those values:

[isNew]="jobModel.isNew"
[isEdit]="jobModel.isEdit"
[isError]="jobModel.isError"
[isValid]="jobModel.isValid"

are not changed.
We can take JobModel#isEdit as example:

get isEdit(): boolean {
    return this.formGroup.dirty || Job.isEdit(this.job.status)
}

I have no clue what's happening, but I know that if I do press a button or switch a tab eberywhere else, sch-job-row-toolbar receives the updated values.

All Components use onPush strategy.

Explanatory GIF: enter image description here

And, interestingly, when I do the same thing from the first Tab, it works! enter image description here

Tried using Default change detection strategy, but it's the same result.

11
  • Try calling ChangeDetectorRef.markForCheck() instead of ChangeDetectorRef.detectChanges(). Commented Jan 15, 2019 at 15:44
  • @ConnorsFan nada, same thing. I'm on this since a couple of hours, can't figure out. Have you seen the GIF? Commented Jan 15, 2019 at 15:46
  • @LppEdd look at this answer (stackoverflow.com/questions/54072065/…) Commented Jan 15, 2019 at 16:02
  • 1
    @ConnorsFan markForCheck() is not required until OnPush is used, since it marks a view and all of its ancestors dirty. This would result in rebuilding them in next tick. Commented Jan 18, 2019 at 12:17
  • 1
    hi @LppEdd so can someone post a final answer for this? thanks Commented Dec 16, 2019 at 5:47

1 Answer 1

2

Why don't you use @Input and @Output in order to detect changes from parent to child component? I would like to suggest you to use @Input (parent => child) and to use @Output (child=> parent) instead of detectChanges().

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

1 Comment

I recommend against rhetoric questions in answers. They risk being misunderstood as not an answer at all. You are trying to answer the question at the top of this page, aren't you? Otherwise please delete this post.

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.