• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

atinc / ngx-tethys / 2870c620-2d85-41e2-b173-8f75b35be23b

05 Sep 2023 05:55AM UTC coverage: 90.112% (-0.009%) from 90.121%
2870c620-2d85-41e2-b173-8f75b35be23b

Pull #2817

circleci

cmm-va
fix(form): 增加validateDetail 函数,添加验证测试
Pull Request #2817: fix(form): 表达验证触发错误时候,应该自动聚焦到错误元素上

5128 of 6349 branches covered (0.0%)

Branch coverage included in aggregate %.

8 of 8 new or added lines in 2 files covered. (100.0%)

12970 of 13735 relevant lines covered (94.43%)

974.38 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.23
/src/form/form-validator.service.ts
1
import { Dictionary } from 'ngx-tethys/types';
2
import { isUndefinedOrNull } from 'ngx-tethys/util';
3
import { of, Subject } from 'rxjs';
4
import { debounceTime, distinctUntilChanged, filter, switchMap, takeUntil } from 'rxjs/operators';
5

6
import { Injectable, OnDestroy } from '@angular/core';
7
import { AbstractControl, FormControlName, FormGroupDirective, NgControl, NgForm, ValidationErrors } from '@angular/forms';
8

9
import { ERROR_VALUE_REPLACE_REGEX, ThyFormValidatorLoader } from './form-validator-loader';
10
import { ThyFormValidatorConfig, ThyValidateOn, ThyValidateResult } from './form.class';
11

1✔
12
/**
13
 * @private
111✔
14
 */
111✔
15
@Injectable()
106✔
16
export class ThyFormValidatorService implements OnDestroy {
17
    private _ngForm: NgForm | FormGroupDirective;
18

5✔
19
    private _formElement: HTMLFormElement;
20

21
    private _config: ThyFormValidatorConfig;
22

98✔
23
    public errors: string[] = [];
10✔
24

10✔
25
    private _controls: NgControl[] = [];
10✔
26

27
    // 记录所有元素的验证信息
28
    public validations: Dictionary<{
29
        hasError?: boolean;
79✔
30
        errorMessages?: string[];
79✔
31
    }> = {};
58✔
32

33
    private _destroy$ = new Subject<void>();
79✔
34

35
    private _getElement(name: string) {
36
        const element = this._formElement.elements[name];
1✔
37
        if (element) {
38
            return element;
39
        } else {
19✔
40
            return this._formElement.querySelector(`[name='${name}']`);
41
        }
42
    }
7✔
43

44
    private _clearElementError(name: string) {
9✔
45
        if (this.validations[name] && this.validations[name].hasError) {
46
            this.validations[name].hasError = false;
8✔
47
            this.validations[name].errorMessages = [];
8✔
48
            this.thyFormValidateLoader.removeError(this._getElement(name));
49
        }
50
    }
51

52
    private _tryGetValidation(name: string) {
7✔
53
        const controls = this._getControls();
7!
54
        if (!this.validations[name]) {
55
            this._initializeFormControlValidation(name, controls[name] as any);
7✔
56
        }
1✔
57
        return this.validations[name];
×
58
    }
59

60
    private _addError(message: string) {
61
        this.errors.unshift(message);
6✔
62
    }
6✔
63

64
    private _clearErrors() {
65
        this.errors = [];
66
    }
67

68
    private _setControlValidateByChange(control: NgControl) {
72✔
69
        control.valueChanges
70
            .pipe(
71
                debounceTime(100),
72
                distinctUntilChanged(),
72✔
73
                filter(item => {
7✔
74
                    return item;
75
                }),
76
                switchMap(item => {
65✔
77
                    this.validateControl(control.name as string);
7✔
78
                    return of([]);
79
                })
65✔
80
            )
19✔
81
            .subscribe();
19✔
82
    }
83

84
    private _setControlValidateByBlur(control: NgControl) {
85
        const element: HTMLElement = this._getElement(control.name as string);
86
        if (element) {
7✔
87
            // 继承了 AbstractControlValueAccessor 的自定义 Accessor,通过 __onBlurValidation 控制触发验证函数
7!
88
            if (control.valueAccessor['__onBlurValidation']) {
7✔
89
                control.valueAccessor['__onBlurValidation'] = () => {
7✔
90
                    this.validateControl(control.name as string);
91
                };
92
            } else {
93
                element.onblur = (event: FocusEvent) => {
22✔
94
                    this.validateControl(control.name as string);
22✔
95
                };
22!
96
            }
22✔
97
        }
10!
98
    }
10!
99

100
    private _initializeFormControlValidation(name: string, control: AbstractControl | FormControlName | NgControl) {
101
        this.validations[name] = {
102
            hasError: false,
103
            errorMessages: []
×
104
        };
105
        if (this._getValidateOn() === 'change') {
106
            this._setControlValidateByChange(control as NgControl);
107
        } else {
22✔
108
            if (this._getValidateOn() === 'blur') {
22✔
109
                this._setControlValidateByBlur(control as NgControl);
110
            }
111

112
            control.valueChanges.pipe(takeUntil(this._destroy$)).subscribe(item => {
10✔
113
                this._clearElementError(name);
114
                this._clearErrors();
115
            });
12✔
116
        }
117
    }
22✔
118

119
    private _restFormControlValidation(name: string) {
120
        const validation = this.validations[name];
22✔
121
        if (validation) {
22✔
122
            validation.hasError = false;
22!
123
            validation.errorMessages = [];
22✔
124
        }
125
    }
126

22✔
127
    private _formatValidationMessage(name: string, message: string) {
128
        const controls = this._getControls();
129
        const control = controls[name];
24✔
130
        if (control) {
24✔
131
            return message.replace(ERROR_VALUE_REPLACE_REGEX, (tag, key) => {
24✔
132
                if (key) {
24✔
133
                    return isUndefinedOrNull(control.errors[key][key]) ? control.errors[key].requiredLength : control.errors[key][key];
134
                }
135
            });
298✔
136
        } else {
137
            return message;
138
        }
152✔
139
    }
152✔
140

152✔
141
    private _getValidationMessage(name: string, validationError: string) {
142
        let message = null;
152✔
143
        if (
152✔
144
            this._config &&
145
            this._config.validationMessages &&
146
            this._config.validationMessages[name] &&
151✔
147
            this._config.validationMessages[name][validationError]
151✔
148
        ) {
149
            message = this._config.validationMessages[name][validationError];
150
        } else {
161✔
151
            message = this.thyFormValidateLoader.getErrorMessage(name, validationError);
6!
152
        }
20✔
153
        return this._formatValidationMessage(name, message);
14✔
154
    }
155

156
    private _getValidationMessages(name: string, validationErrors: ValidationErrors) {
6✔
157
        const messages = [];
158
        for (const validationError in validationErrors) {
159
            if (validationErrors.hasOwnProperty(validationError)) {
160
                messages.push(this._getValidationMessage(name, validationError));
41✔
161
            }
162
        }
163
        return messages;
188✔
164
    }
130✔
165

166
    private _setControlValidationError(name: string, errorMessages: string[]) {
58!
167
        const validation = this._tryGetValidation(name);
58✔
168
        validation.errorMessages = errorMessages;
58✔
169
        validation.hasError = true;
232✔
170
        this.thyFormValidateLoader.showError(this._getElement(name), errorMessages);
171
    }
58✔
172

173
    private _getValidateOn(): ThyValidateOn {
174
        return (this._config && this._config.validateOn) || this.thyFormValidateLoader.validateOn;
175
    }
70✔
176

70✔
177
    constructor(private thyFormValidateLoader: ThyFormValidatorLoader) {}
178

179
    initialize(ngForm: NgForm | FormGroupDirective, formElement: HTMLFormElement) {
70✔
180
        this._ngForm = ngForm;
70✔
181
        this._formElement = formElement;
70✔
182
    }
22✔
183

22✔
184
    initializeFormControlsValidation(controls: NgControl[]) {
185
        if (this._getValidateOn() !== 'submit') {
70✔
186
            (controls || []).forEach((control: NgControl) => {
187
                if (!this._controls.find(item => item.name === control.name)) {
188
                    this._initializeFormControlValidation(control.name as string, control);
189
                }
190
            });
191
            this._controls = controls;
192
        }
193
    }
194

195
    setValidatorConfig(config: ThyFormValidatorConfig) {
17✔
196
        this._config = config;
17✔
197
    }
17✔
198

55!
199
    private _getControls() {
55✔
200
        if (this._ngForm instanceof NgForm) {
55✔
201
            return (this._ngForm as NgForm).controls;
55✔
202
        } else if (this._ngForm instanceof FormGroupDirective) {
203
            const controls = {};
204
            (this._ngForm as FormGroupDirective).directives.forEach(directive => {
205
                controls[directive.name] = directive;
17✔
206
            });
17✔
207
            return controls;
55!
208
        }
×
209
    }
210

211
    private _getControlByName(name: string): AbstractControl | FormControlName {
17✔
212
        const controls = this._getControls();
213
        return controls[name];
214
    }
1✔
215

216
    validateControl(name: string) {
217
        this._clearElementError(name);
×
218
        const control = this._getControlByName(name);
×
219
        if (control && control.invalid) {
×
220
            const errorMessages = this._getValidationMessages(name, control.errors);
221
            this._setControlValidationError(name, errorMessages);
222
        }
17✔
223
        return {
17✔
224
            valid: control.valid,
17✔
225
            control: control,
226
            element: this._getElement(name)
55✔
227
        };
55✔
228
    }
229

230
    validateControls() {
231
        // 主要是 无法检测到 ngForm 的 controls 的变化,或者是我没有找到
2✔
232
        // 验证的时候循环 ngForm 的 controls 验证
2✔
233
        // 发现没有 validation 初始化一个,已经存在不会重新初始化,保存缓存数据
7!
234
        const results = [];
7✔
235
        const controls = this._getControls();
7✔
236
        for (const name in controls) {
237
            if (controls.hasOwnProperty(name)) {
238
                this._tryGetValidation(name);
239
                const result = this.validateControl(name);
240
                results.push(result);
2✔
241
            }
2✔
242
        }
243
        // 移除已经不存在的 validation
244
        const names = Object.keys(this.validations);
152✔
245
        names.forEach(name => {
246
            if (!controls[name]) {
1✔
247
                delete this.validations[name];
248
            }
249
        });
250
        return results;
1✔
251
    }
252

253
    addError(message: string) {
254
        this._addError(message);
255
    }
256

257
    validate($event?: Event): boolean {
258
        this._ngForm.onSubmit($event);
259
        this.validateControls();
260
        return this._ngForm.valid;
261
    }
262

263
    validateWithDetail($event?: Event): ThyValidateResult {
264
        this._ngForm.onSubmit($event);
265
        const results = this.validateControls();
266
        return {
267
            valid: this._ngForm.valid,
268
            invalidControls: results.filter(res => !res.valid),
269
            validControls: results.filter(res => res.valid)
270
        };
271
    }
272

273
    reset() {
274
        this._ngForm.reset();
275
        for (const name in this.validations) {
276
            if (this.validations.hasOwnProperty(name)) {
277
                this._restFormControlValidation(name);
278
                this._clearElementError(name);
279
            }
280
        }
281
    }
282

283
    setElementErrorMessage(name: string, message: string) {
284
        this._clearElementError(name);
285
        this._setControlValidationError(name, [message]);
286
    }
287

288
    ngOnDestroy(): void {
289
        this._destroy$.next();
290
    }
291
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc