Ionic 2 Validation


#1

I am using Ionic 2. I have just moved some artifacts from in Ionic 2 rc3 project to a Ionic 2 rc4 project.

I have been using an html tag <control-messages> that hold the validation messages. But I get errors (see below). So I was wondering if this perhaps changes in a later version, and there’s a better way to do it?

If I change the <control-messages> tags to <div> tags, then I get the following error: Can't bind to 'control' since it isn't a known property of 'div'.

Thanks

I get the following error at run-time in the browser after running ionic serve:

Unhandled Promise rejection: Template parse errors:
Can't bind to 'control' since it isn't a known property of 'control-messages'.
1. If 'control-messages' is an Angular component and it has 'control' input, then verify that it is part of this module.
2. If 'control-messages' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message.
 ("lName="username" id="username"></ion-input>
    </ion-item>
    <control-messages class="error-box" [ERROR ->][control]="forgotForm.controls.username"></control-messages>
    <br>
    <p class="or-text">OR</p>
"): ForgotPage@16:40
'control-messages' is not a known element:
1. If 'control-messages' is an Angular component, then verify that it is part of this module.
2. If 'control-messages' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message. ("   <ion-input type="text" formControlName="username" id="username"></ion-input>
    </ion-item>
    [ERROR ->]<control-messages class="error-box" [control]="forgotForm.controls.username"></control-messages>
    "): ForgotPage@16:4
Can't bind to 'control' since it isn't a known property of 'control-messages'.
1. If 'control-messages' is an Angular component and it has 'control' input, then verify that it is part of this module.
2. If 'control-messages' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message.
 ("ControlName="email" id="email"></ion-input>
    </ion-item>
    <control-messages class="error-box" [ERROR ->][control]="forgotForm.controls.email"></control-messages>
    <br/>
    <ion-buttons>
"): ForgotPage@23:40
'control-messages' is not a known element:
1. If 'control-messages' is an Angular component, then verify that it is part of this module.
2. If 'control-messages' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message. ("
      <ion-input type="text" formControlName="email" id="email"></ion-input>
    </ion-item>
    [ERROR ->]<control-messages class="error-box" [control]="forgotForm.controls.email"></control-messages>
    <br"): ForgotPage@23:4 ; Zone: <root> ; Task: Promise.then ; Value: Error: Template parse errors:
Can't bind to 'control' since it isn't a known property of 'control-messages'.
1. If 'control-messages' is an Angular component and it has 'control' input, then verify that it is part of this module.
2. If 'control-messages' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message.
 ("lName="username" id="username"></ion-input>
    </ion-item>
    <control-messages class="error-box" [ERROR ->][control]="forgotForm.controls.username"></control-messages>
    <br>
    <p class="or-text">OR</p>
"): ForgotPage@16:40
'control-messages' is not a known element:
1. If 'control-messages' is an Angular component, then verify that it is part of this module.
2. If 'control-messages' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message. ("   <ion-input type="text" formControlName="username" id="username"></ion-input>
    </ion-item>
    [ERROR ->]<control-messages class="error-box" [control]="forgotForm.controls.username"></control-messages>
    "): ForgotPage@16:4
Can't bind to 'control' since it isn't a known property of 'control-messages'.
1. If 'control-messages' is an Angular component and it has 'control' input, then verify that it is part of this module.
2. If 'control-messages' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message.
 ("ControlName="email" id="email"></ion-input>
    </ion-item>
    <control-messages class="error-box" [ERROR ->][control]="forgotForm.controls.email"></control-messages>
    <br/>
    <ion-buttons>
"): ForgotPage@23:40
'control-messages' is not a known element:
1. If 'control-messages' is an Angular component, then verify that it is part of this module.
2. If 'control-messages' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message. ("
      <ion-input type="text" formControlName="email" id="email"></ion-input>
    </ion-item>
    [ERROR ->]<control-messages class="error-box" [control]="forgotForm.controls.email"></control-messages>
    <br"): ForgotPage@23:4
Stack trace:
TemplateParser</TemplateParser.prototype.parse@http://localhost:8100/build/main.js:27049:19
RuntimeCompiler</RuntimeCompiler.prototype._compileTemplate@http://localhost:8100/build/main.js:54254:30
RuntimeCompiler</RuntimeCompiler.prototype._compileComponents/<@http://localhost:8100/build/main.js:54174:56
RuntimeCompiler</RuntimeCompiler.prototype._compileComponents@http://localhost:8100/build/main.js:54174:9
RuntimeCompiler</RuntimeCompiler.prototype._compileModuleAndComponents/createResult@http://localhost:8100/build/main.js:54070:13
T</d</t.prototype.invoke@http://localhost:8100/build/polyfills.js:3:9092
T</v</e.prototype.run@http://localhost:8100/build/polyfills.js:3:6462
h/<@http://localhost:8100/build/polyfills.js:3:4581
T</d</t.prototype.invokeTask@http://localhost:8100/build/polyfills.js:3:9712
T</v</e.prototype.runTask@http://localhost:8100/build/polyfills.js:3:7064
i@http://localhost:8100/build/polyfills.js:3:3664
 TemplateParser</TemplateParser.prototype.parse@http://localhost:8100/build/main.js:27049:19
RuntimeCompiler</RuntimeCompiler.prototype._compileTemplate@http://localhost:8100/build/main.js:54254:30
RuntimeCompiler</RuntimeCompiler.prototype._compileComponents/<@http://localhost:8100/build/main.js:54174:56
RuntimeCompiler</RuntimeCompiler.prototype._compileComponents@http://localhost:8100/build/main.js:54174:9
RuntimeCompiler</RuntimeCompiler.prototype._compileModuleAndComponents/createResult@http://localhost:8100/build/main.js:54070:13
T</d</t.prototype.invoke@http://localhost:8100/build/polyfills.js:3:9092
T</v</e.prototype.run@http://localhost:8100/build/polyfills.js:3:6462
h/<@http://localhost:8100/build/polyfills.js:3:4581
T</d</t.prototype.invokeTask@http://localhost:8100/build/polyfills.js:3:9712
T</v</e.prototype.runTask@http://localhost:8100/build/polyfills.js:3:7064
i@http://localhost:8100/build/polyfills.js:3:3664
  polyfills.js:3:3373
Error: Uncaught (in promise): Error: Template parse errors:
Can't bind to 'control' since it isn't a known property of 'control-messages'.
1. If 'control-messages' is an Angular component and it has 'control' input, then verify that it is part of this module.
2. If 'control-messages' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message.
 ("lName="username" id="username"></ion-input>
    </ion-item>
    <control-messages class="error-box" [ERROR ->][control]="forgotForm.controls.username"></control-messages>
    <br>
    <p class="or-text">OR</p>
"): ForgotPage@16:40
'control-messages' is not a known element:
1. If 'control-messages' is an Angular component, then verify that it is part of this module.
2. If 'control-messages' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message. ("   <ion-input type="text" formControlName="username" id="username"></ion-input>
    </ion-item>
    [ERROR ->]<control-messages class="error-box" [control]="forgotForm.controls.username"></control-messages>
    "): ForgotPage@16:4
Can't bind to 'control' since it isn't a known property of 'control-messages'.
1. If 'control-messages' is an Angular component and it has 'control' input, then verify that it is part of this module.
2. If 'control-messages' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message.
 ("ControlName="email" id="email"></ion-input>
    </ion-item>
    <control-messages class="error-box" [ERROR ->][control]="forgotForm.controls.email"></control-messages>
    <br/>
    <ion-buttons>
"): ForgotPage@23:40
'control-messages' is not a known element:
1. If 'control-messages' is an Angular component, then verify that it is part of this module.
2. If 'control-messages' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message. ("
      <ion-input type="text" formControlName="email" id="email"></ion-input>
    </ion-item>
    [ERROR ->]<control-messages class="error-box" [control]="forgotForm.controls.email"></control-messages>
    <br"): ForgotPage@23:4
TemplateParser</TemplateParser.prototype.parse@http://localhost:8100/build/main.js:27049:19
RuntimeCompiler</RuntimeCompiler.prototype._compileTemplate@http://localhost:8100/build/main.js:54254:30
RuntimeCompiler</RuntimeCompiler.prototype._compileComponents/<@http://localhost:8100/build/main.js:54174:56
RuntimeCompiler</RuntimeCompiler.prototype._compileComponents@http://localhost:8100/build/main.js:54174:9
RuntimeCompiler</RuntimeCompiler.prototype._compileModuleAndComponents/createResult@http://localhost:8100/build/main.js:54070:13
T</d</t.prototype.invoke@http://localhost:8100/build/polyfills.js:3:9092
T</v</e.prototype.run@http://localhost:8100/build/polyfills.js:3:6462
h/<@http://localhost:8100/build/polyfills.js:3:4581
T</d</t.prototype.invokeTask@http://localhost:8100/build/polyfills.js:3:9712
T</v</e.prototype.runTask@http://localhost:8100/build/polyfills.js:3:7064
i@http://localhost:8100/build/polyfills.js:3:3664

Stack trace:
s@http://localhost:8100/build/polyfills.js:3:4211
s@http://localhost:8100/build/polyfills.js:3:4034
h/<@http://localhost:8100/build/polyfills.js:3:4574
T</d</t.prototype.invokeTask@http://localhost:8100/build/polyfills.js:3:9712
T</v</e.prototype.runTask@http://localhost:8100/build/polyfills.js:3:7064
i@http://localhost:8100/build/polyfills.js:3:3664
  polyfills.js:3:3551

html

  <form [formGroup]="forgotForm" (ngSubmit)="submit()">
    <ion-item>
      <ion-label floating>Username</ion-label>
      <ion-input type="text" formControlName="username" id="username"></ion-input>
    </ion-item>
    <control-messages class="error-box" [control]="forgotForm.controls.username"></control-messages>
    <br>
    <p class="or-text">OR</p>
    <ion-item>
      <ion-label floating>Email</ion-label>
      <ion-input type="text" formControlName="email" id="email"></ion-input>
    </ion-item>
    <control-messages class="error-box" [control]="forgotForm.controls.email"></control-messages>
    <br/>
    <ion-buttons>
      <button ion-button class="form-button-text" type="submit" [disabled]="!forgotForm.valid" block round>Email</button>
    </ion-buttons>
  </form>

ts

import { Component } from '@angular/core';
import { NavController, NavParams, AlertController } from 'ionic-angular';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ValidationService } from '../validation/validationService';
import { EmailService } from '../service/emailService';
import { PersonModel } from '../model/PersonModel';

@Component({
  selector: 'forgot',
  templateUrl: 'forgot.html'
})
export class ForgotPage {

  public forgotForm: FormGroup;
  public emailService: EmailService = null;
  public personModel: PersonModel = null;

  constructor(public navCtrl: NavController, public navParams: NavParams, public builder: FormBuilder, emailService: EmailService, public alertCtrl: AlertController) {
    this.emailService = emailService;
    this.personModel = this.navParams.get('personModel');
    this.forgotForm = builder.group({
      'username': ['', [Validators.maxLength(55), ValidationService.userNameValidator]],
      'email': ['', [Validators.maxLength(55), ValidationService.emailValidator]],
    }
    // , {
    //     'validator': ValidationService.oneRequired('username', 'email')
    //   }
     );
  }

  submit() {
    if (this.forgotForm.dirty && this.forgotForm.valid) {
      if (this.forgotForm.value.email || this.forgotForm.value.username) {
        this.forgotPassword(this.forgotForm.value.email, this.forgotForm.value.username);
      }
    }
  }

  forgotPassword(email: string, userName: string) {
    let promise: Promise<string> = this.emailService.sendPassword(email, userName);
    promise.then((msg: string) => {
      this.doAlert(msg);
    });
  }

  doAlert(msg: string) {
    let alert = this.alertCtrl.create({
      title: 'Forgot Password',
      subTitle: msg,
      buttons: ['Dismiss']
    });
    alert.present();
  }
}

package.json

{
  "name": "ionic-hello-world",
  "author": "Ionic Framework",
  "homepage": "http://ionicframework.com/",
  "private": true,
  "scripts": {
    "clean": "ionic-app-scripts clean",
    "build": "ionic-app-scripts build",
    "ionic:build": "ionic-app-scripts build",
    "ionic:serve": "ionic-app-scripts serve"
  },
  "dependencies": {
    "@angular/common": "2.2.1",
    "@angular/compiler": "2.2.1",
    "@angular/compiler-cli": "2.2.1",
    "@angular/core": "2.2.1",
    "@angular/forms": "2.2.1",
    "@angular/http": "2.2.1",
    "@angular/platform-browser": "2.2.1",
    "@angular/platform-browser-dynamic": "2.2.1",
    "@angular/platform-server": "2.2.1",
    "@ionic/storage": "1.1.7",
    "@types/googlemaps": "^3.25.42",
    "angular2-moment": "^1.1.0",
    "angularfire2": "^2.0.0-beta.7.1-pre",
    "firebase": "^3.6.4",
    "ionic-angular": "2.0.0-rc.4",
    "ionic-native": "2.2.11",
    "ionicons": "3.0.0",
    "rxjs": "^5.0.3",
    "zone.js": "0.6.26"
  },
  "devDependencies": {
    "@ionic/app-scripts": "^1.0.0",
    "sw-toolbox": "^3.4.0",
    "typescript": "^2.0.9"
  },
  "cordovaPlugins": [
    "cordova-plugin-whitelist",
    "cordova-plugin-console",
    "cordova-plugin-statusbar",
    "cordova-plugin-device",
    "cordova-plugin-splashscreen",
    "ionic-plugin-keyboard"
  ],
  "cordovaPlatforms": [],
  "description": "theWhoZoo-chat: An Ionic project"
}

Ionic Info

Your system information:

 ordova CLI: 6.4.0
Ionic Framework Version: 2.0.0-rc.4
Ionic CLI Version: 2.1.18
Ionic App Lib Version: 2.1.9
Ionic App Scripts Version: 1.0.0
ios-deploy version: Not installed
ios-sim version: Not installed
OS: Windows 10
Node Version: v6.9.2
Xcode version: Not installed

Any advise appreciated.

UPDATE

Looking at this, if I try import { ControlMessagesComponent } from './control-messages.component'; in app.module.ts I get:

Error: Cannot find module "./control-messages.component"


#2

Chances are you’ve read this article

<control-messages> is a custom component that you have to build.


#3

I just found what I was doing wrong. There was a missing component in app.modules.ts.

Added the following:

import { ControlMessages } from '../pages/validation/controlMessages';

where ControlMessages has a selector of control-messages:

import { Component, Input } from '@angular/core';
import { FormControl } from '@angular/forms';
import { ValidationService } from './validationService';

@Component({
  selector: 'control-messages',
  template: `<div *ngIf="errorMessage !== null">{{errorMessage}}</div>`
})
export class ControlMessages {
  @Input() control: FormControl;

  constructor() {

  }

  get errorMessage(): string {
    for (let propertyName in this.control.errors) {
      if (this.control.errors.hasOwnProperty(propertyName) && this.control.touched) {
        return ValidationService.getValidatorErrorMessage(propertyName, this.control.errors[propertyName]);
      }
    }
    
    return null;
  }
}