pop
pop

Reputation: 3714

Angular OnPush: an updated Input string won't update the template

There is a parent component with default changeDetection and two child components with onPush change detection strategy. While the input variable is being updated, the template will not update. I can't used async pipes in the ChildBComponent as it is a global UI component for the project and refactoring is not an option. How do I go about this?

parent.ts

@Component({
   selector: 'parent',
   template: `
   <div>
     <child-a>
        <child-b [label]="firstLabel"></child-b>
        <child-b [label]="secondLabel"></child-b>
        <child-b label="Just a plain label"></child-b>
     </child-a>
   </div>`
})
export class ParentComponent implements OnInit {

   public ngOnInit(): void {
      this.httpService.loadData()
        .subscribe(data => {
           this.someValue = 1;
           this.otherValue = 2;
        });
   }

   // getters to explicitly trigger change detection
   public get firstLabel(): string {
     return `Something ${ this.someValue || '?'}`;
   }

   public get secondLabel(): string {
     return `Different ${ this.otherValue || '?'}`;
   }
}

child-a.ts

@Component({
   selector: 'child-a',
   template: `
      <div>Layout wrapper</div>
      <ng-content></ng-content>`,
   changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildBComponent implements AfterContentInit {
}

child-b.ts

@Component({
   selector: 'child-b',
   template: '<div>{{ label }}</div>',
   changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildBComponent implements AfterViewInit, OnDestroy {
   @Input public label: string;
}

I tried converting @Input into getter/setter - to no avail. When debugging, the changes are coming through, but the template will only update if I click on the component itself. Throwing in a markForCheck() didn't help either.

And idea is helpful.

Edit: Added StackBlitz. Please use a wide preview to see the issue; miraculously it works properly when mobile view elements are shown.

Upvotes: 0

Views: 1131

Answers (1)

Siddhant
Siddhant

Reputation: 3171

The problem is not with Change Detection, and the label values are correctly reflected in html. The change in countA and countB values do get reflected in HTML, it's just the CSS that gives the impression as if the html is not updated.

In fieldtabs.component.html you have logic to display tabs via <ul> and you also have <ng-content>.

The problem is when the device has max width of 767.98px, the <ul *ngIf="!vertical" class="fieldtabs--tabs-container"> element is not shown as a result of below CSS:

@media (max-width: 767.98px) {
  .fieldtabs--tabs-container {
      display:none
  }
}

whereas the <fieldtab> i.e the Content Children are only visible when device max width is 767.98px. Below is the CSS responsible for the same:

.fieldtabs-l {
  display: none
}

@media (max-width: 767.98px) {
  .fieldtabs-l {
      display:flex;
      align-items: center
  }
}

The fieldtab template html responsible for displaying label has the above class:

<div *ngIf="!invisible" class="fieldtabs-l fieldtab--accord"

So when device max width is 767.98px:

  • The {{ label }} interpolation present within your OnPush Content child fieldtab template is visible
  • The {{ tab.label }} interpolation present within your OnPush fieldtabs template is not visible

When width is greater than 767.98px, the opposite happens i.e {{ tab.label}} is what you see on the screen, which doesn't get updated as the tabs are being constructed within ngAfterContentInit and the method won't be called again when countA and countB get assigned new values.

Upvotes: 1

Related Questions