Render a reactive form when function is called

Viewed 116

I would like to know if it is possible render a reactive form just when a function is called(instead of in ngOnInit()) using TemplateRef and ViewContainerRef. Here is my code:

component.html

<div
  class="group"
  (mouseover)="showActions = true"
  (mouseleave)="showActions = false"
>
  <dt>Username:</dt>
  <dd> - </dd>
  <div class="actions">
    <a
      mat-button (click)="editUsername()"
      >Edit</a
    >
  </div>
  <ng-container #editUsernameContainer></ng-container>
</div>
.
.
.
<ng-template #tmplEditUsername>
  <div class="username-form">
    <form [formGroup]="changeUsernameForm">
      <input matInput #input placeholder="Username" formControlName="username"/>
      <div class="form-actions">
        <a mat-button>Cancel</a>
        <button mat-raised-button (click)="updateUsername()">
          Update
        </button>
      </div>
    </form>
  </div>
</ng-template>

component.ts

export class MyComponent implements OnInit {
  @ViewChild('editUsernameContainer', { read: ViewContainerRef, static: true }) editUsernameContainer: ViewContainerRef;
  @ViewChild('tmplEditUsername', { read: TemplateRef, static: true }) tmplEditUsername: TemplateRef<any>;

  constructor(private formBuilder: FormBuilder) { }

  ngOnInit(): void {}

  editUsername() {
    this.editUsernameContainer.createEmbeddedView(this.tmplEditUsername);
    this.myForm = this.formBuilder.group({
      username: new FormControl(''),
    });
  }
}

I am having this error in console:

Cannot read properties of undefined (reading 'createFormGroup') TypeError: Cannot read properties of undefined (reading 'createFormGroup')

Any solution?

4 Answers

In this specific use case, you do not need to do all viewChild work. The ng-container in which you want to show the template and the actual template are in same file so you have the reference of template inside the HTML.

You can do it like this:

In Component.html

<ng-container *ngIf="editUsernameContainer" [ngTemplateOutlet]="tmplEditUsername"></ng-container>

And it Component.ts

editUsername() {
 this.myForm = this.formBuilder.group({
    username: new FormControl(''),
 });

 this.editUsernameContainer = true;
}

OR: You can also move the template to ng-container to remove ng-template at all like this

<ng-container *ngIf="editUsernameContainer">
<div class="username-form">
<form [formGroup]="changeUsernameForm">
  <input matInput #input placeholder="Username" formControlName="username"/>
  <div class="form-actions">
    <a mat-button>Cancel</a>
    <button mat-raised-button (click)="updateUsername()">
      Update
    </button>
  </div>
</form>
 </div>
</ng-container>

for this case, why don`t you use 'valueChanges'?

this.myForm.valueChanges.pipe().subscribe();

inside pipe() or subscribe() you can render whatever you want. all you need is to get form dirty.

also we can change only part of form:

this.myForm.get('username').valueChanges().subscribe();

Why not just ngIf?

Template:

<div
  class="group"
  (mouseover)="showActions = true"
  (mouseleave)="showActions = false"
>
  <dt>Username:</dt>
  <dd> - </dd>
  <div class="actions">
    <a
      mat-button (click)="editUsername()"
      >Edit</a
    >
  </div>
  <div *ngIf="showEditUserName" class="username-form">
    <form [formGroup]="changeUsernameForm">
      <input matInput #input placeholder="Username" formControlName="username"/>
      <div class="form-actions">
        <a mat-button>Cancel</a>
        <button mat-raised-button (click)="updateUsername()">
          Update
        </button>
      </div>
    </form>
  </div>
</div>

Component:

export class MyComponent implements OnInit {
  showEditUserName = false;

  constructor(private formBuilder: FormBuilder) { }

  ngOnInit(): void {}

  editUsername() {    
    this.myForm = this.formBuilder.group({
      username: new FormControl(''),
    });

    this.showEditUserName = true;
  }

  updateUserName() {
    // ...
    this.showUpdateUserName = false;
  }
}

Below snippet might help you:

HTML:

<div class="group" (mouseover)="showActions = true" (mouseleave)="showActions = false" >
    <dt>Username:</dt>
    <dd>-</dd>
    <div class="actions">
      <a mat-button (click)="editUsername()">Edit</a>
</div>
<ng-container #editUsernameContainer></ng-container>
    </div>
    . . .
    <ng-template #tmplEditUsername>
    <div class="username-form">
      <form [formGroup]="changeUsernameForm">
      <input matInput placeholder="Username" formControlName="username" />
      <div class="form-actions">
        <a mat-button>Cancel</a>
        <button mat-raised-button (click)="updateUsername()">Update</button>
      </div>
    </form>
  </div>
</ng-template>

TS:

import {
  Component,
  TemplateRef,
  VERSION,
  ViewChild,
  ViewContainerRef,
} from '@angular/core';
import { FormBuilder, FormControl, FormGroup } from '@angular/forms';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  showActions;
  changeUsernameForm: FormGroup;

  updateUsername() {
    console.log(this.changeUsernameForm.value);
  }

  @ViewChild('editUsernameContainer', { read: ViewContainerRef, static: true })
  editUsernameContainer: ViewContainerRef;
  @ViewChild('tmplEditUsername', { read: TemplateRef, static: true })
  tmplEditUsername: TemplateRef<any>;

  constructor(private formBuilder: FormBuilder) {}

  ngOnInit(): void {}

  editUsername() {
    this.editUsernameContainer.clear();
    this.editUsernameContainer.createEmbeddedView(this.tmplEditUsername);
    this.changeUsernameForm = this.formBuilder.group({
      username: new FormControl(''),
    });
  }
}

Hope this will help you.

Related