Aurelia Validation abstracted to Model - javascript

I have followed Scott Allen's Aurelia Pluralsight course which makes use of the Aurelia-Validation plugin.
This results in a view-model edit.js:
import {inject} from "aurelia-framework";
import {MovieService} from "./movieService";
import {Router} from "aurelia-router";
import {Validation} from "aurelia-validation";
#inject(MovieService, Router, Validation)
export class Edit {
constructor(movieService, router, validation) {
this.service = movieService;
this.router = router;
this.validation = validation.on(this)
.ensure("movie.title").isNotEmpty().hasLengthBetween(3, 50)
.ensure("movie.releaseYear").isNotEmpty().isNumber().isBetween(1900, 2100)
.ensure("movie.starRating").isNotEmpty().isNumber().isBetween(0, 5);
}
activate(params) {
this.service.getById(params.id)
.then(movie => {
this.movie = movie;
});
}
save() {
this.validation.validate()
.then(() => {
this.service.save(this.movie)
.then(movie => {
let url = this.router.generate("home");
this.router.navigate(url);
});
})
.catch(() => {
});
}
}
And a view edit.html
<form class="form-horizontal"
validate.bind="validation"
submit.trigger="save()">
<div class="form-group">
<label class="col-sm-2" for="title">Title</label>
<div class="col-sm-10">
<input type="text" id="title" placeholder="Title" value.bind="movie.title" class="form-control"/>
</div>
</div>
<div class="form-group">
<label class="col-sm-2" for="rating">Rating</label>
<div class="col-sm-10">
<input type="text" id="rating" placeholder="Rating" value.bind="movie.starRating" class="form-control" />
</div>
</div>
<div class="form-group">
<label class="col-sm-2" for="releaseYear">Release Year</label>
<div class="col-sm-10">
<input type="text" id="releaseYear" placeholder="Release Year" value.bind="movie.releaseYear" class="form-control" />
</div>
</div>
<div class="pull-right">
<a route-href="route:home" class="btn btn-default" role="button">Cancel</a>
<input type="submit" class="btn btn-primary" value="Save" />
</div>
</form>
This works as expected with the validation preventing invalid data from being submitted.
What I would like to do next is to abstract the validation rules out from the view model and into a model class. I have created a model movieModel.js
import {inject} from "aurelia-framework";
import {Validation} from 'aurelia-validation';
import {ensure} from 'aurelia-validation';
#inject(Validation)
export class MovieModel {
#ensure(function(it){ it.isNotEmpty().hasLengthBetween(3, 50) })
title = "";
#ensure(function(it){ it.isNotEmpty().isNumber().isBetween(1900, 2100) })
releaseYear = "";
#ensure(function(it){ it.isNotEmpty().isNumber().isBetween(0, 5) })
starRating = "";
constructor(validation) {
this.title = "";
this.releaseYear = "";
this.starRating = "";
}
}
And made use of it in the view model like this:
import {inject} from "aurelia-framework";
import {MovieService} from "./movieService";
import {Router} from "aurelia-router";
import {Validation} from "aurelia-validation";
import {MovieModel} from "./movieModel";
#inject(MovieService, Router, Validation, MovieModel)
export class Edit {
constructor(movieService, router, validation, movieModel) {
this.service = movieService;
this.router = router;
this.movie = movieModel;
this.validation = validation.on(this.movie);
}
activate(params) {
this.service.getById(params.id)
.then(movie => {
this.movie = movie;
});
}
save() {
this.validation.validate()
.then(() => {
this.service.save(this.movie)
.then(movie => {
let url = this.router.generate("home");
this.router.navigate(url);
});
})
.catch(() => {
});
}
}
Changes to the view edit.html are just adding the validate attribute to the input fields e.g.
<input type="text" id="title" placeholder="Title" value.bind="movie.title" class="form-control"/>
becomes
<input type="text" id="title" placeholder="Title" value.bind="movie.title" validate="title" class="form-control"/>
However, when I edit a movie retrieved from the service via the activate() function and then attempt to save it, the validation flags all the properties as "is required".
I assume the statement this.validation = validation.on(this.movie) in the constructor of the view model is tying the validation to a non-populated instance of the model but I have no idea how to overcome this.

you need to create validation every time your model is changed
activate(params) {
this.service.getById(params.id)
.then(movie => {
this.movie = movie;
this.validation = this.Validation.on(this.movie);
});
}
UPDATE: and in constructor
this.Validation = validation;

Related

Property 'title' does not exist on type '{}'

Angular
In the first few tags I have an input tag input #title = "ngModel" [(ngModel)]="product.title" this code where I am trying to use two way binding to be able to edit my form and values that I get from the firebase database. I created an empty product object in product-form.component.ts but I get the property type error. I am not sure why the empty product object causing error because the tutorial I'm watching has the same approach. Goal is to be able to use that empty product object or an alternate approach to be able to work with two way binding
product-form.component.ts
import { ProductService } from './../../product.service';
import { CategoryService } from './../../category.service';
import { Component, OnInit } from '#angular/core';
import { Router, ActivatedRoute } from '#angular/router';
import { take } from 'rxjs/operators';
#Component({
selector: 'app-product-form',
templateUrl: './product-form.component.html',
styleUrls: ['./product-form.component.css']
})
export class ProductFormComponent implements OnInit {
categories$;
product = {};
constructor(
private route: ActivatedRoute,
private router: Router,
private categoryService: CategoryService,
private productService: ProductService)
{
this.categories$ = categoryService.getCategories();
let id = this.route.snapshot.paramMap.get('id');
if (id) this.productService.get(id).pipe(take(1)).subscribe(p => this.product = p);
}
save(product){
this.productService.create(product);
this.router.navigate(['/admin/products']);
}
ngOnInit(): void {
}
}
product-form.html
<div class="row">
<div class="col-md-6">
<form #f = "ngForm" (ngSubmit) = "save(f.value)">
<div class="form-group">
<label for="title">Title</label>
<input #title = "ngModel" [(ngModel)]="product.title" name = "title" id = "title" type="text" class="form-control" required>
<div class="alert alert-danger" *ngIf = "title.touched && title.invalid">
Title is required
</div>
</div>
<div class="form-group">
<label for="price">Price</label>
<div class="input-group">
<span class="input-group-text">$</span>
<input #price="ngModel" ngModel name ="price" id = "price" type="number" class="form-control" required>
</div>
<div class="alert alert-danger" *ngIf="price.touched && price.invalid">
<div *ngIf="price.errors.required">Price is required.</div>
<div *ngIf="price.errors.min">Price should be 0.</div>
</div>
</div>
<div class="form-group">
<label for="category">Category</label>
<select #category="ngModel" ngModel name="category" id = "category" type="text" class="form-control" required>
<option value=""></option>
<option *ngFor = "let c of categories$ | async" [value] = "c.name">
{{c.name}}
</option>
</select>
<div class="alert alert-danger" *ngIf = "category.touched && category.invalid">
Category is required.
</div>
</div>
<div class="form-group">
<label for="imageUrl">Image Url</label>
<input #imageUrl="ngModel" ngModel name= "imageUrl" id = "imageUrl" type="url" class="form-control" required>
<div class="alert alert-danger" *ngIf = "imageUrl.touched && imageUrl.invalid">
<div *ngIf = "imageUrl.errors.required">Image URL is required.</div>
<div *ngIf = "imageUrl.errors.url">Invalid URL</div>
</div>
</div>
<button class="btn btn-primary">Save</button>
</form>
</div>
<div class="col-md-6">
<div class="card" style="width: 18rem;">
<img [src]="imageUrl.value" class="card-img-top">
<div class="card-body">
<h5 class="card-title">{{title.value}}</h5>
<p class="card-text">{{price.value | currency:'USD':true}}</p>
</div>
</div>
</div>
</div>
product.service.ts
import { Injectable } from '#angular/core';
import { AngularFireDatabase } from '#angular/fire/database';
#Injectable({
providedIn: 'root'
})
export class ProductService {
constructor(private db: AngularFireDatabase) { }
create(product){
return this.db.list('/products').push(product);
// const itemsRef = this.db.list('products');
// return itemsRef.push(product);
}
getAll() {
return this.db.list('/products').valueChanges();
}
get(productId){
return this.db.object('/products/' + productId).valueChanges();
}
}
There are 2 ways to do this
Approach 1
Declare an interface
export interface Product {
title: string
}
Change the Component code section as follows
from
product = {};
To
product:Product;
Approach 2 - This can have side effects
Change HTML
From
<input #title = "ngModel" [(ngModel)]="product.title" name = "title" id = "title" type="text" class="form-control" required>
To
<input #title = "ngModel" [(ngModel)]="product['title']" name = "title" id = "title" type="text" class="form-control" required>
I fixed this error by changing a couple of things in 'tsconfig.json' file.
First remove "strict":true and add instead of it "noImplicitUseStrict":true
(Don't forget adding the comma)

Angular - Redirect to Login after register and show a message

I'm working on an Angular and Spring Boot app and I'm very new to Angular. I've made some components, a component for login and one for register, also I've made some validation, etc. Now, what I want to do is when the user registration is successfully the user is redirected to login page and also I want to show a message like this: "Registration Successful! Please Login!" I know how to redirect the user to the login page but I don't know how to show this message for the user.
Register ts
import { Component, OnInit, ViewChild } from '#angular/core';
import { NgForm } from '#angular/forms';
import { User } from '../model/user.model';
import { UserDataService } from '../service/data/user-data.service';
import { Router } from '#angular/router';
#Component({
selector: 'app-register',
templateUrl: './register.component.html',
styleUrls: ['./register.component.css']
})
export class RegisterComponent implements OnInit {
invalidRegister = false;
errorMessage = '';
pass1 = '';
pass2 = '';
userName: string;
emailAddress: string;
user: User;
#ViewChild('f') signupForm: NgForm;
constructor(
private userDataService: UserDataService,
private router: Router) { }
ngOnInit() {
}
onSignup(form: NgForm) {
if (this.signupForm.valid === false) {
this.invalidRegister = true;
this.errorMessage = 'You must fill in all the fields!';
} else if (this.pass1 !== this.pass2) {
this.invalidRegister = true;
this.errorMessage = 'The passwords do not match!';
} else {
this.user = new User(this.userName, this.emailAddress, this.pass1);
console.log(this.user);
this.userDataService.addUser(this.user).subscribe(
data => {
console.log(data);
},
error => {
if (error.error.email === "duplicated") {
this.invalidRegister = true;
this.errorMessage = 'The email address you have used is already registered!';
} else if (error.error.username === "duplicated") {
this.invalidRegister = true;
this.errorMessage = 'The username is not available!';
}
},
() => {
this.invalidRegister = false;
this.router.navigate(['login']);
})
}
}
}
Register html
<h1>Register</h1>
<div class="alert alert-warning" *ngIf="invalidRegister">{{ errorMessage }}</div>
<form (ngSubmit)="onSignup()" #f="ngForm">
<div class="form-group row">
<label for="username" class="col-2 col-form-label">Username</label>
<div class="col-6">
<input
type="text"
id="username"
name="username"
ngModel
class="form-control"
required
#username="ngModel"
[(ngModel)]="userName">
<span
class="help-block text-danger"
*ngIf="!username.valid && username.touched">The username field is required!</span>
</div>
</div>
<div class="form-group row">
<label for="email" class="col-2 col-form-label">Email</label>
<div class="col-6">
<input
type="email"
id="email"
name="email"
ngModel
class="form-control"
required
email
#email="ngModel"
[(ngModel)]="emailAddress">
<span
class="help-block text-danger"
*ngIf="!email.valid && email.touched">Please enter a valid email!</span>
</div>
</div>
<div class="form-group row">
<label for="password" class="col-2 col-form-label">Password</label>
<div class="col-6">
<input
type="password"
id="password"
name="password"
ngModel
class="form-control"
required
#password="ngModel"
[(ngModel)]="pass1">
<span
class="help-block text-danger"
*ngIf="!password.valid && password.touched">The password field is required!</span>
</div>
</div>
<div class="form-group row">
<label for="pass" class="col-2 col-form-label">Confirm Password</label>
<div class="col-6">
<input
type="password"
id="pass"
name="pass"
ngModel
class="form-control"
required
#pass="ngModel"
[(ngModel)]="pass2">
<span
class="help-block text-danger"
*ngIf="!pass.valid && pass.touched">Please confirm your password!</span>
</div>
</div>
<button class="btn btn-primary" type="submit">Sign Up</button>
</form>
Login ts
import { Component, OnInit } from '#angular/core';
import { Router } from '#angular/router';
import { BasicAuthenticationService } from '../service/basic-authentication.service';
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
username = '';
password = '';
errorMessage = 'Invalid Credentials';
invalidLogin = false;
constructor(
private router: Router,
private basicAuthenticationService: BasicAuthenticationService) { }
ngOnInit() {
}
handleBasicAuthLogin() {
this.basicAuthenticationService.executeAuthenticationService(this.username, this.password)
.subscribe(
data => {
console.log(data);
this.router.navigate(['welcome', this.username]);
this.invalidLogin = false;
},
error => {
console.log(error);
this.invalidLogin = true;
}
);
}
Login html
<h1>Login</h1>
<div class="container">
<div class="alert alert-warning" *ngIf='invalidLogin'>{{ errorMessage }}</div>
<div>
User Name: <input type="text" name="username" [(ngModel)]="username" >
Password: <input type="password" name="password" [(ngModel)]="password">
<button (click)="handleBasicAuthLogin()" class="btn btn-success">Login</button>
</div>
</div>
When the registration is successful, you can add query params to the route and navigate to login
this.router.navigate(['login'], {queryParams: { registered: 'true' } });
Url will look like this: https://foobar.com/login?registered=true
In login.ts
infoMessage = '';
ngOnInit() {
this.route.queryParams
.subscribe(params => {
if(params.registered !== undefined && params.registered === 'true') {
infoMessage = 'Registration Successful! Please Login!';
}
});
}
And add this kind of line in login.html
<span *ngIf="infoMessage.length > 0">{{ infoMessage }}</span>

React Submit Form Not Sending POST Request

I am trying to create a frontend for a Spring Boot application and I chose React but I do not have much experience with React or JavaScript.
So I have a form that I am trying to use to send a post request but when I press the submit button, nothing seems to happen. I assume its my onSubmit handler that's the problem but I don't know what is wrong with it. When I send the POST request manually it works fine so I don't think it's the REST API thats causing an issue.
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import axios from 'axios';
import { Link } from 'react-router-dom';
class Create extends Component {
constructor() {
super();
this.state = {
name: '',
email: '',
title: '',
description: ''
};
}
onChange = (e) => {
const state = this.state
state[e.target.name] = e.target.value;
this.setState(state);
}
onSubmit = (e) => {
e.preventDefault();
const { name, email, title, description } = this.state;
axios.post('/create', { name, email, title, description })
.then((result) => {
this.props.history.push("/")
});
}
render() {
const { name, email, title, description } = this.state;
return (
<div class="container">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
Send Message
</h3>
</div>
<div class="panel-body">
<h4><Link to="/"><span class="glyphicon glyphicon-th-list" aria-hidden="true"></span> Message List</Link></h4>
<form onSubmit={this.onSubmit}>
<div class="form-group">
<label for="name">Name:</label>
<input type="text" class="form-control" name="name" onChange={this.onChange}/>
</div>
<div class="form-group">
<label for="email">Email:</label>
<input type="text" class="form-control" name="email" onChange={this.onChange}/>
</div>
<div class="form-group">
<label for="title">Title:</label>
<input type="text" class="form-control" name="title" onChange={this.onChange}/>
</div>
<div class="form-group">
<label for="description">Description:</label>
<input type="text" class="form-control" name="description" onChange={this.onChange}/>
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
</div>
</div>
</div>
);
}
}
export default Create;
The first rule of React is, You do not update state property directly (or React wouldn't know that the state has changed).
It's not easy to tell how React would behave if you do so.
So instead of setting the state value directly,
onChange = (e) => {
const state = this.state
state[e.target.name] = e.target.value;
this.setState(state);
}
Change it like this and try to submit again.
onChange = (e) => {
const state = this.state
this.setState({[e.target.name]: e.target.value});
}

When using template driven forms in Angular 2, how to access the form in the component?

I have a simple template driven form like so:
HTML:
<div class="container">
<h1>Hero Form</h1>
<form (ngSubmit)="onSubmit()" #heroForm="ngForm">
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" id="name" [(ngModel)]="model.name" name="name" #name="ngModel">
</div>
<div class="form-group">
<label for="alterEgo">Alter Ego</label>
<input type="text" class="form-control" id="alterEgo" [(ngModel)]="model.alterEgo" name="alterEgo">
</div>
<div class="form-group">
<label for="power">Hero Power</label>
<select class="form-control" id="power" [(ngModel)]="model.power" name="power">
<option *ngFor="let pow of powers" [value]="pow">{{pow}}</option>
</select>
</div>
<button type="submit" class="btn btn-success" [disabled]="!heroForm.form.valid">Submit</button>
</form>
</div>
Component:
import { Component, OnInit } from '#angular/core';
import { Hero } from './hero';
#Component({
selector: 'at-hero',
templateUrl: './hero.component.html',
styleUrls: ['./hero.component.scss']
})
export class HeroComponent implements OnInit {
constructor() {
//
}
ngOnInit() {
//
}
powers = ['Really Smart', 'Super Flexible', 'Super Hot', 'Weather Changer'];
model = new Hero(18, 'Dr IQ', this.powers[0], 'Chuck Overstreet');
submitted = false;
onSubmit() { this.submitted = true; }
newHero() {
this.model = new Hero(42, '', '');
}
}
How can I:
Reset the whole form from the component (not from the markup)?
Reset a single form field (e.g. the name field) also from the component and not the markup?
You can get the form by using ViewChild
Markup
<form (ngSubmit)="onSubmit()" #heroForm="ngForm">
...
</form>
Component
#ViewChild('heroForm') public heroForm: NgForm;
I suggest you also to look at Reactive Forms too. I think this will be more handy if you want to work with form in the typescript, not in the markup

how to get value from html form and pass is to typescript in angular

Do anyone know how can I get value from HTML forms in typescript?
this is how my html page: login.component.html
<form [formGroup]="loginForm" id="loginForm" (ngSubmit)="authUser()" #userForm="ngForm">
<div class="container">
<div class="row">
<div class="col-sm-6 col-sm-offset-3">
<p><input type="email" formControlName="email" id="email" placeholder="Your email" [(ngModel)]="user.email" required></p>
</div>
</div>
<div class="row">
<div class="col-sm-6 col-sm-offset-3">
<p><input type="password" formControlName="password" id="password" placeholder="Your password" [(ngModel)]="user.password" required></p>
</div>
</div>
<div class="row">
<div class="col-sm-6 col-sm-offset-3">
<p><button class="btn btn-primary" type="submit" [disabled]="!bookForm.form.valid">Login</button> </p>
</div>
</div>
</div>
</form>
<div *ngIf="edited" class="alert alert-success box-msg" role="alert">
<strong>Successful Login!</strong>
</div>
and this is my typescript: login.component.ts
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup } from '#angular/forms';
import { Router } from '#angular/router';
import { Http } from '#angular/http';
import { PostsService } from '../posts.service';
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
loginForm: FormGroup;
login = false;
users: any;
user = {};
constructor(private fb: FormBuilder, private http: Http, private router: Router, private postsService: PostsService) {
this.postsService.getAllPosts().subscribe(users => {
this.users = users;
});
}
ngOnInit() {
this.loginForm = this.fb.group({
email: 'a#b.com',
password: 'abc123.'
});
}
authUser() {
var i:number;
var email:string = this.loginForm.value.email;
var password:string = this.loginForm.value.password;
var userType:string;
var userEmail:string;
for(i=0;i<this.users.length;i++){
if(this.users[i].email == email && this.users[i].password == password){
this.login = true
userEmail = this.users[i].email;
userType = this.users[i].accountType;
}
}
if(this.login){
console.log("Sucessful");
}
else{
console.log("Unsucessful");
}
}
}
I have tried a lot of methods but none of them seems to work for now my login the way my code words is that once the button is press it will not check whats in the html forms. but it will check againce var email:string = this.loginForm.value.email; and my database for the email and the value thats in var email:string = this.loginForm.value.email; have been hardcoded.
You can try to get value from your ngModel and use .find function to filter your data it's easy and fast to get user details
public userType: string;
public userEmail:string;
public loginUser:any;
authUser() {
this.loginUser = this.users.find(x => x.email == this.user.email && x.password == this.user.password);
if(this.loginUser){
this.login = true
this.userEmail = this.loginUser.email;
this.userType = this.loginUser.accountType;
}
}
You need the prevent default behavior of form. Use following code in your authUser method:
authUser(event) {
event.preventDefault();
//rest of code
}
and update your form tag:
<form [formGroup]="loginForm" id="loginForm" (ngSubmit)="authUser($event)" #userForm="ngForm">
1.- don't mix Reactive form and Template forms.
//with ReactiveForm
<input type="password" formControlName="password" id="password" placeholder="Your password" >
//with template Drive Form
<input type="password" id="password" placeholder="Your password" [(ngModel)]="user.password" required>
2.- with Template driven Form you must have
this.user:any={
email: 'a#b.com',
password: 'abc123.'
}
//And in the submit function you have this.user.email and this.user.password
3.- with Reactive Form
//the validators it's included when create the fbgroup
this.loginForm = this.fb.group({
email: ['a#b.com',Required]
password: ['abc123.',Required]
});
//in the submit pass loginForm as argument
<form [formGroup]="loginForm" id="loginForm" (ngSubmit)="authUser(loginForm)">
//then in your authUser
authUser(form:any){
if (form.valid)
{
console.log(form.value.email,form.value.password)
}
}
4.-You're checking email and password compared with a list of users, but you really have not a list of users. if really post.Service.getAllPost get a list of users the method have a "very bad" name. Ah! put the subscribe in a ngOnInit, not in de constructor
ngOnInit()
{
this.postsService.getAllPosts().subscribe(users => {
this.users = users;
});
....
}

Categories

Resources