Execute a function following the completion of rendering the ViewChild

My template includes the following:

@Component({
  standalone: true,
  imports: [DynamicFormComponent, NgForOf, NgIf, NgSwitch, NgSwitchCase, NgTemplateOutlet],
  template: `
  <ng-container [ngSwitch]="method">
    <ng-container *ngSwitchCase="file">
      <ng-container *ngIf="!isFileUploaded; else form">
        ...some html...
      </ng-container>
    </ng-container>
    <ng-container *ngSwitchCase="manual">
      <ng-container *ngTemplateOutlet="form"></ng-container>
    </ng-container>
  </ng-container>

<ng-template #form>
  <app-dynamic-form></app-dynamic-form>
</ng-template>
`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ParentComponent implements OnInit, AfterViewInit {
  @ViewChild(DynamicFormComponent, { static: false })
  public form!: DynamicFormComponent;

  private createForm(): void {
    // method to run after DynamicFormComponent render...
    const fields: any[] = this.createFields();
    this.form.setFields(fields);
  }

  createFields(): any[] {
    return [/*fields...*/]
  }
}

I am trying to set a variable inside <app-dynamic-form> using its setFields() method.

export class DynamicFormComponent {
  public fields: any[];

  public setFields(val: any[]): void {
    if (val) {
      this.fields = val;
    }
  }
}

An issue arises when this component is rendered later via ng-template than when the parent is initialized. I have attempted to do it in ngAfterViewInit() without success, and also tried emitting the event in ngOnInit() with no luck either.

Answer №1

If you want to trigger an event when DynamicFormComponent initializes, you can use the code snippet below:

export class DynamicFormComponent {
  public fields: any[];

  @Output() onInit: EventEmitter<boolean> = new EventEmitter<boolean>();

  ngOnInit() {
    this.onInit.emit(true);
  }

  public setFields(val: any[]): void {
    if (val) {
      this.fields = val;
    }
  }
}

To bind the create form method with the initialization event, you can include the following code:

<app-dynamic-form (onInit)="createForm()" ></app-dynamic-form>

Answer №2

There are two methods to achieve this:

  1. Utilize output in your app-dynamic-form component. It is important to consider the following points with this approach:
  • You must trigger the event emitter asynchronously from the child to avoid potential errors such as
    ExpressionChangedAfterItHasBeenCheckedError
    . This occurs because Angular follows a unidirectional change detection flow from parent to child. If you emit sync during this process, the state might change in the parent before it has been checked by Angular, leading to inconsistencies in rendering.
  • To ensure that your ViewChild is captured by the parent, you can either opt for an async output as mentioned above (which allows the parent to catch the event once the ViewChild is defined), or use an output handler with a hook like ngAfterViewChecked to capture the ViewChild.

Using async output effectively addresses both issues:

app-dynamic-form:

@Output() init = new EventEmitter<void>(true); // true ensures that the eventemitter triggers asynchronously in the next event loop tick

ngOnInit(): void {
  this.init.emit();
}

Following this, you should be able to capture the initialization event in the parent and have it defined in the ViewChild property.


The other method is a bit more complex but serves as an alternative - there is no need to add outputs, etc., but instead, handle changes of ViewChild using the ngAfterViewChecked hook:

parent:

@ViewChild(DynamicFormComponent, { static: false })
  public form!: DynamicFormComponent;
private childInited = false;

ngAfterViewChecked(): void {
  if(!this.childInited && this.form) {    
    this.childInited = true; // to prevent triggering in the next change detection cycle
    setTimeout(() => this.onDynamicFormInited());
  }
  else if(!this.form) { // handle the scenario when the form was present but then hidden by ngIf 
    this.childInited = false;
  }
}

private onDynamicFormInited(): void {
  ...
}

Note: Use setTimeout when making state changes (properties used in the template) to avoid expression changed errors during change detection.

Additionally, since your component utilizes OnPush, make sure to include markForCheck to ensure that setTimeout state changes are reflected in the re-rendering process.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Struggling to fetch a DropDown menu from an Array in ReactJS

Trying to utilize an array of City Names in a Auto Complete text field within ReactJS. The array is successfully printed, but encountering difficulty displaying the list in the browser's Dropdown. An error message is being displayed in the browser r ...

Is it possible to trigger this Angular function upon page load?

I have an async function called getDataStandard() that I want to execute without the need for a click event. Can someone please guide me on how to achieve this as I am new to Ionic? async getDataStandard() { let loading = await this.loadingCtrl.create ...

How do I retrieve the value of a dxi-item in TypeScript with Angular and Devextreme?

One of the challenges I am facing is related to a Data Grid that has editing capabilities in popup mode. Within this grid, there are two data fields - password and confirm password - which I would like to validate to ensure they match. However, I am strugg ...

Utilizing React Hooks efficiently with JSDoc annotations and destructuring arrays

Seeking guidance on how to properly JSDoc a React Typescript component that uses hooks, specifically when working with declared destructured arrays. Despite my efforts, I have not been able to find a solution that allows JSDoc to work seamlessly with destr ...

Creating an array of objects in Angular 2

I'm facing an issue with the following expression: public mySentences:Array<string> = [ {id: 1, text: 'Sentence 1'}, {id: 2, text: 'Sentence 2'}, {id: 3, text: 'Sentence 3'}, {id: 4, text: 'Sen ...

I am encountering issues with the TypeScript repository build on my local machine, but it successfully passes when

I am encountering an issue with a TypeScript repository failing to build on my local machine. The error message I receive is as follows: $ tsc --pretty -p tsconfig.json ../../../../../../node_modules/@types/graphql/subscription/subscribe.d.ts:17:12 - erro ...

Tips for navigating the material ui Expanded attribute within the Expansion Panel

After looking at the image provided through this link: https://i.stack.imgur.com/kvELU.png I was faced with the task of making the expansion panel, specifically when it is active, take up 100% of its current Div space. While setting height: 100% did achi ...

Executing several Angular applications on a shared Node server

Currently learning about the MEAN stack, I am looking to create an application that includes both admin and client sections. To accomplish this, I have developed two Angular 2 apps within my Node environment. However, I am experiencing difficulty in render ...

Tips for avoiding the <p> and <br> elements while using a ContentEditable div

Upon pressing the enter key, the editor automatically inserts paragraph and page break elements. What are some strategies to avoid these unwanted elements in the editor? https://i.sstatic.net/r4jU1.png ...

Is it acceptable to use JavaScript files in the pages directory in NEXTJS13, or is it strongly advised to only use TypeScript files in the most recent version?

In the previous iterations of nextJS, there were JavaScript files in the app directory; however, in the most recent version, TypeScript files have taken their place. Is it still possible to begin development using JavaScript? I am working on creating an a ...

What is the method for executing a custom command within the scope of a tree view item?

I am trying to implement a custom "ping" function in my VS Code Extension that will send the details of a selected treeview object. Despite looking at various examples, I have been unable to properly build and register the command. Although I can invoke th ...

The React Provider values in Typescript are not compatible with each other

Currently, I am diving into learning React on my own and I have hit a roadblock: Recently, I was following a tutorial on how to develop an authentication application with Firebase and React. However, the tutorial instructor was using JavaScript, whereas I ...

What is the process for including a resource parameter in the acquireTokenSilent method of an MSAL instance within an Angular application?

const requestToken = { permissions: ['view_only'], }; newToken = await this.authInstance.acquireTokenSilent(requestToken); I'm trying to include the client ID of my Web API as a resource parameter when requesting the access token. Strugg ...

Tips for implementing type safety in a generic class to verify that the response type aligns with the anticipated type in TypeScript

In TypeScript, I have created a QueryFactory class with two generic type parameters: TQuery and TResponse. My goal is to ensure type safety so that if the TResponse type does not match the expected response type of the TQuery type, the compiler will throw ...

Tips for successfully sending an API request using tRPC and NextJS without encountering an error related to invalid hook calls

I am encountering an issue while attempting to send user input data to my tRPC API. Every time I try to send my query, I receive an error stating that React Hooks can only be used inside a function component. It seems that I cannot call tRPC's useQuer ...

Expanding TypeScript Definitions

I've been experimenting with TypeScript and Express. After importing type declarations from Typings, I found the following code: // Imported from typings // Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/7de6c3dd94feaeb21f2 ...

The property is not found in the '{}' type but is necessary in the type... Typescript connect strategy

Hello, I am currently trying to establish a connection pattern for React with TypeScript. I have a reducer set up as follows: type State = { version: number, a?: string } interface ActionC { type: string payload?: number } type IAction = Action ...

A guide on incorporating a JavaScript plugin using Vue.use() into a TypeScript project equipped with typings

Currently, I am facing an issue while attempting to integrate Semantic-UI-Vue into my Vue project. Upon trying to execute Vue.use(SuiVue), the following error message is displayed: Argument of type 'typeof import("semantic-ui-vue")' is not ass ...

Setting up Emotion js in a React TypeScript project using Vite 4

Currently, I am in the process of transitioning from Webpack to Vite for my React Typescript application. I have been attempting to integrate Emotion js into the project. "@vitejs/plugin-react": "^4.0.1", "vite": "^4.3.9 ...

Showcasing an Angular Material Dialog following a delay of x seconds

I'm currently working on integrating an Angular Material Dialog and I would like it to appear x seconds after the user clicks the openDialog button. Is this achievable? ...