Client: reactive forms
authorChocobozzz <florian.bigard@gmail.com>
Fri, 9 Sep 2016 20:16:51 +0000 (22:16 +0200)
committerChocobozzz <florian.bigard@gmail.com>
Fri, 9 Sep 2016 20:16:51 +0000 (22:16 +0200)
20 files changed:
client/src/app/account/account.component.html
client/src/app/account/account.component.ts
client/src/app/admin/friends/friend-add/friend-add.component.html
client/src/app/admin/friends/friend-add/friend-add.component.ts
client/src/app/admin/users/user-add/user-add.component.html
client/src/app/admin/users/user-add/user-add.component.ts
client/src/app/app.module.ts
client/src/app/login/login.component.html
client/src/app/login/login.component.ts
client/src/app/shared/form-validators/index.ts [deleted file]
client/src/app/shared/form-validators/url.validator.ts [deleted file]
client/src/app/shared/forms/form-reactive.ts [new file with mode: 0644]
client/src/app/shared/forms/form-validators/index.ts [new file with mode: 0644]
client/src/app/shared/forms/form-validators/url.validator.ts [new file with mode: 0644]
client/src/app/shared/forms/form-validators/user.ts [new file with mode: 0644]
client/src/app/shared/forms/form-validators/video.ts [new file with mode: 0644]
client/src/app/shared/forms/index.ts [new file with mode: 0644]
client/src/app/shared/index.ts
client/src/app/videos/video-add/video-add.component.html
client/src/app/videos/video-add/video-add.component.ts

index 4797fa9149c3e39157b0319fcc516e29f20186ae..5a8847acda812bf45b0970197ba72bf86451da70 100644 (file)
@@ -3,25 +3,25 @@
 <div *ngIf="information" class="alert alert-success">{{ information }}</div>
 <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
 
-<form role="form" (ngSubmit)="changePassword()" [formGroup]="changePasswordForm">
+<form role="form" (ngSubmit)="changePassword()" [formGroup]="form">
   <div class="form-group">
     <label for="new-password">New password</label>
     <input
-      type="password" class="form-control" name="new-password" id="new-password"
-      [(ngModel)]="newPassword" #newPasswordInput="ngModel"
+      type="password" class="form-control" id="new-password"
+      formControlName="new-password"
     >
-    <div [hidden]="changePasswordForm.controls['new-password'].valid || changePasswordForm.controls['new-password'].pristine" class="alert alert-warning">
-      The password should have more than 5 characters
+    <div *ngIf="formErrors['new-password']" class="alert alert-danger">
+      {{ formErrors['new-password'] }}
     </div>
   </div>
 
   <div class="form-group">
     <label for="name">Confirm new password</label>
     <input
-      type="password" class="form-control" name="new-confirmed-password" id="new-confirmed-password"
-      [(ngModel)]="newConfirmedPassword" #newConfirmedPasswordInput="ngModel"
+      type="password" class="form-control" id="new-confirmed-password"
+      formControlName="new-confirmed-password"
     >
   </div>
 
-  <input type="submit" value="Change password" class="btn btn-default" [disabled]="!changePasswordForm.valid">
+  <input type="submit" value="Change password" class="btn btn-default" [disabled]="!form.valid">
 </form>
index a22738d3f509a6ce011db8a7140ea923ff22c266..b503406c93f2b5e4299302cf1d755de093fb01e4 100644 (file)
@@ -1,44 +1,64 @@
 import {  } from '@angular/common';
 import { Component, OnInit } from '@angular/core';
-import { FormControl, FormGroup, Validators } from '@angular/forms';
+import { FormBuilder, FormGroup } from '@angular/forms';
 import { Router } from '@angular/router';
 
 import { AccountService } from './account.service';
+import { FormReactive, USER_PASSWORD } from '../shared';
 
 @Component({
   selector: 'my-account',
   template: require('./account.component.html')
 })
 
-export class AccountComponent implements OnInit {
-  newPassword = '';
-  newConfirmedPassword = '';
-  changePasswordForm: FormGroup;
+export class AccountComponent extends FormReactive implements OnInit {
   information: string = null;
   error: string = null;
 
+  form: FormGroup;
+  formErrors = {
+    'new-password': '',
+    'new-confirmed-password': ''
+  };
+  validationMessages = {
+    'new-password': USER_PASSWORD.MESSAGES,
+    'new-confirmed-password': USER_PASSWORD.MESSAGES
+  };
+
   constructor(
     private accountService: AccountService,
+    private formBuilder: FormBuilder,
     private router: Router
-  ) {}
+  ) {
+    super();
+  }
 
-  ngOnInit() {
-    this.changePasswordForm = new FormGroup({
-      'new-password': new FormControl('', [ <any>Validators.required, <any>Validators.minLength(6) ]),
-      'new-confirmed-password': new FormControl('', [ <any>Validators.required, <any>Validators.minLength(6) ]),
+  buildForm() {
+    this.form = this.formBuilder.group({
+      'new-password': [ '', USER_PASSWORD.VALIDATORS ],
+      'new-confirmed-password': [ '', USER_PASSWORD.VALIDATORS ],
     });
+
+    this.form.valueChanges.subscribe(data => this.onValueChanged(data));
+  }
+
+  ngOnInit() {
+    this.buildForm();
   }
 
   changePassword() {
+    const newPassword = this.form.value['new-password'];
+    const newConfirmedPassword = this.form.value['new-confirmed-password'];
+
     this.information = null;
     this.error = null;
 
-    if (this.newPassword !== this.newConfirmedPassword) {
+    if (newPassword !== newConfirmedPassword) {
       this.error = 'The new password and the confirmed password do not correspond.';
       return;
     }
 
-    this.accountService.changePassword(this.newPassword).subscribe(
+    this.accountService.changePassword(newPassword).subscribe(
       ok => this.information = 'Password updated.',
 
       err => this.error = err
index 5b8dc8d87a04bdf7de9b7024577b857c152ba241..788f3b44d168d5894aabc4316e9079fb898c13ca 100644 (file)
@@ -2,14 +2,14 @@
 
 <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
 
-<form (ngSubmit)="makeFriends()" [formGroup]="friendAddForm">
+<form (ngSubmit)="makeFriends()" [formGroup]="form">
   <div class="form-group"  *ngFor="let url of urls; let id = index; trackBy:customTrackBy">
     <label for="username">Url</label>
 
     <div class="input-group">
       <input
         type="text" class="form-control" placeholder="http://domain.com"
-        [name]="'url-' + id"  [id]="'url-' + id" [formControlName]="'url-' + id" [(ngModel)]="urls[id]"
+        [id]="'url-' + id" [formControlName]="'url-' + id"
       />
       <span class="input-group-btn">
         <button *ngIf="displayAddField(id)" (click)="addField()" class="btn btn-default" type="button">+</button>
@@ -17,7 +17,7 @@
       </span>
     </div>
 
-    <div [hidden]="friendAddForm.controls['url-' + id].valid || friendAddForm.controls['url-' + id].pristine" class="alert alert-warning">
+    <div [hidden]="form.controls['url-' + id].valid || form.controls['url-' + id].pristine" class="alert alert-warning">
       It should be a valid url.
     </div>
   </div>
index 55aed9156a6364c628de5305ff66271d0d053b7e..68363b482509cae5c237232fd141faacf29c708c 100644 (file)
@@ -11,19 +11,19 @@ import { FriendService } from '../shared';
   styles: [ require('./friend-add.component.scss') ]
 })
 export class FriendAddComponent implements OnInit {
-  friendAddForm: FormGroup;
+  form: FormGroup;
   urls = [ ];
   error: string = null;
 
   constructor(private router: Router, private friendService: FriendService) {}
 
   ngOnInit() {
-    this.friendAddForm = new FormGroup({});
+    this.form = new FormGroup({});
     this.addField();
   }
 
   addField() {
-    this.friendAddForm.addControl(`url-${this.urls.length}`, new FormControl('', [ validateUrl ]));
+    this.form.addControl(`url-${this.urls.length}`, new FormControl('', [ validateUrl ]));
     this.urls.push('');
   }
 
@@ -42,7 +42,7 @@ export class FriendAddComponent implements OnInit {
   isFormValid() {
     // Do not check the last input
     for (let i = 0; i < this.urls.length - 1; i++) {
-      if (!this.friendAddForm.controls[`url-${i}`].valid) return false;
+      if (!this.form.controls[`url-${i}`].valid) return false;
     }
 
     const lastIndex = this.urls.length - 1;
@@ -50,13 +50,13 @@ export class FriendAddComponent implements OnInit {
     if (this.urls[lastIndex] === '' && lastIndex !== 0) {
       return true;
     } else {
-      return this.friendAddForm.controls[`url-${lastIndex}`].valid;
+      return this.form.controls[`url-${lastIndex}`].valid;
     }
   }
 
   removeField(index: number) {
     // Remove the last control
-    this.friendAddForm.removeControl(`url-${this.urls.length - 1}`);
+    this.form.removeControl(`url-${this.urls.length - 1}`);
     this.urls.splice(index, 1);
   }
 
@@ -94,7 +94,8 @@ export class FriendAddComponent implements OnInit {
   private getNotEmptyUrls() {
     const notEmptyUrls = [];
 
-    this.urls.forEach((url) => {
+    Object.keys(this.form.value).forEach((urlKey) => {
+      const url = this.form.value[urlKey];
       if (url !== '') notEmptyUrls.push(url);
     });
 
index 09219893b882ab0824f36a4bc2500e88dd9ceb1c..9b76c7c1b95cda3e7a381aed632be6af8090b0c5 100644 (file)
@@ -2,28 +2,28 @@
 
 <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
 
-<form role="form" (ngSubmit)="addUser()" [formGroup]="userAddForm">
+<form role="form" (ngSubmit)="addUser()" [formGroup]="form">
   <div class="form-group">
     <label for="username">Username</label>
     <input
-      type="text" class="form-control" name="username" id="username" placeholder="Username"
-      [(ngModel)]="username"
+      type="text" class="form-control" id="username" placeholder="Username"
+      formControlName="username"
     >
-    <div [hidden]="userAddForm.controls.username.valid || userAddForm.controls.username.pristine" class="alert alert-danger">
-      Username is required with a length >= 3 and <= 20
+    <div *ngIf="formErrors.username" class="alert alert-danger">
+      {{ formErrors.username }}
     </div>
   </div>
 
   <div class="form-group">
     <label for="password">Password</label>
     <input
-      type="password" class="form-control" name="password" id="password" placeholder="Password"
-      [(ngModel)]="password"
+      type="password" class="form-control" id="password" placeholder="Password"
+      formControlName="password"
     >
-    <div [hidden]="userAddForm.controls.password.valid || userAddForm.controls.password.pristine" class="alert alert-danger">
-      Password is required with a length >= 6
+    <div *ngIf="formErrors.password" class="alert alert-danger">
+      {{ formErrors.password }}
     </div>
   </div>
 
-  <input type="submit" value="Add user" class="btn btn-default" [disabled]="!userAddForm.valid">
+  <input type="submit" value="Add user" class="btn btn-default" [disabled]="!form.valid">
 </form>
index e3f4b2e1aa266506f472b1abbc6803a2373cebd1..b79437795e9b2edc5be1f6d401f9f4d69aed0766 100644 (file)
@@ -1,32 +1,54 @@
 import { Component, OnInit } from '@angular/core';
-import { FormGroup, FormControl, Validators } from '@angular/forms';
+import { FormBuilder, FormGroup } from '@angular/forms';
 import { Router } from '@angular/router';
 
 import { UserService } from '../shared';
+import { FormReactive, USER_USERNAME, USER_PASSWORD } from '../../../shared';
 
 @Component({
   selector: 'my-user-add',
   template: require('./user-add.component.html')
 })
-export class UserAddComponent implements OnInit {
-  userAddForm: FormGroup;
+export class UserAddComponent extends FormReactive implements OnInit {
   error: string = null;
-  username = '';
-  password = '';
 
-  constructor(private router: Router, private userService: UserService) {}
+  form: FormGroup;
+  formErrors = {
+    'username': '',
+    'password': ''
+  };
+  validationMessages = {
+    'username': USER_USERNAME.MESSAGES,
+    'password': USER_PASSWORD.MESSAGES,
+  };
 
-  ngOnInit() {
-    this.userAddForm = new FormGroup({
-      username: new FormControl('', [ <any>Validators.required, <any>Validators.minLength(3), <any>Validators.maxLength(20) ]),
-      password: new FormControl('', [ <any>Validators.required, <any>Validators.minLength(6) ]),
+  constructor(
+    private formBuilder: FormBuilder,
+    private router: Router,
+    private userService: UserService
+  ) {
+    super();
+  }
+
+  buildForm() {
+    this.form = this.formBuilder.group({
+      username: [ '', USER_USERNAME.VALIDATORS ],
+      password: [ '', USER_PASSWORD.VALIDATORS ],
     });
+
+    this.form.valueChanges.subscribe(data => this.onValueChanged(data));
+  }
+
+  ngOnInit() {
+    this.buildForm();
   }
 
   addUser() {
     this.error = null;
 
-    this.userService.addUser(this.username, this.password).subscribe(
+    const { username, password } = this.form.value;
+
+    this.userService.addUser(username, password).subscribe(
       ok => this.router.navigate([ '/admin/users/list' ]),
 
       err => this.error = err.text
index 950b3c48e3117d2eced9140b64309278e35f2905..f071224c5fbf2094b69a8b22fe5e5715601ebe74 100644 (file)
@@ -28,7 +28,8 @@ import {
   VideoMiniatureComponent,
   VideoSortComponent,
   VideoWatchComponent,
-  VideoService
+  VideoService,
+  WebTorrentService
 } from './videos';
 import {
   FriendsComponent,
@@ -59,7 +60,7 @@ const APP_PROVIDERS = [
 
   AuthService,
   RestExtractor,
-  RestExtractor, RestService, VideoService, SearchService, FriendService, UserService, AccountService
+  RestExtractor, RestService, VideoService, SearchService, FriendService, UserService, AccountService, WebTorrentService
 ];
 /**
  * `AppModule` is the main entry point into Angular2's bootstraping process
index 6368729420f1464bcc7af21bdd44eed6c305da3b..94a405405baac501c98b536a8422702e826e2e88 100644 (file)
@@ -2,28 +2,28 @@
 
 <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
 
-<form role="form" (ngSubmit)="login()" [formGroup]="loginForm">
+<form role="form" (ngSubmit)="login()" [formGroup]="form">
   <div class="form-group">
     <label for="username">Username</label>
     <input
-      type="text" class="form-control" name="username" id="username" placeholder="Username"
-      [(ngModel)]="username"
+      type="text" class="form-control" id="username" placeholder="Username" required
+      formControlName="username"
     >
-    <div [hidden]="loginForm.controls.username.valid || loginForm.controls.username.pristine" class="alert alert-danger">
-      Username is required
+    <div *ngIf="formErrors.username" class="alert alert-danger">
+      {{ formErrors.username }}
     </div>
   </div>
 
   <div class="form-group">
     <label for="password">Password</label>
     <input
-      type="password" class="form-control" name="password" id="password" placeholder="Password"
-      [(ngModel)]="password"
+      type="password" class="form-control" name="password" id="password" placeholder="Password" required
+      formControlName="password"
     >
-    <div [hidden]="loginForm.controls.password.valid || loginForm.controls.password.pristine" class="alert alert-danger">
-      Password is required
+    <div *ngIf="formErrors.password" class="alert alert-danger">
+      {{ formErrors.password }}
     </div>
   </div>
 
-  <input type="submit" value="Login" class="btn btn-default" [disabled]="!loginForm.valid">
+  <input type="submit" value="Login" class="btn btn-default" [disabled]="!form.valid">
 </form>
index 7a4e15c2c28ff607ebc973d44c9d8dc98f17058e..378714ca1ce540c246ce0f90619e400de3c51e1d 100644 (file)
@@ -1,39 +1,60 @@
 import { Component, OnInit } from '@angular/core';
-import { FormControl, FormGroup, Validators } from '@angular/forms';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
 import { Router } from '@angular/router';
 
-import { AuthService } from '../shared';
+import { AuthService, FormReactive } from '../shared';
 
 @Component({
   selector: 'my-login',
   template: require('./login.component.html')
 })
 
-export class LoginComponent implements OnInit {
+export class LoginComponent extends FormReactive implements OnInit {
   error: string = null;
-  username = '';
-  password: '';
-  loginForm: FormGroup;
+
+  form: FormGroup;
+  formErrors = {
+    'username': '',
+    'password': ''
+  };
+  validationMessages = {
+    'username': {
+      'required': 'Username is required.',
+    },
+    'password': {
+      'required': 'Password is required.'
+    }
+  };
 
   constructor(
     private authService: AuthService,
+    private formBuilder: FormBuilder,
     private router: Router
-  ) {}
+  ) {
+    super();
+  }
 
-  ngOnInit() {
-    this.loginForm = new FormGroup({
-      username: new FormControl('', [ <any>Validators.required ]),
-      password: new FormControl('', [ <any>Validators.required ]),
+  buildForm() {
+    this.form = this.formBuilder.group({
+      username: [ '', Validators.required ],
+      password: [ '', Validators.required ],
     });
+
+    this.form.valueChanges.subscribe(data => this.onValueChanged(data));
+  }
+
+  ngOnInit() {
+    this.buildForm();
   }
 
   login() {
-    this.authService.login(this.username, this.password).subscribe(
-      result => {
-        this.error = null;
+    this.error = null;
+
+    const { username, password } = this.form.value;
+
+    this.authService.login(username, password).subscribe(
+      result => this.router.navigate(['/videos/list']),
 
-        this.router.navigate(['/videos/list']);
-      },
       error => {
         console.error(error.json);
 
diff --git a/client/src/app/shared/form-validators/index.ts b/client/src/app/shared/form-validators/index.ts
deleted file mode 100644 (file)
index f9e9a61..0000000
+++ /dev/null
@@ -1 +0,0 @@
-export * from './url.validator';
diff --git a/client/src/app/shared/form-validators/url.validator.ts b/client/src/app/shared/form-validators/url.validator.ts
deleted file mode 100644 (file)
index 67163b4..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-import { FormControl } from '@angular/forms';
-
-export function validateUrl(c: FormControl) {
-  let URL_REGEXP = new RegExp('^https?://(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$');
-
-  return URL_REGEXP.test(c.value) ? null : {
-    validateUrl: {
-      valid: false
-    }
-  };
-}
diff --git a/client/src/app/shared/forms/form-reactive.ts b/client/src/app/shared/forms/form-reactive.ts
new file mode 100644 (file)
index 0000000..1e8a697
--- /dev/null
@@ -0,0 +1,24 @@
+import { FormGroup } from '@angular/forms';
+
+export abstract class FormReactive {
+  abstract form: FormGroup;
+  abstract formErrors: Object;
+  abstract validationMessages: Object;
+
+  abstract buildForm(): void;
+
+  protected onValueChanged(data?: any) {
+    for (const field in this.formErrors) {
+      // clear previous error message (if any)
+      this.formErrors[field] = '';
+      const control = this.form.get(field);
+
+      if (control && control.dirty && !control.valid) {
+        const messages = this.validationMessages[field];
+        for (const key in control.errors) {
+          this.formErrors[field] += messages[key] + ' ';
+        }
+      }
+    }
+  }
+}
diff --git a/client/src/app/shared/forms/form-validators/index.ts b/client/src/app/shared/forms/form-validators/index.ts
new file mode 100644 (file)
index 0000000..1d2ae6f
--- /dev/null
@@ -0,0 +1,3 @@
+export * from './url.validator';
+export * from './user';
+export * from './video';
diff --git a/client/src/app/shared/forms/form-validators/url.validator.ts b/client/src/app/shared/forms/form-validators/url.validator.ts
new file mode 100644 (file)
index 0000000..67163b4
--- /dev/null
@@ -0,0 +1,11 @@
+import { FormControl } from '@angular/forms';
+
+export function validateUrl(c: FormControl) {
+  let URL_REGEXP = new RegExp('^https?://(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$');
+
+  return URL_REGEXP.test(c.value) ? null : {
+    validateUrl: {
+      valid: false
+    }
+  };
+}
diff --git a/client/src/app/shared/forms/form-validators/user.ts b/client/src/app/shared/forms/form-validators/user.ts
new file mode 100644 (file)
index 0000000..5b11ff2
--- /dev/null
@@ -0,0 +1,17 @@
+import { Validators } from '@angular/forms';
+
+export const USER_USERNAME = {
+  VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(20) ],
+  MESSAGES: {
+    'required': 'Username is required.',
+    'minlength': 'Username must be at least 3 characters long.',
+    'maxlength': 'Username cannot be more than 20 characters long.'
+  }
+};
+export const USER_PASSWORD = {
+  VALIDATORS: [ Validators.required, Validators.minLength(6) ],
+  MESSAGES: {
+    'required': 'Password is required.',
+    'minlength': 'Password must be at least 6 characters long.',
+  }
+};
diff --git a/client/src/app/shared/forms/form-validators/video.ts b/client/src/app/shared/forms/form-validators/video.ts
new file mode 100644 (file)
index 0000000..3766d40
--- /dev/null
@@ -0,0 +1,25 @@
+import { Validators } from '@angular/forms';
+
+export const VIDEO_NAME = {
+  VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(50) ],
+  MESSAGES: {
+    'required': 'Video name is required.',
+    'minlength': 'Video name must be at least 3 characters long.',
+    'maxlength': 'Video name cannot be more than 50 characters long.'
+  }
+};
+export const VIDEO_DESCRIPTION = {
+  VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(250) ],
+  MESSAGES: {
+    'required': 'Video description is required.',
+    'minlength': 'Video description must be at least 3 characters long.',
+    'maxlength': 'Video description cannot be more than 250 characters long.'
+  }
+};
+
+export const VIDEO_TAGS = {
+  VALIDATORS: [ Validators.pattern('^[a-zA-Z0-9]{2,10}$') ],
+  MESSAGES: {
+    'pattern': 'A tag should be between 2 and 10 alphanumeric characters long.'
+  }
+};
diff --git a/client/src/app/shared/forms/index.ts b/client/src/app/shared/forms/index.ts
new file mode 100644 (file)
index 0000000..588ebb4
--- /dev/null
@@ -0,0 +1,2 @@
+export * from './form-validators';
+export * from './form-reactive';
index c362a0e4ae00690a933f1f9362ae69d694144e21..af34b4b64da57cad4e724a699e887ef7c7257d1b 100644 (file)
@@ -1,5 +1,5 @@
 export * from './auth';
-export * from './form-validators';
+export * from './forms';
 export * from './rest';
 export * from './search';
 export * from './users';
index 76bb61f7db63dedeb44121feef8fff7c90f6ff65..64320cae7dc5885ef7a9831ba34a54c2beed2ace 100644 (file)
@@ -2,31 +2,31 @@
 
 <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
 
-<form novalidate (ngSubmit)="upload()" [formGroup]="videoForm">
+<form novalidate (ngSubmit)="upload()" [formGroup]="form">
   <div class="form-group">
     <label for="name">Name</label>
     <input
-      type="text" class="form-control" name="name" id="name"
-      [(ngModel)]="video.name"
+      type="text" class="form-control" id="name"
+      formControlName="name"
     >
-    <div [hidden]="videoForm.controls.name.valid || videoForm.controls.name.pristine" class="alert alert-warning">
-      A name is required and should be between 3 and 50 characters long
+    <div *ngIf="formErrors.name" class="alert alert-danger">
+      {{ formErrors.name }}
     </div>
   </div>
 
   <div class="form-group">
     <label for="tags">Tags</label>
     <input
-      type="text" class="form-control" name="tags" id="tags"
-      [disabled]="isTagsInputDisabled" (keyup)="onTagKeyPress($event)" [(ngModel)]="currentTag"
+      type="text" class="form-control" id="currentTag"
+      formControlName="currentTag" (keyup)="onTagKeyPress($event)"
     >
-    <div [hidden]="videoForm.controls.tags.valid || videoForm.controls.tags.pristine" class="alert alert-warning">
-      A tag should be between 2 and 10 characters (alphanumeric) long
+    <div *ngIf="formErrors.currentTag" class="alert alert-danger">
+      {{ formErrors.currentTag }}
     </div>
   </div>
 
   <div class="tags">
-    <div class="label label-primary tag" *ngFor="let tag of video.tags">
+    <div class="label label-primary tag" *ngFor="let tag of tags">
       {{ tag }}
       <span class="remove" (click)="removeTag(tag)">x</span>
     </div>
   <div class="form-group">
     <label for="description">Description</label>
     <textarea
-      name="description" id="description" class="form-control" placeholder="Description..."
-      [(ngModel)]="video.description"
+      id="description" class="form-control" placeholder="Description..."
+      formControlName="description"
     >
     </textarea>
-    <div [hidden]="videoForm.controls.description.valid || videoForm.controls.description.pristine" class="alert alert-warning">
-        A description is required and should be between 3 and 250 characters long
+    <div *ngIf="formErrors.description" class="alert alert-danger">
+      {{ formErrors.description }}
     </div>
   </div>
 
@@ -69,7 +69,7 @@
   <div class="form-group">
     <input
       type="submit" value="Upload" class="btn btn-default form-control" [title]="getInvalidFieldsTitle()"
-      [disabled]="!videoForm.valid || video.tags.length === 0 || filename === null"
+      [disabled]="!form.valid || tags.length === 0 || filename === null"
     >
   </div>
 </form>
index f0695d76890320b9500f862f13a239f3d70ecf1b..16a8409be1da6fb55096dea3c5a9c23e6e610204 100644 (file)
@@ -1,10 +1,10 @@
 import { Component, ElementRef, OnInit } from '@angular/core';
-import { FormControl, FormGroup, Validators } from '@angular/forms';
+import { FormBuilder, FormGroup } from '@angular/forms';
 import { Router } from '@angular/router';
 
 import { FileUploader } from 'ng2-file-upload/ng2-file-upload';
 
-import { AuthService } from '../../shared';
+import { AuthService, FormReactive, VIDEO_NAME, VIDEO_DESCRIPTION, VIDEO_TAGS } from '../../shared';
 
 @Component({
   selector: 'my-videos-add',
@@ -12,22 +12,31 @@ import { AuthService } from '../../shared';
   template: require('./video-add.component.html')
 })
 
-export class VideoAddComponent implements OnInit {
-  currentTag: string; // Tag the user is writing in the input
-  error: string = null;
-  videoForm: FormGroup;
+export class VideoAddComponent extends FormReactive implements OnInit {
+  tags: string[] = [];
   uploader: FileUploader;
-  video = {
+
+  error: string = null;
+  form: FormGroup;
+  formErrors = {
     name: '',
-    tags: [],
-    description: ''
+    description: '',
+    currentTag: ''
+  };
+  validationMessages = {
+    name: VIDEO_NAME.MESSAGES,
+    description: VIDEO_DESCRIPTION.MESSAGES,
+    currentTag: VIDEO_TAGS.MESSAGES
   };
 
   constructor(
     private authService: AuthService,
     private elementRef: ElementRef,
+    private formBuilder: FormBuilder,
     private router: Router
-  ) {}
+  ) {
+    super();
+  }
 
   get filename() {
     if (this.uploader.queue.length === 0) {
@@ -37,20 +46,26 @@ export class VideoAddComponent implements OnInit {
     return this.uploader.queue[0].file.name;
   }
 
-  get isTagsInputDisabled () {
-    return this.video.tags.length >= 3;
+  buildForm() {
+    this.form = this.formBuilder.group({
+      name: [ '', VIDEO_NAME.VALIDATORS ],
+      description: [ '', VIDEO_DESCRIPTION.VALIDATORS ],
+      currentTag: [ '', VIDEO_TAGS.VALIDATORS ]
+    });
+
+    this.form.valueChanges.subscribe(data => this.onValueChanged(data));
   }
 
   getInvalidFieldsTitle() {
     let title = '';
-    const nameControl = this.videoForm.controls['name'];
-    const descriptionControl = this.videoForm.controls['description'];
+    const nameControl = this.form.controls['name'];
+    const descriptionControl = this.form.controls['description'];
 
     if (!nameControl.valid) {
       title += 'A name is required\n';
     }
 
-    if (this.video.tags.length === 0) {
+    if (this.tags.length === 0) {
       title += 'At least one tag is required\n';
     }
 
@@ -66,13 +81,6 @@ export class VideoAddComponent implements OnInit {
   }
 
   ngOnInit() {
-    this.videoForm = new FormGroup({
-      name: new FormControl('', [ <any>Validators.required, <any>Validators.minLength(3), <any>Validators.maxLength(50) ]),
-      description: new FormControl('', [ <any>Validators.required, <any>Validators.minLength(3), <any>Validators.maxLength(250) ]),
-      tags: new FormControl('', <any>Validators.pattern('^[a-zA-Z0-9]{2,10}$'))
-    });
-
-
     this.uploader = new FileUploader({
       authToken: this.authService.getRequestHeaderValue(),
       queueLimit: 1,
@@ -81,26 +89,37 @@ export class VideoAddComponent implements OnInit {
     });
 
     this.uploader.onBuildItemForm = (item, form) => {
-      form.append('name', this.video.name);
-      form.append('description', this.video.description);
+      const name = this.form.value['name'];
+      const description = this.form.value['description'];
+
+      form.append('name', name);
+      form.append('description', description);
 
-      for (let i = 0; i < this.video.tags.length; i++) {
-        form.append(`tags[${i}]`, this.video.tags[i]);
+      for (let i = 0; i < this.tags.length; i++) {
+        form.append(`tags[${i}]`, this.tags[i]);
       }
     };
+
+    this.buildForm();
   }
 
   onTagKeyPress(event: KeyboardEvent) {
+    const currentTag = this.form.value['currentTag'];
+
     // Enter press
     if (event.keyCode === 13) {
       // Check if the tag is valid and does not already exist
       if (
-        this.currentTag !== '' &&
-        this.videoForm.controls['tags'].valid &&
-        this.video.tags.indexOf(this.currentTag) === -1
+        currentTag !== '' &&
+        this.form.controls['currentTag'].valid &&
+        this.tags.indexOf(currentTag) === -1
       ) {
-        this.video.tags.push(this.currentTag);
-        this.currentTag = '';
+        this.tags.push(currentTag);
+        this.form.patchValue({ currentTag: '' });
+
+        if (this.tags.length >= 3) {
+          this.form.get('currentTag').disable();
+        }
       }
     }
   }
@@ -110,7 +129,7 @@ export class VideoAddComponent implements OnInit {
   }
 
   removeTag(tag: string) {
-    this.video.tags.splice(this.video.tags.indexOf(tag), 1);
+    this.tags.splice(this.tags.indexOf(tag), 1);
   }
 
   upload() {