Kesem David
Kesem David

Reputation: 2225

Angular2 Child component destroyed unexpectedly in ngFor

So i have this Component of a from with an @Output event that trigger on submit, as follows:

@Component({
    selector: 'some-component',
    templateUrl: './SomeComponent.html'
})
export class SomeComponent{    
    @Input() data: any;
    @Output() onSubmit: EventEmitter<void> = new EventEmitter<void>();

    constructor(private someService: SomeService) {}

    submitForm(): void{
        this.someService.updateBackend(this.data, ()=>{
            this.onSubmit.emit();
        });
    }
}

I'm using an ngFor to create multiple elements of this Component :

<template let-data ngFor [ngForOf]="dataCollection">
    <some-component  [data]="data" (onSubmit)="doSomthing()"></some-component>
</template>

The last missing part is the service used on submitting:

@Injectable()
export class SomeService{

    constructor() {}

    updateBackend(data: any, callback: () => void): void{
        /*
         * updating the backend
         */.then((result) => {
            const { errors, data } = result;

            if (data) {
                callback();
            }
        })
    }
}

At the beginning of the submitForm() function, the this.onSubmit.observers is an Array containing one observer, like it should be.

As soon as it reaches the callback method, where the this.onSubmit.emit() is invoked, the this.onSubmit.observers is an Array containing ZERO observers.

I'm experiencing two very weird behaviors:

Any idea what am i doing wrong?

Thanks in advance!

Update:

Thanks to @StevenLuke's comment about logging the ngOnDestroy of SomeComponent I found out that it is being destroyed before the emit.

Actually, the first thing it is doing when the SomeService.updateBackend finishes is Destroying all the instances of this component and recreate them!

This is what makes the observers change! Why would that happen?

Upvotes: 7

Views: 2733

Answers (2)

ksach
ksach

Reputation: 133

If you provide a trackBy function in your *ngFor to identify items in your dataCollection, it will not destroy and init. Your template would be:

<some-component *ngFor="let data of dataCollection;trackBy:trackByFunction"
  [data]="data" (onSubmit)="doSomthing()"></some-component>

And the trackByFunction would look like:

trackByFunction(index, item) {
    return item ? item.id : undefined;
}

So even though an item in your dataCollection is a fresh object, if its id matches an id in the previous collection, *ngFor will update [data] but not destroy and init the component.

Upvotes: 13

Kesem David
Kesem David

Reputation: 2225

Thanks to @GünterZöchbauer comments I found out the case was that the data the ngFor is bound to was being replaced by a new instance as I updated the backend, hence, it rerendered it's child Components causing reinitializing (destory + init) of them, which made the instance of the Component to be overwritten.

In order to solve this issue i had to place the dataCollection in a separate service, getting it for the parent component ngOnInit, saving it from causing a rerender of the ngFor, and fetch its data again only after the execution of the Child Components ended

Hope it'll be helpful to somebody!

Upvotes: 3

Related Questions