Skip to content

Commit f886c06

Browse files
authored
fix(ref: 1560): form marked as dirty on initial load
fix(ref: 1560): form marked as dirty on initial load
2 parents c8b18ae + 11d457c commit f886c06

File tree

6 files changed

+51
-64
lines changed

6 files changed

+51
-64
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# 20.0.3(2025-08-01)
2+
3+
### fix
4+
5+
- Fix ([#1560](https://github.com/JsDaddy/ngx-mask/issues/1560))
6+
17
# 20.0.2(2025-07-31)
28

39
### fix

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ngx-mask",
3-
"version": "20.0.2",
3+
"version": "20.0.3",
44
"description": "Awesome ngx mask",
55
"license": "MIT",
66
"engines": {

projects/ngx-mask-lib/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ngx-mask",
3-
"version": "20.0.2",
3+
"version": "20.0.3",
44
"description": "awesome ngx mask",
55
"keywords": [
66
"ng2-mask",

projects/ngx-mask-lib/src/lib/ngx-mask.directive.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,7 @@ export class NgxMaskDirective implements ControlValueAccessor, OnChanges, Valida
416416

417417
@HostListener('input', ['$event'])
418418
public onInput(e: CustomKeyboardEvent): void {
419+
this._maskService.isInitialized = true;
419420
// If IME is composing text, we wait for the composed text.
420421
if (this._isComposing()) {
421422
return;
@@ -1024,8 +1025,10 @@ export class NgxMaskDirective implements ControlValueAccessor, OnChanges, Valida
10241025
];
10251026
// Let the service know we've finished writing value
10261027
this._maskService.writingValue = false;
1028+
this._maskService.isInitialized = true;
10271029
} else {
10281030
this._maskService.formElementProperty = ['value', inputValue];
1031+
this._maskService.isInitialized = true;
10291032
}
10301033
this._inputValue.set(inputValue);
10311034
} else {

projects/ngx-mask-lib/src/lib/ngx-mask.service.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export class NgxMaskService extends NgxMaskApplierService {
2121
* since writeValue should be a one way only process of writing the DOM value based on the Angular model value.
2222
*/
2323
public writingValue = false;
24+
public isInitialized = false;
2425

2526
private _emitValue = false;
2627
private _start!: number;
@@ -267,7 +268,6 @@ export class NgxMaskService extends NgxMaskApplierService {
267268

268269
this._emitValue =
269270
this.previousValue !== this.currentValue ||
270-
(newInputValue !== this.currentValue && this.writingValue) ||
271271
(this.previousValue === this.currentValue && justPasted);
272272
}
273273

@@ -594,8 +594,14 @@ export class NgxMaskService extends NgxMaskApplierService {
594594
const outputTransformFn = this.outputTransformFn
595595
? this.outputTransformFn
596596
: (v: unknown) => v;
597+
597598
this.writingValue = false;
598599
this.maskChanged = false;
600+
601+
if (!this.isInitialized && this._emitValue) {
602+
return;
603+
}
604+
599605
if (Array.isArray(this.dropSpecialCharacters)) {
600606
this.onChange(
601607
outputTransformFn(
Lines changed: 33 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Component } from '@angular/core';
22
import type { ComponentFixture } from '@angular/core/testing';
33
import { TestBed } from '@angular/core/testing';
4-
import { FormControl, ReactiveFormsModule } from '@angular/forms';
4+
import { FormControl, ReactiveFormsModule, FormsModule } from '@angular/forms';
55
import { NgxMaskDirective, provideNgxMask } from 'ngx-mask';
66

77
@Component({
@@ -13,79 +13,51 @@ class TestMaskComponent {
1313
public form: FormControl = new FormControl('');
1414
}
1515

16+
@Component({
17+
selector: 'jsdaddy-phone-test',
18+
imports: [FormsModule, NgxMaskDirective],
19+
template: `
20+
<form #phoneForm="ngForm">
21+
<input
22+
name="phoneNumber"
23+
[(ngModel)]="phoneNumber"
24+
[pattern]="phoneValidationPattern"
25+
[dropSpecialCharacters]="false"
26+
[mask]="phoneMask" />
27+
</form>
28+
`,
29+
})
30+
class TestPhoneMaskComponent {
31+
public phoneValidationPattern =
32+
/^\(?([2-9][0-8][0-9])\)?[-. ]*([2-9][0-9]{2})[-. ]*([0-9]{4})$/;
33+
public phoneMask = '(000) 000-0000';
34+
public phoneNumber = '3333333333';
35+
}
36+
1637
describe('Directive: Forms', () => {
1738
let fixture: ComponentFixture<TestMaskComponent>;
18-
let component: TestMaskComponent;
1939

2040
beforeEach(() => {
2141
TestBed.configureTestingModule({
2242
imports: [NgxMaskDirective],
2343
providers: [provideNgxMask()],
2444
});
2545
fixture = TestBed.createComponent(TestMaskComponent);
26-
component = fixture.componentInstance;
2746
fixture.detectChanges();
2847
});
2948

30-
it('should propagate masked value to the form control value', () => {
31-
component.form.setValue('A1234Z');
32-
expect(component.form.value).toBe('1234');
33-
});
34-
35-
it('should propagate masked value to the form control valueChanges observable', () => {
36-
component.form.valueChanges.subscribe((newValue) => {
37-
expect(newValue).toEqual('1234');
38-
});
39-
40-
component.form.setValue('A1234Z');
41-
});
42-
43-
it('should mask values when multiple calls to setValue() are made', () => {
44-
component.form.setValue('A1234Z');
45-
expect(component.form.value).toBe('1234');
46-
component.form.setValue('A1234Z');
47-
expect(component.form.value).toBe('1234');
48-
component.form.setValue('A1234Z');
49-
expect(component.form.value).toBe('1234');
50-
});
51-
52-
it('should propagate masked value to the form control valueChanges observable when multiple calls to setValue() are made', () => {
53-
component.form.valueChanges.subscribe((newValue) => {
54-
expect(newValue).toEqual('1234');
55-
});
56-
57-
component.form.setValue('A1234Z');
58-
component.form.setValue('A1234Z');
59-
component.form.setValue('A1234Z');
60-
});
61-
62-
it('should not emit to valueChanges if the masked value has not changed with emitEvent: true', () => {
63-
let emissionsToValueChanges = 0;
64-
65-
component.form.valueChanges.subscribe(() => {
66-
emissionsToValueChanges++;
67-
});
68-
69-
component.form.setValue('1234', { emitEvent: true });
70-
component.form.setValue('1234', { emitEvent: true });
71-
72-
// Expect to emit 3 times, once for the first setValue() call, once by ngx-mask, and once for the second setValue() call.
73-
// There is not fourth emission for when ngx-mask masks the value for a second time.
74-
expect(emissionsToValueChanges).toBe(3);
75-
});
76-
77-
it('should not emit to valueChanges if the masked value has not changed with emitEvent: false', () => {
78-
let emissionsToValueChanges = 0;
79-
80-
component.form.valueChanges.subscribe(() => {
81-
emissionsToValueChanges++;
82-
});
49+
it('should not mark form as dirty on initial load with initial value', () => {
50+
const testBed = TestBed.createComponent(TestPhoneMaskComponent);
51+
const phoneComponent = testBed.componentInstance;
52+
phoneComponent.phoneNumber = '3333333333';
53+
testBed.detectChanges();
8354

84-
component.form.setValue('1234', { emitEvent: false });
85-
component.form.setValue('1234', { emitEvent: false });
55+
// Get the form element and check if it's not dirty
56+
const formElement = testBed.nativeElement.querySelector('form');
57+
const inputElement = testBed.nativeElement.querySelector('input');
8658

87-
// Expect to only have emitted once, only by ngx-mask.
88-
// There is no second emission for when ngx-mask masks the value for a second time.
89-
expect(emissionsToValueChanges).toBe(1);
59+
// Check that the form is not dirty on initial load
60+
expect(formElement.classList.contains('ng-dirty')).toBe(false);
61+
expect(inputElement.classList.contains('ng-dirty')).toBe(false);
9062
});
9163
});

0 commit comments

Comments
 (0)