markdown 使用Bootstrap样式验证角度反应形式
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了markdown 使用Bootstrap样式验证角度反应形式相关的知识,希望对你有一定的参考价值。
# Validation Angular Reactive Form with Bootstrap styling
[SOURCE](http://jasonwatmore.com/post/2018/05/10/angular-6-reactive-forms-validation-example), [SOURCE](https://github.com/angular/vscode-ng-language-service/issues/126)
This works for `Angular 6.0.3` and `Bootstrap 4.1.1`
This form validate on submit rather than as soon as each field is changed, this is implemented with a `submitted` field in the app component that is set to true when the form is submitted for the first time.
Styling of the example is all done with Bootstrap 4 CSS.
The component:
```typescript
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app',
templateUrl: 'app.component.html'
})
export class AppComponent implements OnInit {
registerForm: FormGroup;
submitted = false;
constructor(private formBuilder: FormBuilder) { }
ngOnInit() {
this.registerForm = this.formBuilder.group({
firstName: ['', Validators.required],
lastName: ['', Validators.required],
email: ['', [Validators.required, Validators.email]],
password: ['', [Validators.required, Validators.minLength(6)]]
});
}
// convenience getter for easy access to form fields
// you can access the email field in the template using f.email instead of registerForm.controls.email
get f() { return this.registerForm.controls; }
onSubmit() {
this.submitted = true;
// stop here if form is invalid
if (this.registerForm.invalid) {
return;
}
alert('SUCCESS!! :-)')
}
}
```
The form binds the form submit event to the `onSubmit()` handler in the app component using the Angular event binding `(ngSubmit)="onSubmit()"`. Validation messages are displayed only after the user attempts to submit the form for the first time, this is controlled with the submitted property of the app component.
The template:
```html
<form [formGroup]="registerForm" (ngSubmit)="onSubmit()">
<div class="form-group">
<label>First Name</label>
<input type="text" formControlName="firstName" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.firstName.errors }" />
<div *ngIf="submitted && f.firstName.errors" class="invalid-feedback">
<div *ngIf="f.firstName.errors.required">First Name is required</div>
</div>
</div>
<div class="form-group">
<label>Last Name</label>
<input type="text" formControlName="lastName" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.lastName.errors }" />
<div *ngIf="submitted && f.lastName.errors" class="invalid-feedback">
<div *ngIf="f.lastName.errors.required">Last Name is required</div>
</div>
</div>
<div class="form-group">
<label>Email</label>
<input type="text" formControlName="email" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.email.errors }" />
<div *ngIf="submitted && f.email.errors" class="invalid-feedback">
<div *ngIf="f.email.errors.required">Email is required</div>
<div *ngIf="f.email.errors.email">Email must be a valid email address</div>
</div>
</div>
<div class="form-group">
<label>Password</label>
<input type="password" formControlName="password" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.password.errors }" />
<div *ngIf="submitted && f.password.errors" class="invalid-feedback">
<div *ngIf="f.password.errors.required">Password is required</div>
<div *ngIf="f.password.errors.minlength">Password must be at least 6 characters</div>
</div>
</div>
<div class="form-group">
<button [disabled]="loading" class="btn btn-primary">Register</button>
</div>
</form>
```
**NOTE:**
If tsLint complain about `*ngIf="submitted && f.firstName.errors"` you can bypass it by using either:
```html
*ngIf="submitted && f['firstName'].errors"
or
*ngIf="submitted && !!f.firstName.errors"
```
or just ignore it, it can run just fine. See ([SOURCE](https://github.com/angular/vscode-ng-language-service/issues/126)) for more details.
## Another Example
Using a util class to prevent repeating condition check. Also, this one will check for validation as long as the fields is touched. Notice the `.custom-select` for select is from [Bootstrap](https://getbootstrap.com/docs/4.0/components/forms/#select-menu).
The component:
```typescript
@Component({
selector: 'app-checkout',
templateUrl: './checkout.component.html',
styleUrls: ['./checkout.component.scss']
})
export class CheckoutComponent implements OnInit {
private readonly PHONE_NUMBER_REGEX = '^[+]?[0-9]{8,20}$'; // https://stackoverflow.com/a/6358825/1602807
private readonly EMAIL_REGEX = '[a-zA-Z0-9.-_]{1,}@[a-zA-Z.-]{2,}[.]{1}[a-zA-Z]{2,}'; // https://stackoverflow.com/a/46440357/1602807
form: FormGroup;
readonly address = 'address';
readonly district = 'district';
readonly city = 'city';
readonly name = 'name';
readonly email = 'email';
readonly phoneNumber = 'phoneNumber';
private readonly phoneNumberValidators = Validators.compose([
Validators.required,
Validators.pattern(this.PHONE_NUMBER_REGEX)
]);
private readonly emailValidators = Validators.compose([
Validators.required,
Validators.pattern(this.EMAIL_REGEX)
]);
constructor(formBuilder: FormBuilder) {
this.createForm(formBuilder);
}
private createForm(formBuilder: FormBuilder) {
this.form = formBuilder.group({
[this.address]: ['', Validators.required],
[this.district]: ['', Validators.required],
[this.city]: [''],
[this.name]: ['', Validators.required],
[this.email]: ['', this.emailValidators],
[this.phoneNumber]: ['', this.phoneNumberValidators],
});
}
private restoreFormIfPossible() {
const info = this.checkoutInfoDao.get();
if (info) {
this.form.get(this.address).setValue(info.address);
this.form.get(this.district).setValue(info.district.value.toString());
//this.form.get(this.city).setValue(info.city.value);
this.form.get(this.name).setValue(info.name);
this.form.get(this.email).setValue(info.email);
this.form.get(this.phoneNumber).setValue(info.phoneNumber);
}
}
isFieldInvalid(field: string) {
return FormUtil.isFieldInvalid(this.form, field);
}
addInvalidCSSClass(field: string) {
return FormUtil.addInvalidCSSClass(this.form, field);
}
addInvalidCSSClassForSelect(field: string) {
return FormUtil.addInvalidCSSClassForSelect(this.form, field);
}
isFieldRequired(field: string) {
return FormUtil.isFieldRequired(this.form, field);
}
isFieldEmpty(field: string) {
return FormUtil.isFieldEmpty(this.form, field);
}
isSelectedOptionInvalid(field: string) {
return FormUtil.isSelectedOptionInvalid(this.form, field);
}
submit() {
// stop here if form is invalid
if (this.form.invalid) {
return;
}
alert('SUCCESS!! :-)');
}
}
```
The Util:
```typescript
export class FormUtil {
static isFieldInvalid(form: FormGroup, field: string) {
return form.get(field).invalid && form.get(field).touched;
}
static addInvalidCSSClass(form: FormGroup, field: string) {
return {
'is-invalid': this.isFieldInvalid(form, field),
};
}
static isSelectedOptionInvalid(form: FormGroup, selectName: string) {
const value = Number(form.get(selectName).value);
return (value === 0 || isNaN(value)) && form.get(selectName).touched;
}
static addInvalidCSSClassForSelect(form: FormGroup, selectName: string) {
return {
'is-invalid': this.isSelectedOptionInvalid(form, selectName),
};
}
static isFieldRequired(form: FormGroup, field: string) {
return form.get(field).errors.required;
}
static isFieldEmpty(form: FormGroup, field: string) {
return !form.get(field).value;
}
}
```
The template:
```html
<!--Delivery Address Section-->
<hr class="separator">
<span class="section-title-number">01</span><span class="section-title">{{ 'check_out.delivery_title' | translate }}</span>
<form class="section-form" [formGroup]="form" (ngSubmit)="onSubmit()">
<div class="form-group">
<input type="text" class="form-control"
[autofocus] //custom directive to auto focus on this field
[placeholder]="'check_out.address' | translate"
[formControlName]="address"
[ngClass]="addInvalidCSSClass(address)">
<div *ngIf="isFieldInvalid(address)" class="invalid-feedback">
<div *ngIf="isFieldRequired(address)">{{ 'check_out.error_field_address' | translate }}</div>
</div>
</div>
<div class="form-row">
<div class="col">
<select class="custom-select custom-select-lg"
[formControlName]="district"
[ngClass]="addInvalidCSSClassForSelect(district)"
[class.semi-transparent]="isFieldEmpty(district)">
<option hidden>{{ 'check_out.district' | translate }}</option>
<option *ngFor="let dist of districtList" [value]="dist.value">{{ dist.text | translate }}</option>
</select>
<div *ngIf="isSelectedOptionInvalid(district)" class="invalid-feedback">
<div *ngIf="isSelectedOptionInvalid(district)">{{ 'check_out.error_field_district' | translate }}</div>
</div>
</div>
<div class="col">
<select class="custom-select custom-select-lg"
[formControlName]="city">
<option selected>{{ selectedCity.text | translate }}</option>
</select>
</div>
</div>
</form>
<!--End Delivery Address Section-->
<!--Contact Information Section-->
<hr class="separator">
<span class="section-title-number">03</span><span class="section-title">{{ 'check_out.contact_info_title' | translate }}</span>
<form class="section-form" [formGroup]="form" (ngSubmit)="onSubmit()">
<div class="form-group">
<input type="text" class="form-control"
[placeholder]="'check_out.name' | translate"
[formControlName]="name"
[ngClass]="addInvalidCSSClass(name)">
<div *ngIf="isFieldInvalid(name)" class="invalid-feedback">
<div *ngIf="isFieldRequired(name)">{{ 'check_out.error_field_name' | translate }}</div>
</div>
</div>
<div class="form-group">
<input type="text" class="form-control"
[placeholder]="'check_out.email' | translate"
[formControlName]="email"
[ngClass]="addInvalidCSSClass(email)">
<div *ngIf="isFieldInvalid(email)" class="invalid-feedback">
<div *ngIf="isFieldRequired(email)">{{ 'check_out.error_field_email' | translate }}</div>
<div *ngIf="!isFieldEmpty(email)">{{ 'check_out.error_field_email_invalid' | translate }}</div>
</div>
</div>
<div class="form-group">
<input type="text" class="form-control"
[placeholder]="'check_out.phone_number' | translate"
[formControlName]="phoneNumber"
[ngClass]="addInvalidCSSClass(phoneNumber)">
<div *ngIf="isFieldInvalid(phoneNumber)" class="invalid-feedback">
<div *ngIf="isFieldRequired(phoneNumber)">{{ 'check_out.error_field_phone_number' | translate }}</div>
<div *ngIf="!isFieldEmpty(phoneNumber)">{{ 'check_out.error_field_phone_number_invalid' | translate }}</div>
</div>
</div>
</form>
<!--End Contact Information Section-->
<button type="submit" class="btn btn-primary" id="submit-button" (click)="submit()"
[disabled]="!form.valid">
{{ 'check_out.submit_button_text' | translate }}
</button>
```
The SCSS:
```css
@import "color";
@import "typo";
#container {
padding-top: 70px;
width: 650px;
height: auto;
display: block;
margin-left: auto;
margin-right: auto;
#title {
font-size: 36px;
}
#container-with-padding {
padding: 34px 25px;
margin-top: 14px;
background-color: $color-light-gray;
#hr-subtotal {
margin: 1px 0 21px;
}
#subtotal {
@extend .font-bold;
color: $color-text-lighter-black;
line-height: 2;
margin-bottom: -10px;
}
#total-price {
@extend .font-bold;
color: $color-text-lighter-black;
text-align: right;
margin-bottom: -10px;
}
}
.separator {
margin: 40px 0 30px;
}
.section-base-style {
@extend .font-bold;
font-size: 24px;
}
.section-title-number {
@extend .section-base-style;
@extend .text-green;
}
.section-title {
@extend .section-base-style;
@extend .text-primary-black;
padding-left: 15px;
}
.section-form {
margin-top: 30px;
}
//region Form Custom Styling
$form-font-size: 16px;
$form-font-opacity: 0.5;
.form-control {
@extend .font-bold;
color: $color-text-lighter-black;
font-size: $form-font-size;
}
.form-control::-webkit-input-placeholder {
font-family: AvenirNext-Bold;
color: $color-text-lighter-black;
opacity: $form-font-opacity;
font-size: $form-font-size;
} /* WebKit, Blink, Edge */
.form-control:-moz-placeholder {
font-family: AvenirNext-Bold;
color: $color-text-lighter-black;
opacity: $form-font-opacity;
font-size: $form-font-size;
} /* Mozilla Firefox 4 to 18 */
.form-control::-moz-placeholder {
font-family: AvenirNext-Bold;
color: $color-text-lighter-black;
opacity: $form-font-opacity;
font-size: $form-font-size;
} /* Mozilla Firefox 19+ */
.form-control:-ms-input-placeholder {
font-family: AvenirNext-Bold;
color: $color-text-lighter-black;
opacity: $form-font-opacity;
font-size: $form-font-size;
} /* Internet Explorer 10-11 */
.form-control::-ms-input-placeholder {
font-family: AvenirNext-Bold;
color: $color-text-lighter-black;
opacity: $form-font-opacity;
font-size: $form-font-size;
} /* Microsoft Edge */
.is-invalid {
color: #E53A40;
}
//endregion Form Custom Styling
#payment-type-container{
margin-top: 19px;
}
.custom-select {
@extend .font-bold;
color: $color-text-lighter-black;
font-size: $form-font-size;
border-radius: 10px;
}
.semi-transparent {
opacity: $form-font-opacity;
}
#submit-button {
width: 210px;
height: 64px;
@extend .font-bold;
font-size: 20px;
margin-top: 40px;
margin-bottom: 138px;
}
}
```
以上是关于markdown 使用Bootstrap样式验证角度反应形式的主要内容,如果未能解决你的问题,请参考以下文章
协调 Angular.js 和 Bootstrap 表单验证样式
Bootstrap Blazor 实战 Markdown 编辑器使用
markdown [1000hz-bootstrap-validator]用于抛光验证的有用代码:NIP,PESEL,REGON,IDENTITY CARD NUMBER
FormValidation / formvalidation.io Bootstrap 插件在样式更改时放错了验证图标