Search code examples
javascriptangulartypescriptangular-changedetection

Angular 6 UI implementing change detection without using parent template


I need to make a reusable component in angular that will be packaged into a npm module. Being new to angular I've hit a roadblock with the UI change detection.

for the component to work first the application needs to make an api call to collect the data required for setup then the component needs to run some initalization.

this.dataService.getData().subscribe((data) => {
   this.data = data;
   this.npmComponent.init(this.data);
});


<app-npm-component></app-npm-component>

In my component I then need to run a bunch of setup tasks and afterwards it needs to listen for changes in the data rerun a sub set of setup tasks and update the ui.

@Component({
...,
changeDetection: ChangeDetectionStrategy.OnPush
});

...

init(setupData: any): void {
    this.cdr.detectChanges();
    this.data = setupData;
}

<ul *ngIf="data.arrayOfStuff">
    <li *ngFor="let item of data.arrayOfStuff">{{item}}</li>
</ul>

the usual ChangeDection implementation throws an error: NullInjectorError: No provider for ChangeDetectorRef! since the data isn't being passed from it's parent via the template.

Whats the right way to implement this kind of thing? Thanks in advance for your time.

Edit: I found the answer in ViewChild

@ViewChild(NpmComponent, {static: false}) npmComponent: NpmComponent;

Solution

  • Put an input on the child

    <app-npm-component [data]="setupData"></app-npm-component>
    

    and in the child component's class

    @Input() data;
    

    Angular will take care of listening for changes to inputs.

    You can also do it with subscribing using the async pipe.

    In the parent component

    setupData$ = this.dataService.getData();
    

    and pass the data to the child with the async pipe

    <app-npm-component [data]="setupData$ | async"></app-npm-component>
    

    No subscriptions required.

    If you need to run the data through a function use a map

    setupData$ = this.dataService.getData().pipe(map(data => yourFunction(data)));