我正在尝试将 ng-bootstrap(Angular 4 和 Bootstrap 4)日期选择器封装在自定义组件中,并添加反应式表单验证。
我想知道我是否使用了正确的方法,所以我会向您展示我的代码。
这是自定义组件:
@Component({
selector: 'ngb-date-time-picker',
template: `
<div class="form-inline" [hidden]="timeOnly">
<div class="form-group">
<div class="input-group">
<input
readonly
class="form-control form-control-danger"
[placeholder]="placeholder"
name="date"
[(ngModel)]="dateStruct"
(ngModelChange)="updateDate()"
ngbDatepicker
#datePicker="ngbDatepicker">
<div class="input-group-addon btn-toggle-date-picker" (click)="datePicker.toggle()" >
<i class="material-icons md-18">date_range</i>
</div>
</div>
</div>
</div>
<ngb-timepicker [(ngModel)]="timeStruct" (ngModelChange)="updateTime()" [meridian]="true" [hidden]="dateOnly"></ngb-timepicker>
`,
styles: [`
.form-group {
width: 100%;
}
.btn-toggle-date-picker {
cursor: pointer;
}
.btn-toggle-date-picker:hover {
background-color: #ccc;
}
`],
providers: [
NgbDatepickerConfig,
I18n,
{ provide: NgbDatepickerI18n, useClass: NgbMultiLanguageDatepickerI18n },
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NgbDateTimePickerComponent), multi: true },
{ provide: NG_VALIDATORS, useExisting: forwardRef(() => NgbDateTimePickerComponent), multi: true }
]
})
export class NgbDateTimePickerComponent implements OnInit, OnChanges, ControlValueAccessor {
@Input() placeholder: string;
@Input() isRequired: boolean = true;
protected onChange: any = () => { };
protected onTouched: any = () => { };
private _date: Date = null;
//get accessor
@Input()
get date(): Date {
return this._date;
};
//set accessor
set date(val: Date) {
this._date = val;
this.onChange(val);
}
@Output() dateChange: EventEmitter<Date> = new EventEmitter<Date>();
@Input() minDate: Date = null;
@Input() dateOnly: boolean = false;
@Input() timeOnly: boolean = false;
@Input() startOfDay: boolean = false;
@Input() endOfDay: boolean = false;
dateStruct: NgbDateStruct;
timeStruct: NgbTimeStruct;
constructor(private config: NgbDatepickerConfig) { }
ngOnInit(): void {
if (this.minDate != null) {
this.config.minDate = { year: getYear(this.minDate), month: getMonth(this.minDate) + 1, day: getDate(this.minDate) };
}
}
ngOnChanges(changes: SimpleChanges): void {
if (changes.date && changes.date.currentValue !== changes.date.previousValue && changes.date.currentValue != null) {
this.dateStruct = {
day: getDate(this.date),
month: getMonth(this.date) + 1,
year: getYear(this.date)
};
this.timeStruct = {
second: getSeconds(this.date),
minute: getMinutes(this.date),
hour: getHours(this.date),
};
} else if (changes.date && changes.date.currentValue == null) {
this.dateStruct = null;
this.timeStruct = null;
}
}
updateDate(): void {
if (this._date == null) {
this._date = new Date();
}
const newDate: Date = setYear(setMonth(setDate(this._date, this.dateStruct.day), this.dateStruct.month - 1), this.dateStruct.year);
if (this.startOfDay) {
setHours(setMinutes(setSeconds(setMilliseconds(newDate, 0), 0), 0), 0);
newDate.setHours(0, 0, 0, 0);
} else if (this.endOfDay) {
newDate.setHours(23, 59, 59, 999);
} else {
let now: Date = new Date();
newDate.setHours(getHours(now), getMinutes(now), getSeconds(now), getMilliseconds(now));
}
this.dateChange.next(newDate);
}
updateTime(): void {
const newDate: Date = setHours(setMinutes(setSeconds(this._date, this.timeStruct.second), this.timeStruct.minute), this.timeStruct.hour);
this.dateChange.next(newDate);
}
public writeValue(value: any): void {
if (value) {
if (value === "") value = null;
this.date = value;
}
}
public registerOnChange(fn: (_: any) => {}): void { this.onChange = fn; }
public registerOnTouched(fn: () => {}): void { this.onTouched = fn; }
validate(c: FormControl) {
if (this.isRequired) {
let res = Validators.required(c)
return res;
}
return null;
}
}
这是我使用自定义组件的组件:
@Component({
moduleId: module.id.toString(),
template: require('./user-add-edit.component.html'),
styles: [require('./user-add-edit.component.scss')],
// make slide in/out animation available to this component
animations: [slideInOutAnimation],
// attach the slide in/out animation to the host (root) element of this component
host: { '[@slideInOutAnimation]': '' }
})
export class UserAddEditComponent implements OnInit, CanComponentDeactivate {
title: string = 'Aggiungi Abbonato';
isBusy: Subscription;
isEditMode: boolean = false;
isCancelling: boolean = false;
user: IUser = <IUser>{};
model: IPutUser = <IPutUser>{ };
addEditForm: FormGroup;
uploader: FileUploader = new FileUploader({ autoUpload: true });
uploading: boolean = false;
minDate: Date = new Date();
isCertificateChanged: boolean = false;
// Workaround for https://github.com/valor-software/ng2-file-upload/issues/220
@ViewChild('fileInput') fileInput: ElementRef
form: FormGroup;
counterValue = 3;
minValue = 0;
maxValue = 12;
constructor(private route: ActivatedRoute, private router: Router, private appService: AppService, private pubSubService: PubSubService,
private modalService: ModalService, private formBuilder: FormBuilder, @Inject('ORIGIN_URL') private originUrl: string,
public authService: AuthService, private toastyService: ToastyService)
{
// Uploader configuration
this.uploader.options.url = this.originUrl + '/api/certificates';
this.uploader.authToken = this.authService.getAuthToken();
this.uploader.onBeforeUploadItem = (item: FileItem) => this.uploading = true;
this.uploader.onCompleteItem = (item: FileItem, response: string, status: number, headers: ParsedResponseHeaders) => {
this.uploading = false;
var resp = JSON.parse(response);
if (resp.error) {
console.error(resp.error);
this.toastyService.error(resp.error);
}
this.model.medicalCertificateUrl = resp.url;
this.toastyService.success("Caricamento completato.");
};
this.uploader.onAfterAddingFile = (item: FileItem) => {
// Workaround for https://github.com/valor-software/ng2-file-upload/issues/220
this.fileInput.nativeElement.value = '';
//this.setMedicalCertificateExpiryRequired(true);
this.addEditForm.addControl("medicalCertificateExpiry", new FormControl(""));
};
}
public ngOnInit(): void {
let userId: string = this.route.snapshot.params['id'];
if (userId) {
this.title = 'Modifica Abbonato';
this.isEditMode = true;
this.isBusy = this.appService.getUser(userId)
.subscribe(user => {
this.user = user;
this.model = <IPutUser>{
id: this.user.id,
firstName: this.user.firstName,
lastName: this.user.lastName,
taxCode: this.user.taxCode,
userName: this.user.userName,
phoneNumber: this.user.phoneNumber,
email: this.user.email,
medicalCertificateUrl: this.user.medicalCertificateUrl,
medicalCertificateExpiry: this.user.medicalCertificateExpiry
};
if (user.medicalCertificateUrl != null) {
this.addEditForm.addControl("medicalCertificateExpiry", new FormControl(""));
this.addEditForm.controls.medicalCertificateExpiry.patchValue(user.medicalCertificateExpiry);
}
this.onValueChanged();
});
}
this.initForm();
this.form = this.formBuilder.group({
counter: this.counterValue
});
}
dateChanged(event: any) {
console.log("dateChanged", event);
}
save(): void {
let apiCall: Observable<any>;
if (this.isEditMode) {
apiCall = this.appService.putUser(this.model);
} else {
apiCall = this.appService.postUser(this.model);
}
this.isBusy = apiCall.subscribe(() => {
this.router.navigate(['registry']);
this.pubSubService.publish('registry-updated');
this.pubSubService.publish('check-alerts');
});
}
removeCertificate(showToast: boolean = true): void {
this.appService.deleteCertificate(this.model.medicalCertificateUrl).subscribe(() => {
this.model.medicalCertificateUrl = null;
this.model.medicalCertificateExpiry = null;
this.addEditForm.removeControl("medicalCertificateExpiry");
if (showToast) {
this.toastyService.success("Certificato rimosso con successo.");
}
});
}
canDeactivate(): Promise<boolean> | boolean {
if (this.isCancelling) {
let title: string = 'Conferma';
let message: string = 'Sicuro di voler annullare le modifiche?';
let isModelChanged: boolean = false;
if (this.isEditMode) {
this.isCertificateChanged = this.user.medicalCertificateUrl !== this.model.medicalCertificateUrl;
isModelChanged = this.user.firstName !== this.model.firstName
|| this.user.lastName !== this.model.lastName
|| this.user.taxCode !== this.model.taxCode
|| this.user.userName !== this.model.userName
|| this.user.phoneNumber !== this.model.phoneNumber
|| this.user.email !== this.model.email
|| this.isCertificateChanged
|| ((this.user.medicalCertificateExpiry != null && this.model.medicalCertificateExpiry != null) ? (this.user.medicalCertificateExpiry.getTime() !== this.model.medicalCertificateExpiry.getTime()) : false)
|| (this.user.medicalCertificateExpiry != null && this.model.medicalCertificateExpiry == null)
|| (this.user.medicalCertificateExpiry == null && this.model.medicalCertificateExpiry != null);
} else {
if (this.model.medicalCertificateUrl) {
this.isCertificateChanged = true;
}
if (this.model.firstName || this.model.lastName || this.model.taxCode || this.model.userName || this.model.phoneNumber || this.model.email
|| this.model.medicalCertificateUrl || this.model.medicalCertificateExpiry) {
isModelChanged = true;
}
}
if (isModelChanged) {
return this.modalService.confirm(title, message, { cancelButtonLabel: 'Annulla', showClose: true })
.then(ok => {
if (ok && this.isCertificateChanged && this.model.medicalCertificateUrl) {
this.removeCertificate(false);
}
this.isCancelling = false;
return ok;
});
}
}
return true;
}
private initForm() {
this.addEditForm = this.formBuilder.group({
firstName: ['', Validators.required],
lastName: ['', Validators.required],
taxCode: ['', [Validators.required, Validators.pattern(/^[a-zA-Z]{6}[0-9]{2}[a-zA-Z][0-9]{2}[a-zA-Z][0-9]{3}[a-zA-Z]$/)]],
userName: ['', [Validators.required, Validators.minLength(6)]],
phoneNumber: ['', [Validators.required, Validators.pattern(/^\d+$/)]],
email: ['', [Validators.required, Validators.pattern(/^[a-zA-Z0-9\+\.\_\%\-\+]{1,256}\@[a-zA-Z0-9][a-zA-Z0-9\-]{0,64}(\.[a-zA-Z0-9][a-zA-Z0-9\-]{0,25})$/)]],
});
this.addEditForm.valueChanges
.subscribe(data => this.onValueChanged(data));
this.onValueChanged(); // (re)set validation messages now
}
onValueChanged(data?: any): void {
if (!this.addEditForm) { return; }
const form = this.addEditForm;
for (const field in this.formErrors) {
// clear previous error message (if any)
this.formErrors[field] = '';
const control = 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] + ' ';
}
}
}
}
formErrors: any = {
'firstName': '',
'lastName': '',
'taxCode': '',
'userName': '',
'phoneNumber': '',
'email': '',
'medicalCertificateExpiry': ''
};
validationMessages: any = {
'firstName': {
'required': 'Nome é obbligatorio.'
},
'lastName': {
'required': 'Cognome é obbligatorio.'
},
'taxCode': {
'required': 'Codice Fiscale é obbligatorio.',
'pattern': 'Codice Fiscale non valido.'
},
'userName': {
'required': 'Username é obbligatorio.',
'minlength': 'Username deve avere almeno 6 caratteri.'
},
'phoneNumber': {
'required': 'Telefono é obbligatorio.',
'pattern': 'Telefono non valido.'
},
'email': {
'required': 'Email é obbligatorio.',
'pattern': 'Email non valida.'
},
'medicalCertificateExpiry': {
'required': 'Scadenza Certificato Medico é obbligatorio.'
}
};
}
这是观点:
<div class="side-form">
<div [ngBusy]="isBusy"></div>
<h1 style="margin-bottom:1em;">{{title}}</h1>
<div class="form-container">
<form name="form" (ngSubmit)="f.form.valid && save()" #f="ngForm" [formGroup]="addEditForm" novalidate>
<div class="form-group" [ngClass]="{ 'has-danger': formErrors.firstName }">
<label class="form-control-label" for="firstName">Nome</label>
<input type="text" id="firstName" name="firstName" placeholder="Nome" [(ngModel)]="model.firstName" formControlName="firstName" class="form-control form-control-danger" required />
<div *ngIf="formErrors.firstName" class="form-control-feedback">
{{ formErrors.firstName }}
</div>
</div>
<div class="form-group" [ngClass]="{ 'has-danger': formErrors.lastName }">
<label class="form-control-label" for="lastName">Cognome</label>
<input type="text" id="lastName" name="lastName" placeholder="Cognome" [(ngModel)]="model.lastName" formControlName="lastName" class="form-control form-control-danger" required />
<div *ngIf="formErrors.lastName" class="form-control-feedback">
{{ formErrors.lastName }}
</div>
</div>
<div class="form-group" [ngClass]="{ 'has-danger': formErrors.taxCode }">
<label class="form-control-label" for="taxCode">Codice Fiscale</label>
<input type="text" id="taxCode" name="taxCode" placeholder="Codice Fiscale" [(ngModel)]="model.taxCode" formControlName="taxCode" class="form-control form-control-danger" required style="text-transform:uppercase"/>
<div *ngIf="formErrors.taxCode" class="form-control-feedback">
{{ formErrors.taxCode }}
</div>
</div>
<div class="form-group" [ngClass]="{ 'has-danger': formErrors.userName }">
<label class="form-control-label" for="userName">Username</label>
<input type="text" id="userName" name="userName" placeholder="Username" [(ngModel)]="model.userName" formControlName="userName" class="form-control form-control-danger" required min="6" />
<div *ngIf="formErrors.userName" class="form-control-feedback">
{{ formErrors.userName }}
</div>
</div>
<div class="form-group" [ngClass]="{ 'has-danger': formErrors.phoneNumber }">
<label class="form-control-label" for="phoneNumber">Telefono</label>
<input type="text" id="phoneNumber" name="phoneNumber" placeholder="Telefono" [(ngModel)]="model.phoneNumber" formControlName="phoneNumber" class="form-control form-control-danger" required />
<div *ngIf="formErrors.phoneNumber" class="form-control-feedback">
{{ formErrors.phoneNumber }}
</div>
</div>
<div class="form-group" [ngClass]="{ 'has-danger': formErrors.email }">
<label class="form-control-label" for="email">Email</label>
<input type="email" id="email" name="email" placeholder="Email" [(ngModel)]="model.email" formControlName="email" class="form-control form-control-danger" required /> <!-- email -->
<div *ngIf="formErrors.email" class="form-control-feedback">
{{ formErrors.email }}
</div>
</div>
<div class="form-group" [ngClass]="{ 'has-danger': formErrors.medicalCertificateUrl }">
<label class="form-control-label" for="medicalCertificateUrl">Certificato Medico</label>
<div>
<input #fileInput type="file" ng2FileSelect [uploader]="uploader" style="display:none;" />
<button id="browse" name="browse" type="button" class="btn btn-default" (click)="fileInput.click()" style="display:inline-block;" [disabled]="model.medicalCertificateUrl">Scegli file...</button>
<img *ngIf="uploading" src="data:image/gif;base64,R0lGODlhEAAQAPIAAP///wAAAMLCwkJCQgAAAGJiYoKCgpKSkiH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQJCgAAACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkECQoAAAAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkECQoAAAAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkECQoAAAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQJCgAAACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQJCgAAACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAkKAAAALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA==" />
<span style="margin-left:.5em" *ngIf="!model.medicalCertificateUrl">Nessun file selezionato</span>
<div *ngIf="model.medicalCertificateUrl" style="display:inline-block">
<span style="margin-left:.5em">{{ model.medicalCertificateUrl }}</span>
<button type="button" class="btn btn-danger btn-clear" (click)="removeCertificate()" title="Rimuovi certificato">
<i class="material-icons md-18">clear</i>
</button>
</div>
</div>
</div>
<div class="form-group" [ngClass]="{ 'has-danger': formErrors.medicalCertificateExpiry }" *ngIf="model.medicalCertificateUrl">
<label class="form-control-label" for="medicalCertificateExpiry">Scadenza Certificato Medico</label>
<ngb-date-time-picker id="medicalCertificateExpiry" name="medicalCertificateExpiry"
formControlName="medicalCertificateExpiry"
[(date)]="model.medicalCertificateExpiry"
(dateChange)="dateChanged($event)"
[dateOnly]="true"
[endOfDay]="true"
[minDate]="minDate"
placeholder="Scadenza...">
</ngb-date-time-picker>
<div *ngIf="formErrors.medicalCertificateExpiry" class="form-control-feedback">
{{ formErrors.medicalCertificateExpiry }}
</div>
</div>
<div class="form-group">
<button type="button" class="btn btn-secondary" routerLink="/registry" (click)="isCancelling = true">Annulla</button>
<button type="submit" class="btn btn-primary" [disabled]="!addEditForm.valid">Salva</button>
</div>
</form>
</div>
</div>
你是这么想的吗?您认为有什么需要改进的地方吗?
谢谢大家 :)