CompiledIO
CompiledIO

Reputation: 158

Angular child component event from parent fires multiple times

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

Upvotes: 0

Views: 2998

Answers (2)

Rick
Rick

Reputation: 1880

use a ViewChild to send click events to a child.

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

Upvotes: 0

Barremian
Barremian

Reputation: 31135

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();
  }
}

Upvotes: 2

Related Questions