I'm currently using Angular Forms version 2.0.0 and trying to make a contact us modal with a contact form inside.
Immediately after the ContactComponent loads, I get:
EXCEPTION: this.form._updateTreeValidity is not a function
I've already seen some other stack posts suggesting that using FormGroup instead of FormBuilder to init the form object in the component constructor is now standard with the new API so I've updated that.
I import ReactiveFormsModule and FormsModule along with all the form related components and the error doesn't seem to be module related.
My TypeScript isn't throwing errors in compile time and Visual Studio Intellisense seems to be able to find all FormGroup functions just fine so why is this happening at runtime?...
My code:
contact.component.ts:
import { Component, Input, ViewChild } from '@angular/core';
import { ApiService } from '../../../services/api.service';
import { ModalComponent } from 'ng2-bs3-modal/ng2-bs3-modal';
import { Router, ActivatedRoute, Params } from '@angular/router';
import { FormsModule, ReactiveFormsModule, FormGroup, FormControl, Validators } from '@angular/forms';
import 'rxjs/Rx';
declare var jQuery: any;
@Component({
selector: 'my-contact',
templateUrl: 'app/modules/footer/contact/contact.html'
})
export class ContactComponent {
private contactForm: FormGroup;
private invalidEmail: boolean;
private invalidSubject: boolean;
private invalidMessage: boolean;
constructor(private apiService: ApiService, private router: Router, private route: ActivatedRoute) {
this.contactForm = new FormGroup({
emailControl: new FormControl('', <any>Validators.required),
subjectControl: new FormControl('', <any>Validators.required),
messageControl: new FormControl('', <any>Validators.required)
});
}
submit() {
if (this.contactForm.valid) {
this.apiService.sendMessage(this.contactForm.controls['emailControl'].value, this.contactForm.controls['subjectControl'].value, this.contactForm.controls['messageControl'].value);
}
if (!this.contactForm.controls['emailControl'].valid) {
this.invalidEmail = true;
}
if (!this.contactForm.controls['subjectControl'].valid) {
this.invalidSubject = true;
}
if (!this.contactForm.controls['messageControl'].valid) {
this.invalidMessage = true;
}
}
ngOnInit() {
this.invalidEmail = false;
this.invalidSubject = false;
this.invalidMessage = false;
}
}
contact.html:
<modal-header class="c-no-border" [show-close]="true">
<h4 class="modal-title text-uppercase">Send us a message</h4>
</modal-header>
<form novalidate #contactForm [formGroup]="contactForm" (ngSubmit)="submit()">
<div class="modal-body">
<div class="form-group">
<label for="email" class="control-label">Email</label>
<input name="email" formControlName="emailControl" placeholder="" type="text" class="c-square form-control c-margin-b-20" id="email">
<div class="c-font-red-1" *ngIf="invalidEmail" style="position: absolute;">*Required</div>
<label for="subject" class="control-label">Subject</label>
<input name="subject" formControlName="subjectControl" placeholder="" type="text" class="c-square form-control c-margin-b-20" id="subject">
<div class="c-font-red-1" *ngIf="invalidSubject" style="position: absolute;">*Required</div>
<textarea formControlName="messageControl" style="resize: vertical;" class="c-square form-control c-margin-b-20" id="content" (keyup.enter)="submit()"></textarea>
<div class="c-font-red-1" *ngIf="invalidMessage" style="position: absolute;">*Required</div>
</div>
</div>
<modal-footer class="c-no-padding">
<button type="button" class="btn c-btn-square c-btn-bold c-btn-uppercase pull-right">Cancel</button>
<button type="submit" class="btn c-theme-btn c-btn-square c-btn-bold c-btn-uppercase pull-right" style="margin-right: 10px;">Send</button>
</modal-footer>
</form>
app.module.ts:
import { NgModule, enableProdMode } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { Ng2Bs3ModalModule } from 'ng2-bs3-modal/ng2-bs3-modal';
import { QueuesModule } from './modules/queues/queues.module';
import { OrderModule } from './modules/order/order.module';
import { AccountModule } from './modules/account/account.module';
import { AdminModule } from './modules/admin/admin.module';
import { routing } from './app.routing';
import { GridModule } from '@progress/kendo-angular-grid';
import { SplashComponent } from './modules/splash/splash.component';
import { ContactComponent } from './modules/footer/contact/contact.component';
import { SharedModule } from './shared/shared.module';
import { EmailValidator } from './shared/utilities/custom-validators'
import { CookieService } from 'angular2-cookie/services/cookies.service';
import { HttpModule, Response } from '@angular/http';
import { StringService } from './services/string.service';
import { ApiService } from './services/api.service';
import { UserService } from './services/user.service';
import { OrderService } from './services/order.service';
import { OrderGuard } from './services/order-guard.service';
import { FooterComponent } from './modules/footer/footer.component';
import { ErrorComponent } from './modules/error/error.component';
import { CustomFormsModule } from "ng2-validation";
@NgModule({
imports: [
BrowserModule,
FormsModule,
ReactiveFormsModule,
HttpModule,
QueuesModule,
OrderModule,
AccountModule,
AdminModule,
routing,
GridModule,
SharedModule,
Ng2Bs3ModalModule,
CustomFormsModule
],
declarations: [
AppComponent,
SplashComponent,
FooterComponent,
ErrorComponent,
ContactComponent
],
providers: [
StringService,
ApiService,
UserService,
CookieService,
OrderService,
OrderGuard
],
bootstrap: [AppComponent],
exports: [
]
})
export class AppModule {
}
Binding the template variable #contactForm
appears to cause a name conflict and blow up the template processor as it tries to turn the attached template variable into an NgForm
on the backend. Everywhere I have seen model driven forms used there is no template variable binding on the form, whereas there is a #tv="ngForm"
utilized in template driven forms. It appears there was a miss on the mixing of the two forms approaches which resulted in the error.
Simply removing it will resolve the issue.