{"id":16687370,"url":"https://github.com/rodrigokamada/angular-template-driven-form-validation","last_synced_at":"2025-10-28T12:34:04.259Z","repository":{"id":48826595,"uuid":"430694868","full_name":"rodrigokamada/angular-template-driven-form-validation","owner":"rodrigokamada","description":"Application example built with Angular 14 and creating and validating a template-driven form.","archived":false,"fork":false,"pushed_at":"2022-07-23T14:43:58.000Z","size":2274,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-09T19:48:52.098Z","etag":null,"topics":["angular","beginners","dev-community","form","frontend","gh-actions","gh-pages","hacktoberfest","stackblitz","tutorial","validation","webdev","webdevelopment"],"latest_commit_sha":null,"homepage":"https://rodrigokamada.github.io/angular-template-driven-form-validation/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rodrigokamada.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":"rodrigokamada"}},"created_at":"2021-11-22T12:19:53.000Z","updated_at":"2023-10-29T16:53:48.000Z","dependencies_parsed_at":"2022-09-04T22:34:26.100Z","dependency_job_id":null,"html_url":"https://github.com/rodrigokamada/angular-template-driven-form-validation","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rodrigokamada%2Fangular-template-driven-form-validation","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rodrigokamada%2Fangular-template-driven-form-validation/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rodrigokamada%2Fangular-template-driven-form-validation/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rodrigokamada%2Fangular-template-driven-form-validation/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rodrigokamada","download_url":"https://codeload.github.com/rodrigokamada/angular-template-driven-form-validation/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243439940,"owners_count":20291200,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["angular","beginners","dev-community","form","frontend","gh-actions","gh-pages","hacktoberfest","stackblitz","tutorial","validation","webdev","webdevelopment"],"created_at":"2024-10-12T15:08:57.547Z","updated_at":"2025-10-28T12:33:59.212Z","avatar_url":"https://github.com/rodrigokamada.png","language":"TypeScript","readme":"# Angular Template-Driven Form Validation\n\n\nApplication example built with [Angular](https://angular.io/) 14 and creating and validating a template-driven form.\n\nThis tutorial was posted on my [blog](https://rodrigo.kamada.com.br/blog/adicionando-validacao-no-formulario-em-uma-aplicacao-angular) in portuguese and on the [DEV Community](https://dev.to/rodrigokamada/adding-form-validation-to-an-angular-application-387a) in english.\n\n\n\n[![Website](https://shields.braskam.com/v1/shields?name=website\u0026format=rectangle\u0026size=small\u0026radius=5)](https://rodrigo.kamada.com.br)\n[![LinkedIn](https://shields.braskam.com/v1/shields?name=linkedin\u0026format=rectangle\u0026size=small\u0026radius=5)](https://www.linkedin.com/in/rodrigokamada)\n[![Twitter](https://shields.braskam.com/v1/shields?name=twitter\u0026format=rectangle\u0026size=small\u0026radius=5\u0026socialAccount=rodrigokamada)](https://twitter.com/rodrigokamada)\n[![Instagram](https://shields.braskam.com/v1/shields?name=instagram\u0026format=rectangle\u0026size=small\u0026radius=5)](https://www.instagram.com/rodrigokamada)\n\n\n\n## Prerequisites\n\n\nBefore you start, you need to install and configure the tools:\n\n* [git](https://git-scm.com/)\n* [Node.js and npm](https://nodejs.org/)\n* [Angular CLI](https://angular.io/cli)\n* IDE (e.g. [Visual Studio Code](https://code.visualstudio.com/) or [WebStorm](https://www.jetbrains.com/webstorm/))\n\n\n\n## Getting started\n\n\n### Create the Angular application\n\n\n**1.** Let's create the application with the Angular base structure using the `@angular/cli` with the route file and the SCSS style format.\n\n```powershell\nng new angular-template-driven-form-validation --routing true --style scss\nCREATE angular-template-driven-form-validation/README.md (1063 bytes)\nCREATE angular-template-driven-form-validation/.editorconfig (274 bytes)\nCREATE angular-template-driven-form-validation/.gitignore (620 bytes)\nCREATE angular-template-driven-form-validation/angular.json (3279 bytes)\nCREATE angular-template-driven-form-validation/package.json (1082 bytes)\nCREATE angular-template-driven-form-validation/tsconfig.json (863 bytes)\nCREATE angular-template-driven-form-validation/.browserslistrc (600 bytes)\nCREATE angular-template-driven-form-validation/karma.conf.js (1435 bytes)\nCREATE angular-template-driven-form-validation/tsconfig.app.json (287 bytes)\nCREATE angular-template-driven-form-validation/tsconfig.spec.json (333 bytes)\nCREATE angular-template-driven-form-validation/src/favicon.ico (948 bytes)\nCREATE angular-template-driven-form-validation/src/index.html (303 bytes)\nCREATE angular-template-driven-form-validation/src/main.ts (372 bytes)\nCREATE angular-template-driven-form-validation/src/polyfills.ts (2338 bytes)\nCREATE angular-template-driven-form-validation/src/styles.scss (80 bytes)\nCREATE angular-template-driven-form-validation/src/test.ts (745 bytes)\nCREATE angular-template-driven-form-validation/src/assets/.gitkeep (0 bytes)\nCREATE angular-template-driven-form-validation/src/environments/environment.prod.ts (51 bytes)\nCREATE angular-template-driven-form-validation/src/environments/environment.ts (658 bytes)\nCREATE angular-template-driven-form-validation/src/app/app-routing.module.ts (245 bytes)\nCREATE angular-template-driven-form-validation/src/app/app.module.ts (393 bytes)\nCREATE angular-template-driven-form-validation/src/app/app.component.scss (0 bytes)\nCREATE angular-template-driven-form-validation/src/app/app.component.html (23364 bytes)\nCREATE angular-template-driven-form-validation/src/app/app.component.spec.ts (1109 bytes)\nCREATE angular-template-driven-form-validation/src/app/app.component.ts (223 bytes)\n✔ Packages installed successfully.\n    Successfully initialized git.\n```\n\n**2.** Install and configure the Bootstrap CSS framework. Do steps 2 and 3 of the post *[Adding the Bootstrap CSS framework to an Angular application](https://github.com/rodrigokamada/angular-bootstrap)*.\n\n**3.** Let's create a custom validator for the email field. Create the `EmailValidatorDirective` directive.\n\n```powershell\nng generate directive email-validator --skip-tests=true\nCREATE src/app/email-validator.directive.ts (157 bytes)\nUPDATE src/app/app.module.ts (592 bytes)\n```\n\n**4.** Change the `src/app/email-validator.directive.ts` file. Implement the `Validator` interface as below.\n\n```typescript\nimport { Directive } from '@angular/core';\nimport { NG_VALIDATORS, AbstractControl, Validator, ValidationErrors, ValidatorFn } from '@angular/forms';\n\nexport function emailValidator(): ValidatorFn {\n\n  const EMAIL_REGEXP = /^(([^\u003c\u003e()\\[\\]\\.,;:\\s@\\\"]+(\\.[^\u003c\u003e()\\[\\]\\.,;:\\s@\\\"]+)*)|(\\\".+\\\"))@(([^\u003c\u003e()[\\]\\.,;:\\s@\\\"]+\\.)+[^\u003c\u003e()[\\]\\.,;:\\s@\\\"]{2,})$/i;\n\n  return (control: AbstractControl): ValidationErrors | null =\u003e {\n    const isValid = EMAIL_REGEXP.test(control.value);\n\n    if (isValid) {\n      return null;\n    } else {\n      return {\n        emailValidator: {\n          valid: false,\n        },\n      };\n    }\n  };\n\n}\n\n@Directive({\n  selector: '[appEmailValidator]',\n  providers: [{\n    provide: NG_VALIDATORS,\n    useExisting: EmailValidatorDirective,\n    multi: true,\n  }],\n})\nexport class EmailValidatorDirective implements Validator {\n\n  constructor() {\n  }\n\n  public validate(control: AbstractControl): ValidationErrors | null {\n    return emailValidator()(control);\n  }\n\n}\n```\n\n**5.** Change the `src/app/app.component.ts` file. Import the `NgForm` service, create the `IUser` interface and create the `validate` function as below.\n\n```typescript\nimport { Component } from '@angular/core';\nimport { NgForm } from '@angular/forms';\n\ninterface IUser {\n  name: string;\n  nickname: string;\n  email: string;\n  password: string;\n  showPassword: boolean\n}\n\n@Component({\n  selector: 'app-root',\n  templateUrl: './app.component.html',\n  styleUrls: ['./app.component.scss'],\n})\nexport class AppComponent {\n\n  user: IUser;\n\n  constructor() {\n    this.user = {} as IUser;\n  }\n\n  public validate(form: NgForm): void {\n    if (form.invalid) {\n      for (const control of Object.keys(form.controls)) {\n        form.controls[control].markAsTouched();\n      }\n      return;\n    }\n\n    console.info('Name:', this.user.name);\n    console.info('Nickname:', this.user.nickname);\n    console.info('Email:', this.user.email);\n    console.info('Password:', this.user.password);\n  }\n\n}\n```\n\n**6.** Change the `src/app/app.component.html` file. Add the form as below.\n\n```html\n\u003cdiv class=\"container-fluid py-3\"\u003e\n  \u003ch1\u003eAngular Template-Driven Form Validation\u003c/h1\u003e\n\n  \u003cdiv class=\"row justify-content-center my-5\"\u003e\n    \u003cdiv class=\"col-4\"\u003e\n      \u003cform #form=\"ngForm\"\u003e\n        \u003cdiv class=\"row\"\u003e\n          \u003cdiv class=\"col mb-2\"\u003e\n            \u003clabel for=\"name\" class=\"form-label\"\u003eName:\u003c/label\u003e\n            \u003cinput type=\"text\" id=\"name\" name=\"name\" #name=\"ngModel\" [(ngModel)]=\"user.name\" placeholder=\"Your name\" required minlength=\"1\" maxlength=\"250\" class=\"form-control form-control-sm\" [class.is-invalid]=\"name.invalid \u0026\u0026 (name.dirty || name.touched)\"\u003e\n            \u003cdiv *ngIf=\"name.invalid \u0026\u0026 (name.dirty || name.touched)\" class=\"invalid-feedback\"\u003e\n              \u003cdiv *ngIf=\"name.errors?.['required']\"\u003e\n                This field is required.\n              \u003c/div\u003e\n              \u003cdiv *ngIf=\"name.errors?.['minlength']\"\u003e\n                This field must have at least 1 character.\n              \u003c/div\u003e\n              \u003cdiv *ngIf=\"name.errors?.['maxlength']\"\u003e\n                This field must have at most 250 characters.\n              \u003c/div\u003e\n            \u003c/div\u003e\n          \u003c/div\u003e\n        \u003c/div\u003e\n        \u003cdiv class=\"row\"\u003e\n          \u003cdiv class=\"col mb-2\"\u003e\n            \u003clabel for=\"nickname\" class=\"form-label\"\u003eNickname:\u003c/label\u003e\n            \u003cinput type=\"text\" id=\"nickname\" name=\"nickname\" #nickname=\"ngModel\" [(ngModel)]=\"user.nickname\" placeholder=\"Your nickname\" maxlength=\"10\" class=\"form-control form-control-sm\" [class.is-invalid]=\"nickname.invalid \u0026\u0026 (nickname.dirty || nickname.touched)\"\u003e\n            \u003cdiv *ngIf=\"nickname.invalid \u0026\u0026 (nickname.dirty || nickname.touched)\" class=\"invalid-feedback\"\u003e\n              \u003cdiv *ngIf=\"nickname.errors?.['maxlength']\"\u003e\n                This field must have at most 10 characters.\n              \u003c/div\u003e\n            \u003c/div\u003e\n          \u003c/div\u003e\n        \u003c/div\u003e\n        \u003cdiv class=\"row\"\u003e\n          \u003cdiv class=\"col mb-2\"\u003e\n            \u003clabel for=\"email\" class=\"form-label\"\u003eEmail:\u003c/label\u003e\n            \u003cinput type=\"email\" id=\"email\" name=\"email\" #email=\"ngModel\" [(ngModel)]=\"user.email\" placeholder=\"your-name@provider.com\" required minlength=\"1\" maxlength=\"250\" appEmailValidator class=\"form-control form-control-sm\" [class.is-invalid]=\"email.invalid \u0026\u0026 (email.dirty || email.touched)\"\u003e\n            \u003cdiv *ngIf=\"email.invalid \u0026\u0026 (email.dirty || email.touched)\" class=\"invalid-feedback\"\u003e\n              \u003cdiv *ngIf=\"email.errors?.['required']\"\u003e\n                This field is required.\n              \u003c/div\u003e\n              \u003cdiv *ngIf=\"email.errors?.['minlength']\"\u003e\n                This field must have at least 1 character.\n              \u003c/div\u003e\n              \u003cdiv *ngIf=\"email.errors?.['maxlength']\"\u003e\n                This field must have at most 250 characters.\n              \u003c/div\u003e\n              \u003cdiv *ngIf=\"!email.errors?.['required'] \u0026\u0026 !email.errors?.['minlength'] \u0026\u0026 !email.errors?.['maxlength'] \u0026\u0026 email.errors?.['emailValidator']\"\u003e\n                Invalid email format.\n              \u003c/div\u003e\n            \u003c/div\u003e\n          \u003c/div\u003e\n        \u003c/div\u003e\n        \u003cdiv class=\"row\"\u003e\n          \u003cdiv class=\"col mb-2\"\u003e\n            \u003clabel for=\"password\" class=\"form-label\"\u003ePassword:\u003c/label\u003e\n            \u003cdiv class=\"input-group input-group-sm has-validation\"\u003e\n              \u003cinput [type]=\"user.showPassword ? 'text' : 'password'\" id=\"password\" name=\"password\" #password=\"ngModel\" [(ngModel)]=\"user.password\" required minlength=\"15\" class=\"form-control form-control-sm\" [class.is-invalid]=\"password.invalid \u0026\u0026 (password.dirty || password.touched)\"\u003e\n              \u003cbutton type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"user.showPassword = !user.showPassword\"\u003e\n                \u003ci class=\"bi\" [ngClass]=\"{'bi-eye-fill': !user.showPassword, 'bi-eye-slash-fill': user.showPassword}\"\u003e\u003c/i\u003e\n              \u003c/button\u003e\n              \u003cdiv *ngIf=\"password.invalid \u0026\u0026 (password.dirty || password.touched)\" class=\"invalid-feedback\"\u003e\n                \u003cdiv *ngIf=\"password.errors?.['required']\"\u003e\n                  This field is required.\n                \u003c/div\u003e\n                \u003cdiv *ngIf=\"password.errors?.['minlength']\"\u003e\n                  This field must have at least 15 characters.\n                \u003c/div\u003e\n              \u003c/div\u003e\n            \u003c/div\u003e\n          \u003c/div\u003e\n        \u003c/div\u003e\n        \u003cdiv class=\"row\"\u003e\n          \u003cdiv class=\"col mb-2 d-grid\"\u003e\n            \u003cbutton type=\"button\" class=\"btn btn-sm btn-primary\" (click)=\"validate(form)\"\u003eValidate\u003c/button\u003e\n          \u003c/div\u003e\n        \u003c/div\u003e\n      \u003c/form\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\n**7.** Change the `src/app/app.module.ts` file. Import the `FormsModule` module and the `EmailValidatorDirective` directive as below.\n\n```typescript\nimport { FormsModule } from '@angular/forms';\n\nimport { EmailValidatorDirective } from './email-validator.directive';\n\ndeclarations: [\n  AppComponent,\n  EmailValidatorDirective,\n],\nimports: [\n  BrowserModule,\n  FormsModule,\n  AppRoutingModule,\n],\n```\n\n**8.** Run the application with the command below.\n\n```powershell\nnpm start\n\n\u003e angular-template-driven-form-validation@1.0.0 start\n\u003e ng serve\n\n✔ Browser application bundle generation complete.\n\nInitial Chunk Files   | Names         |      Size\nvendor.js             | vendor        |   2.38 MB\nstyles.css, styles.js | styles        | 486.75 kB\npolyfills.js          | polyfills     | 339.09 kB\nscripts.js            | scripts       |  76.33 kB\nmain.js               | main          |  29.11 kB\nruntime.js            | runtime       |   6.87 kB\n\n                      | Initial Total |   3.30 MB\n\nBuild at: 2021-11-22T16:32:20.056Z - Hash: 1789217f1a21bafa - Time: 3632ms\n\n** Angular Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ **\n\n\n✔ Compiled successfully.\n```\n\n**9.** Ready! Access the URL `http://localhost:4200/` and check if the application is working. See the application working on [GitHub Pages](https://rodrigokamada.github.io/angular-template-driven-form-validation/) and [Stackblitz](https://stackblitz.com/edit/angular14-template-driven-form-validation).\n\n![Angular Template-Driven Form Validation](https://res.cloudinary.com/rodrigokamada/image/upload/v1637606970/Blog/angular-validation/angular-validation.png)\n\n\n\n## Cloning the application\n\n**1.** Clone the repository.\n\n```powershell\ngit clone git@github.com:rodrigokamada/angular-template-driven-form-validation.git\n```\n\n**2.** Install the dependencies.\n\n```powershell\nnpm ci\n```\n\n**3.** Run the application.\n\n```powershell\nnpm start\n```\n","funding_links":["https://github.com/sponsors/rodrigokamada"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frodrigokamada%2Fangular-template-driven-form-validation","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frodrigokamada%2Fangular-template-driven-form-validation","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frodrigokamada%2Fangular-template-driven-form-validation/lists"}