0
votes

I have a click event (updateExpanded) on my parent, when clicked I fire and event off that goes to the child. the child then subscribes to this event. The problem is that the event gets called 4 times. I have tried everything I could find but just cant seem to see why it gets fired so often when it should only get fired once.

I need to load data in my child component when my parent component gets expanded.

Parent TS

@Output()
isExpandedEvent = new EventEmitter<string>();

 updateExpanded(id: string) {
        this.isExpandedEvent.emit(id);
        console.log(id + " parent opened");
    }

ngOnDestroy() {
        if (this.isExpandedEvent) {
            this.isExpandedEvent.unsubscribe();
        }
    }

Parent HTML

<ngx-planned-maintenance-detail [model]="plannedMaintenanceRecurring" [index]="i" [expandedEventEmitter]="isExpandedEvent">
                </ngx-planned-maintenance-detail>

.........

Child TS

 @Input()
  expandedEventEmitter = new EventEmitter<string>();

  ngOnInit() {
    this.expandedEventEmitter.subscribe(data => {
        if (data) {
             console.log("detail complete");
        }
     });
  }

ngOnDestroy() {
    this.expandedEventEmitter.unsubscribe();
  }

Parent is called once, detail called 4 times as seen below enter image description here

2
The [index]="i" seems to indicate it shows multiple of the same components. Could this be the cause? - MaartenDev

2 Answers

2
votes

That's because you are binding an observable (the EventEmitter) to a property. There are 2 issues here.

  1. EventEmitter is NOT the correct way to communicate information from parent to child. It's used for the other way around.
  2. If you aren't controlling the change detection strategy, the observables/functions bound to a property might be fired multiple times than expected.

If you look at the EventEmitter source, it's a simple interface extension of RxJS Subject. So what you could do in this instance is to create a singleton service that will hold the shared information between the components directly using a RxJS Subject.

shared.service.ts

import { Subject } from 'rxjs';

@Injectable({providedIn: 'root'})
export class SharedService {
  isExpanded = new Subject<any>();

  constructor() { }

  pushIsExpanded(value: any) {
    this.isExpanded.next(value);
  }

  getIsExpanded() {
    return this.isExpanded.asObservable();
  }
}

Now you could inject this singleton in both the components and use the shared data.

parent.component.ts

export class ParentComponent {
  constructor(private sharedService: SharedService) { }

  updateExpanded(id: string) {
    this.sharedService.pushIsExpanded(id);
  }
}

child.component.ts

import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

export class ChildComponent {
  const completed$ = new Subject<any>();

  constructor(private sharedService: SharedService) { }

  ngOnInit() {
    this.sharedService.getIsExpanded().pipe(
      takeUntil(this.completed$)
    ).subscribe(
      data => {
        if (data) {
          console.log("detail complete");
        }
     }
    );
  }

  ngOnDestroy() {
    this.completed$.next();
    this.completed$.complete();
  }
}
0
votes

use a ViewChild to send click events to a child.

use EventEmitter to send click events from the child up to the parent.