7

I've implemented a child component to render a table based on a list provided via @Input(). The data is loaded via http, however the UI (child component) is not updated unless I wave my mouse over the screen. I've seen people post about implementing ngOnChanges() in my child, but I thought Angular was supposed to do this by default? Am I missing something? Why would the UI not update with this?

Child code looks something like this:

child.component.ts

@Component({
  selector: 'child',
  templateUrl: './child.component.html',
  styleUrls: ['./child.component.scss'],
})
export class ChildComponent implements {
  @Input() data: any[] = [];
  constructor() {}
}

child.component.html

<table>
  <tr *ngFor="let item of data"><td>{{ item }}</td></tr>
</table>

Parent code that uses the component looks something like this:

parent.component.ts

@Component({
  selector: 'parent',
  templateUrl: './parent.component.html',
  styleUrls: ['./parent.component.scss'],
})
export class ParentComponent implements OnInit {
  data: string[] = [];

  constructor(private endpointService: EndpointService) {}

  ngOnInit() {
    // response is a string array like: ['hello', 'world']
    this.endpointService.loadData().subscribe((response) => {
      this.data = response;
    });
  }
}

parent.component.html

<child [data]="data"></child>

============================= EDIT ==================================

I verified that it only fails to load when updating inside of the subscribe callback (if I set a static array, it loads just fine).

So it looks like I'm able to resolve this by running changeDetectorRef.detectChanges() in the parent component, but this feels hackish like I shouldn't have to do this. Is this a good way to resolve this? Or does this indicate something wrong with my implementation?

parent.component.ts

@Component({
  selector: 'parent',
  templateUrl: './parent.component.html',
  styleUrls: ['./parent.component.scss'],
})
export class ParentComponent implements OnInit {
  data: string[] = [];

  constructor(private endpointService: EndpointService,
              private changeDetectorRef: ChangeDetectorRef) {}

  ngOnInit() {
    // response is a string array like: ['hello', 'world']
    this.endpointService.loadData().subscribe((response) => {
      this.data = response;
      this.changeDetectorRef.detectChanges();
    });
  }
}
1
  • You can create a boolean variable on child component and use ngIf om table. This would make sure table is not loaded until the Input is received. Commented Dec 8, 2020 at 21:25

5 Answers 5

9

You can also try to force change detection by forcing the value reference update via, for example, the spread operator:

this.endpointService.loadData().subscribe((response) => {
  this.data = [...response];
});
Sign up to request clarification or add additional context in comments.

3 Comments

Great feedback! I was not aware of the spread operator method, but I am calling changeDetectorRef.detectChanges() and that seems to have resolved the issue. However, I'm confused why I'm having to do that? It seems like a hack and I'm worried it means I've got something wrong with my code base?
The reason seems to be that you have set ChangeDetectionStrategy.OnPush in some of parent components. According to the official docs the chosen strategy applies to all child directives and cannot be overridden - angular.io/api/core/ChangeDetectionStrategy
Ok, after looking into it, yes the AppComponent has the ChangeDetectionStrategy.OnPush... I will look into this, but that is I think what the issue is, thank you!
1

I replaced the service with a static string array and it worked well. I think there is problem with the observable subscription.

child.component.ts

import { Component, OnInit,Input } from '@angular/core';

@Component({
  selector: 'child',
  templateUrl: './child.component.html',
  styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit {
@Input() data: any[] = [];
  constructor() { }

  ngOnInit() {
  }

}

parent.component.ts

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'parent',
  templateUrl: './parent.component.html',
  styleUrls: ['./parent.component.css'],
})
export class ParentComponent implements OnInit {
  data: string[] = [];

  constructor() {}

  ngOnInit() {
    
      this.data = ['hello', 'world','aaa','ddd'];
    
  }
}

1 Comment

This is my thought as well. Did it not work with the subscription for you but when moving to a static array it worked?
1

I have fixed this problem updating the attribute passed on child input instead of re-assign it. I think that Angular passes input as copy. So, in subscribe body you should use

resultOfSubscribe.forEach((v) => varPassedByInput.push(v)); 

Comments

0

hummm well .. when the component is rendered as first time it will show with the empty array becouse the api call stills happening and needs the onchanges method in child component in order to listen the complete api call and the list will re render

2 Comments

Is the onChanges method required for this type of situation?
humm nope I think that you can use change detection strategy -angular.io/api/core/ChangeDetectorRef#changedetectorref - and manually trigger change detection without using ngonchanges method you can see this example codesandbox.io/s/charming-shamir-guvdz?file=/src/app/…
0

Seems that you have some other errors in template expressions which force the whole template to fail. Here's a stackblitz I've created and everything works: https://stackblitz.com/edit/angular-ivy-w2ptbb?file=src%2Fapp%2Fhello.component.ts

Do you have maybe some errors in console?

1 Comment

Thanks for this! yes I'm confused as well why this is not updating... I'm also looking into how the Component is declared. I'm using a theme with a complex Module layout (tons of Modules). Could it be I need to declare the ChildComponent and that could affect how it renders changes?

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.