skip to Main Content

How should be go about using / observing parent @Output() event emitters in child components?

For example in this demo the child component uses the @Output onGreetingChange like this:

<app-greeting [greeting]="onGreetingChange | async"></app-greeting>

And this will work if onGreetingChange emits in the AfterViewInit life cycle hook.

  ngAfterViewInit() {
    this.onGreetingChange.emit('Hola');
  }

However that produces the an ExpressionChangedAfterItHasBeenCheckedError error.

Is there a way to get to this to work without producing the error?

I tried emitting in both the constructor and OnInit. Thoughts?

2

Answers


  1. You can trigger the change detection manually, using detectChanges()

    My guess is that since its a non conventional way of changing an @Input change detection is missing the changes, so we need to run it manually!

      ngAfterViewInit() {
        this.onGreetingChange.emit('Hola');
        this.cdr.detectChanges();
      }
    

    stackblitz

    Login or Signup to reply.
  2. In general ngAfterViewInit life cycle hook is not the best place to initiate the value.
    You should use ngDoInit

    BUT

    You can always use BehaviorSubject instead of EventEmitter. See this:

    https://stackblitz.com/edit/stackblitz-starters-bncmrb?file=src%2Fmain.ts

    Main App

    @Component({
      selector: 'app-root',
      imports: [AppChild, AppChildTwo, AsyncPipe],
      standalone: true,
      templateUrl: './app.component.html',
    })
    export class App implements OnInit, AfterViewInit {
      @Output()
      // onGreetingChange: EventEmitter<string> = new EventEmitter();
      onGreetingChange = new BehaviorSubject('Hola!!');
    
      greetingTwo!: string;
    
      constructor() {
        //    this.onGreetingChange.emit('Hola!!');
      }
    
      ngOnInit() {
        // this.onGreetingChange.emit('Hola!!');
      }
    
      ngAfterViewInit() {
        // this.onGreetingChange.emit('Hola');
      }
    }
    

    HTML

    <div><app-greeting [greeting]="onGreetingChange | async"></app-greeting></div>
    <div>
      <app-greeting-two (greetingTwo)="greetingTwo = $event"></app-greeting-two>
    </div>
    <h2>{{ greetingTwo }}</h2>
    

    Child one

    @Component({
      selector: 'app-greeting',
      standalone: true,
      template: '<h1>{{ greeting }} Everyone!</h1>',
      changeDetection: ChangeDetectionStrategy.OnPush,
      encapsulation: ViewEncapsulation.None,
    })
    export class AppChild {
      @Input()
      greeting!: string | null;
    }
    

    Child two

    @Component({
      selector: 'app-greeting-two',
      standalone: true,
      template: '<h1>AppChildTwo: {{ greetingTwo | async }} Everyone!</h1>',
      changeDetection: ChangeDetectionStrategy.OnPush,
      encapsulation: ViewEncapsulation.None,
      imports: [AsyncPipe],
    })
    export class AppChildTwo {
      @Output()
      greetingTwo = new BehaviorSubject('Initial greeting TWO');
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search