3

我正在尝试将 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>

你是这么想的吗?您认为有什么需要改进的地方吗?

谢谢大家 :)

4

0 回答 0