diff --git a/components/radio/radio.component.ts b/components/radio/radio.component.ts
index e5b5fd924e..f384c54d3e 100644
--- a/components/radio/radio.component.ts
+++ b/components/radio/radio.component.ts
@@ -25,6 +25,7 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { fromEvent, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
+import { NzFormStatusService } from 'ng-zorro-antd/core/form';
import { BooleanInput, NzSafeAny, OnChangeType, OnTouchedType } from 'ng-zorro-antd/core/types';
import { InputBoolean } from 'ng-zorro-antd/core/util';
@@ -68,6 +69,7 @@ import { NzRadioService } from './radio.service';
}
],
host: {
+ '[class.ant-radio-wrapper-in-form-item]': '!!nzFormStatusService',
'[class.ant-radio-wrapper]': '!isRadioButton',
'[class.ant-radio-button-wrapper]': 'isRadioButton',
'[class.ant-radio-wrapper-checked]': 'isChecked && !isRadioButton',
@@ -111,7 +113,8 @@ export class NzRadioComponent implements ControlValueAccessor, AfterViewInit, On
private focusMonitor: FocusMonitor,
@Optional() private directionality: Directionality,
@Optional() @Inject(NzRadioService) private nzRadioService: NzRadioService | null,
- @Optional() @Inject(NzRadioButtonDirective) private nzRadioButtonDirective: NzRadioButtonDirective | null
+ @Optional() @Inject(NzRadioButtonDirective) private nzRadioButtonDirective: NzRadioButtonDirective | null,
+ @Optional() public nzFormStatusService?: NzFormStatusService
) {}
setDisabledState(disabled: boolean): void {
diff --git a/components/select/select-arrow.component.ts b/components/select/select-arrow.component.ts
index 9e2d6f0fc3..f489bdc60b 100644
--- a/components/select/select-arrow.component.ts
+++ b/components/select/select-arrow.component.ts
@@ -14,16 +14,17 @@ import { NzSafeAny } from 'ng-zorro-antd/core/types';
template: `
-
+
-
+
+ {{ feedbackIcon }}
`,
host: {
class: 'ant-select-arrow',
@@ -33,7 +34,9 @@ import { NzSafeAny } from 'ng-zorro-antd/core/types';
export class NzSelectArrowComponent {
@Input() loading = false;
@Input() search = false;
+ @Input() showArrow = false;
@Input() suffixIcon: TemplateRef | string | null = null;
+ @Input() feedbackIcon: TemplateRef | string | null = null;
constructor() {}
}
diff --git a/components/select/select.component.ts b/components/select/select.component.ts
index fba46f434c..4130fbfd93 100644
--- a/components/select/select.component.ts
+++ b/components/select/select.component.ts
@@ -33,11 +33,12 @@ import {
ViewEncapsulation
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
-import { BehaviorSubject, combineLatest, fromEvent, merge } from 'rxjs';
-import { startWith, switchMap, takeUntil } from 'rxjs/operators';
+import { BehaviorSubject, combineLatest, fromEvent, merge, of as observableOf } from 'rxjs';
+import { distinctUntilChanged, map, startWith, switchMap, takeUntil, withLatestFrom } from 'rxjs/operators';
import { slideMotion } from 'ng-zorro-antd/core/animation';
import { NzConfigKey, NzConfigService, WithConfig } from 'ng-zorro-antd/core/config';
+import { NzFormNoStatusService, NzFormStatusService } from 'ng-zorro-antd/core/form';
import { NzNoAnimationDirective } from 'ng-zorro-antd/core/no-animation';
import { cancelRequestAnimationFrame, reqAnimFrame } from 'ng-zorro-antd/core/polyfill';
import { NzDestroyService } from 'ng-zorro-antd/core/services';
@@ -46,6 +47,7 @@ import {
NgClassInterface,
NzSafeAny,
NzStatus,
+ NzValidateStatus,
OnChangeType,
OnTouchedType
} from 'ng-zorro-antd/core/types';
@@ -108,11 +110,18 @@ export type NzSelectSizeType = 'large' | 'default' | 'small';
(keydown)="onKeyDown($event)"
>
+ [feedbackIcon]="feedbackIconTpl"
+ >
+
+
+
+
+
{
+ return pre.status === cur.status && pre.hasFeedback === cur.hasFeedback;
+ }),
+ withLatestFrom(this.nzFormNoStatusService ? this.nzFormNoStatusService.noFormStatus : observableOf(false)),
+ map(([{ status, hasFeedback }, noStatus]) => ({ status: noStatus ? '' : status, hasFeedback })),
+ takeUntil(this.destroy$)
+ )
+ .subscribe(({ status, hasFeedback }) => {
+ this.setStatusStyles(status, hasFeedback);
+ });
+
this.focusMonitor
.monitor(this.host, true)
.pipe(takeUntil(this.destroy$))
@@ -714,9 +740,12 @@ export class NzSelectComponent implements ControlValueAccessor, OnInit, AfterCon
this.focusMonitor.stopMonitoring(this.host);
}
- private setStatusStyles(): void {
+ private setStatusStyles(status: NzValidateStatus, hasFeedback: boolean): void {
+ this.status = status;
+ this.hasFeedback = hasFeedback;
+ this.cdr.markForCheck();
// render status if nzStatus is set
- this.statusCls = getStatusClassNames(this.prefixCls, this.nzStatus, this.hasFeedback);
+ this.statusCls = getStatusClassNames(this.prefixCls, status, hasFeedback);
Object.keys(this.statusCls).forEach(status => {
if (this.statusCls[status]) {
this.renderer.addClass(this.host.nativeElement, status);
diff --git a/components/select/select.module.ts b/components/select/select.module.ts
index d6b8222b7e..93a30a37f3 100644
--- a/components/select/select.module.ts
+++ b/components/select/select.module.ts
@@ -12,6 +12,7 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
+import { NzFormPatchModule } from 'ng-zorro-antd/core/form';
import { NzNoAnimationModule } from 'ng-zorro-antd/core/no-animation';
import { NzOutletModule } from 'ng-zorro-antd/core/outlet';
import { NzOverlayModule } from 'ng-zorro-antd/core/overlay';
@@ -47,6 +48,7 @@ import { NzSelectComponent } from './select.component';
NzOverlayModule,
NzNoAnimationModule,
NzTransitionPatchModule,
+ NzFormPatchModule,
ScrollingModule,
A11yModule
],
diff --git a/components/select/select.spec.ts b/components/select/select.spec.ts
index cee48a71fa..b3d93ad45b 100644
--- a/components/select/select.spec.ts
+++ b/components/select/select.spec.ts
@@ -13,6 +13,7 @@ import {
ɵcreateComponentBed as createComponentBed
} from 'ng-zorro-antd/core/testing';
import { NzSafeAny, NzStatus } from 'ng-zorro-antd/core/types';
+import { NzFormControlStatusType, NzFormModule } from 'ng-zorro-antd/form';
import { NzIconTestModule } from 'ng-zorro-antd/icon/testing';
import { NzSelectSearchComponent } from './select-search.component';
@@ -1296,6 +1297,40 @@ describe('select', () => {
expect(selectElement.classList).not.toContain('ant-select-status-warning');
});
});
+ describe('in form', () => {
+ let testBed: ComponentBed;
+ let component: TestSelectInFormComponent;
+ let fixture: ComponentFixture;
+ let selectElement!: HTMLElement;
+
+ beforeEach(() => {
+ testBed = createComponentBed(TestSelectInFormComponent, {
+ imports: [NzSelectModule, NzIconTestModule, NzFormModule, FormsModule]
+ });
+ component = testBed.component;
+ fixture = testBed.fixture;
+ selectElement = testBed.debugElement.query(By.directive(NzSelectComponent)).nativeElement;
+ });
+
+ it('should classname correct', () => {
+ fixture.detectChanges();
+ expect(selectElement.classList).toContain('ant-select-status-error');
+ expect(selectElement.classList).toContain('ant-select-in-form-item');
+ expect(selectElement.querySelector('nz-form-item-feedback-icon')).toBeTruthy();
+
+ component.status = 'warning';
+ fixture.detectChanges();
+ expect(selectElement.classList).toContain('ant-select-status-warning');
+
+ component.status = 'success';
+ fixture.detectChanges();
+ expect(selectElement.classList).toContain('ant-select-status-success');
+
+ component.feedback = false;
+ fixture.detectChanges();
+ expect(selectElement.querySelector('nz-form-item-feedback-icon')).toBeNull();
+ });
+ });
});
@Component({
@@ -1593,3 +1628,19 @@ export class TestSelectReactiveTagsComponent {
export class TestSelectStatusComponent {
status: NzStatus = 'error';
}
+
+@Component({
+ template: `
+
+ `
+})
+export class TestSelectInFormComponent {
+ status: NzFormControlStatusType = 'error';
+ feedback = true;
+}
diff --git a/components/time-picker/time-picker.component.spec.ts b/components/time-picker/time-picker.component.spec.ts
index ffd4716f2c..35b153db55 100644
--- a/components/time-picker/time-picker.component.spec.ts
+++ b/components/time-picker/time-picker.component.spec.ts
@@ -12,6 +12,7 @@ import { dispatchFakeEvent, dispatchMouseEvent, typeInElement } from 'ng-zorro-a
import { NzStatus } from 'ng-zorro-antd/core/types';
import { PREFIX_CLASS } from 'ng-zorro-antd/date-picker';
import { getPickerInput, getPickerOkButton } from 'ng-zorro-antd/date-picker/testing/util';
+import { NzFormControlStatusType, NzFormModule } from 'ng-zorro-antd/form';
import { en_GB, NzI18nModule, NzI18nService } from '../i18n';
import { NzTimePickerComponent } from './time-picker.component';
@@ -26,9 +27,14 @@ describe('time-picker', () => {
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
- imports: [BidiModule, NoopAnimationsModule, FormsModule, NzI18nModule, NzTimePickerModule],
+ imports: [BidiModule, NoopAnimationsModule, FormsModule, NzI18nModule, NzTimePickerModule, NzFormModule],
schemas: [NO_ERRORS_SCHEMA],
- declarations: [NzTestTimePickerComponent, NzTestTimePickerStatusComponent, NzTestTimePickerDirComponent]
+ declarations: [
+ NzTestTimePickerComponent,
+ NzTestTimePickerStatusComponent,
+ NzTestTimePickerDirComponent,
+ NzTestTimePickerInFormComponent
+ ]
});
TestBed.compileComponents();
inject([OverlayContainer], (oc: OverlayContainer) => {
@@ -336,6 +342,35 @@ describe('time-picker', () => {
});
});
+ describe('time-picker in form', () => {
+ let testComponent: NzTestTimePickerInFormComponent;
+ let fixture: ComponentFixture;
+ let timeElement!: HTMLElement;
+ beforeEach(() => {
+ fixture = TestBed.createComponent(NzTestTimePickerInFormComponent);
+ testComponent = fixture.debugElement.componentInstance;
+ fixture.detectChanges();
+ timeElement = fixture.debugElement.query(By.directive(NzTimePickerComponent)).nativeElement;
+ });
+ it('should className correct', () => {
+ fixture.detectChanges();
+ expect(timeElement.classList).toContain('ant-picker-status-error');
+ expect(timeElement.querySelector('nz-form-item-feedback-icon')).toBeTruthy();
+
+ testComponent.status = 'warning';
+ fixture.detectChanges();
+ expect(timeElement.classList).toContain('ant-picker-status-warning');
+
+ testComponent.status = 'success';
+ fixture.detectChanges();
+ expect(timeElement.classList).toContain('ant-picker-status-success');
+
+ testComponent.feedback = false;
+ fixture.detectChanges();
+ expect(timeElement.querySelector('nz-form-item-feedback-icon')).toBeNull();
+ });
+ });
+
function queryFromOverlay(selector: string): HTMLElement {
return overlayContainerElement.querySelector(selector) as HTMLElement;
}
@@ -392,3 +427,19 @@ export class NzTestTimePickerStatusComponent {
export class NzTestTimePickerDirComponent {
dir: Direction = 'ltr';
}
+
+@Component({
+ template: `
+
+ `
+})
+export class NzTestTimePickerInFormComponent {
+ status: NzFormControlStatusType = 'error';
+ feedback = true;
+}
diff --git a/components/time-picker/time-picker.component.ts b/components/time-picker/time-picker.component.ts
index 301776d926..b6416e93f1 100644
--- a/components/time-picker/time-picker.component.ts
+++ b/components/time-picker/time-picker.component.ts
@@ -27,14 +27,15 @@ import {
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Observable, of, Subject } from 'rxjs';
-import { map, takeUntil } from 'rxjs/operators';
+import { distinctUntilChanged, map, takeUntil, withLatestFrom } from 'rxjs/operators';
import { isValid } from 'date-fns';
import { slideMotion } from 'ng-zorro-antd/core/animation';
import { NzConfigKey, NzConfigService, WithConfig } from 'ng-zorro-antd/core/config';
+import { NzFormNoStatusService, NzFormStatusService } from 'ng-zorro-antd/core/form';
import { warn } from 'ng-zorro-antd/core/logger';
-import { BooleanInput, NgClassInterface, NzSafeAny, NzStatus } from 'ng-zorro-antd/core/types';
+import { BooleanInput, NgClassInterface, NzSafeAny, NzStatus, NzValidateStatus } from 'ng-zorro-antd/core/types';
import { getStatusClassNames, InputBoolean, isNil } from 'ng-zorro-antd/core/util';
import { DateHelperService, NzI18nInterface, NzI18nService } from 'ng-zorro-antd/i18n';
@@ -66,6 +67,7 @@ const NZ_CONFIG_MODULE_NAME: NzConfigKey = 'timePicker';
+
@@ -179,12 +181,13 @@ export class NzTimePickerComponent implements ControlValueAccessor, OnInit, Afte
// status
prefixCls: string = 'ant-picker';
statusCls: NgClassInterface = {};
+ status: NzValidateStatus = '';
hasFeedback: boolean = false;
@ViewChild('inputElement', { static: true }) inputRef!: ElementRef;
@Input() nzId: string | null = null;
@Input() nzSize: string | null = null;
- @Input() nzStatus?: NzStatus;
+ @Input() nzStatus: NzStatus = '';
@Input() @WithConfig() nzHourStep: number = 1;
@Input() @WithConfig() nzMinuteStep: number = 1;
@Input() @WithConfig() nzSecondStep: number = 1;
@@ -329,10 +332,25 @@ export class NzTimePickerComponent implements ControlValueAccessor, OnInit, Afte
private cdr: ChangeDetectorRef,
private dateHelper: DateHelperService,
private platform: Platform,
- @Optional() private directionality: Directionality
+ @Optional() private directionality: Directionality,
+ @Optional() private nzFormStatusService?: NzFormStatusService,
+ @Optional() private nzFormNoStatusService?: NzFormNoStatusService
) {}
ngOnInit(): void {
+ this.nzFormStatusService?.formStatusChanges
+ .pipe(
+ distinctUntilChanged((pre, cur) => {
+ return pre.status === cur.status && pre.hasFeedback === cur.hasFeedback;
+ }),
+ withLatestFrom(this.nzFormNoStatusService ? this.nzFormNoStatusService.noFormStatus : of(false)),
+ map(([{ status, hasFeedback }, noStatus]) => ({ status: noStatus ? '' : status, hasFeedback })),
+ takeUntil(this.destroy$)
+ )
+ .subscribe(({ status, hasFeedback }) => {
+ this.setStatusStyles(status, hasFeedback);
+ });
+
this.inputSize = Math.max(8, this.nzFormat.length) + 2;
this.origin = new CdkOverlayOrigin(this.element);
@@ -369,7 +387,7 @@ export class NzTimePickerComponent implements ControlValueAccessor, OnInit, Afte
this.updateAutoFocus();
}
if (nzStatus) {
- this.setStatusStyles();
+ this.setStatusStyles(this.nzStatus, this.hasFeedback);
}
}
@@ -430,9 +448,13 @@ export class NzTimePickerComponent implements ControlValueAccessor, OnInit, Afte
);
}
- private setStatusStyles(): void {
+ private setStatusStyles(status: NzValidateStatus, hasFeedback: boolean): void {
+ // set inner status
+ this.status = status;
+ this.hasFeedback = hasFeedback;
+ this.cdr.markForCheck();
// render status if nzStatus is set
- this.statusCls = getStatusClassNames(this.prefixCls, this.nzStatus, this.hasFeedback);
+ this.statusCls = getStatusClassNames(this.prefixCls, status, hasFeedback);
Object.keys(this.statusCls).forEach(status => {
if (this.statusCls[status]) {
this.renderer.addClass(this.element.nativeElement, status);
diff --git a/components/time-picker/time-picker.module.ts b/components/time-picker/time-picker.module.ts
index 5f75550e7b..1a51e2313e 100644
--- a/components/time-picker/time-picker.module.ts
+++ b/components/time-picker/time-picker.module.ts
@@ -10,6 +10,7 @@ import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { NzButtonModule } from 'ng-zorro-antd/button';
+import { NzFormPatchModule } from 'ng-zorro-antd/core/form';
import { NzOutletModule } from 'ng-zorro-antd/core/outlet';
import { NzOverlayModule } from 'ng-zorro-antd/core/overlay';
import { NzI18nModule } from 'ng-zorro-antd/i18n';
@@ -30,7 +31,8 @@ import { NzTimePickerComponent } from './time-picker.component';
NzIconModule,
NzOverlayModule,
NzOutletModule,
- NzButtonModule
+ NzButtonModule,
+ NzFormPatchModule
]
})
export class NzTimePickerModule {}
diff --git a/components/transfer/transfer.component.ts b/components/transfer/transfer.component.ts
index 673c71f115..b35c0bfa7c 100644
--- a/components/transfer/transfer.component.ts
+++ b/components/transfer/transfer.component.ts
@@ -23,10 +23,18 @@ import {
ViewChildren,
ViewEncapsulation
} from '@angular/core';
-import { Observable, of, Subject } from 'rxjs';
-import { takeUntil } from 'rxjs/operators';
+import { Observable, of as observableOf, of, Subject } from 'rxjs';
+import { distinctUntilChanged, map, takeUntil, withLatestFrom } from 'rxjs/operators';
-import { BooleanInput, NgClassInterface, NgStyleInterface, NzSafeAny, NzStatus } from 'ng-zorro-antd/core/types';
+import { NzFormNoStatusService, NzFormStatusService } from 'ng-zorro-antd/core/form';
+import {
+ BooleanInput,
+ NgClassInterface,
+ NgStyleInterface,
+ NzSafeAny,
+ NzStatus,
+ NzValidateStatus
+} from 'ng-zorro-antd/core/types';
import { getStatusClassNames, InputBoolean, toArray } from 'ng-zorro-antd/core/util';
import { NzI18nService, NzTransferI18nInterface } from 'ng-zorro-antd/i18n';
@@ -188,7 +196,7 @@ export class NzTransferComponent implements OnInit, OnChanges, OnDestroy {
@Input() nzNotFoundContent?: string;
@Input() nzTargetKeys: string[] = [];
@Input() nzSelectedKeys: string[] = [];
- @Input() nzStatus?: NzStatus;
+ @Input() nzStatus: NzStatus = '';
// events
@Output() readonly nzChange = new EventEmitter();
@@ -296,7 +304,9 @@ export class NzTransferComponent implements OnInit, OnChanges, OnDestroy {
private i18n: NzI18nService,
private elementRef: ElementRef,
private renderer: Renderer2,
- @Optional() private directionality: Directionality
+ @Optional() private directionality: Directionality,
+ @Optional() private nzFormStatusService?: NzFormStatusService,
+ @Optional() private nzFormNoStatusService?: NzFormNoStatusService
) {}
private markForCheckAllList(): void {
@@ -330,6 +340,19 @@ export class NzTransferComponent implements OnInit, OnChanges, OnDestroy {
}
ngOnInit(): void {
+ this.nzFormStatusService?.formStatusChanges
+ .pipe(
+ distinctUntilChanged((pre, cur) => {
+ return pre.status === cur.status && pre.hasFeedback === cur.hasFeedback;
+ }),
+ withLatestFrom(this.nzFormNoStatusService ? this.nzFormNoStatusService.noFormStatus : observableOf(false)),
+ map(([{ status, hasFeedback }, noStatus]) => ({ status: noStatus ? '' : status, hasFeedback })),
+ takeUntil(this.unsubscribe$)
+ )
+ .subscribe(({ status, hasFeedback }) => {
+ this.setStatusStyles(status, hasFeedback);
+ });
+
this.i18n.localeChange.pipe(takeUntil(this.unsubscribe$)).subscribe(() => {
this.locale = this.i18n.getLocaleData('Transfer');
this.markForCheckAllList();
@@ -358,7 +381,7 @@ export class NzTransferComponent implements OnInit, OnChanges, OnDestroy {
this.handleNzSelectedKeys();
}
if (nzStatus) {
- this.setStatusStyles();
+ this.setStatusStyles(this.nzStatus, this.hasFeedback);
}
}
@@ -367,9 +390,12 @@ export class NzTransferComponent implements OnInit, OnChanges, OnDestroy {
this.unsubscribe$.complete();
}
- private setStatusStyles(): void {
+ private setStatusStyles(status: NzValidateStatus, hasFeedback: boolean): void {
+ // set inner status
+ this.hasFeedback = hasFeedback;
+ this.cdr.markForCheck();
// render status if nzStatus is set
- this.statusCls = getStatusClassNames(this.prefixCls, this.nzStatus, this.hasFeedback);
+ this.statusCls = getStatusClassNames(this.prefixCls, status, hasFeedback);
Object.keys(this.statusCls).forEach(status => {
if (this.statusCls[status]) {
this.renderer.addClass(this.elementRef.nativeElement, status);
diff --git a/components/transfer/transfer.spec.ts b/components/transfer/transfer.spec.ts
index 4eb7f014f0..c078711bfa 100644
--- a/components/transfer/transfer.spec.ts
+++ b/components/transfer/transfer.spec.ts
@@ -11,12 +11,14 @@ import {
ViewEncapsulation
} from '@angular/core';
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
+import { FormsModule } from '@angular/forms';
import { By } from '@angular/platform-browser';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';
import { NzStatus } from 'ng-zorro-antd/core/types';
+import { NzFormControlStatusType, NzFormModule } from 'ng-zorro-antd/form';
import { NzIconTestModule } from 'ng-zorro-antd/icon/testing';
import en_US from '../i18n/languages/en_US';
@@ -36,19 +38,21 @@ describe('transfer', () => {
| Test996Component
| NzTestTransferRtlComponent
| NzTestTransferStatusComponent
+ | NzTestTransferInFormComponent
>;
let dl: DebugElement;
let instance: TestTransferComponent;
let pageObject: TransferPageObject;
beforeEach(() => {
injector = TestBed.configureTestingModule({
- imports: [BidiModule, NoopAnimationsModule, NzTransferModule, NzIconTestModule],
+ imports: [BidiModule, NoopAnimationsModule, NzTransferModule, NzIconTestModule, FormsModule, NzFormModule],
declarations: [
TestTransferComponent,
TestTransferCustomRenderComponent,
Test996Component,
NzTestTransferRtlComponent,
- NzTestTransferStatusComponent
+ NzTestTransferStatusComponent,
+ NzTestTransferInFormComponent
]
});
fixture = TestBed.createComponent(TestTransferComponent);
@@ -405,6 +409,35 @@ describe('transfer', () => {
});
});
+ describe('transfer in form', () => {
+ let componentElement: HTMLElement;
+ let testComponent: NzTestTransferInFormComponent;
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(NzTestTransferInFormComponent);
+ componentElement = fixture.debugElement.query(By.directive(NzTransferComponent)).nativeElement;
+ fixture.detectChanges();
+ testComponent = fixture.debugElement.componentInstance;
+ });
+
+ it('should className correct', () => {
+ fixture.detectChanges();
+ expect(componentElement.classList).toContain('ant-transfer-status-error');
+
+ testComponent.status = 'warning';
+ fixture.detectChanges();
+ expect(componentElement.classList).toContain('ant-transfer-status-warning');
+
+ testComponent.status = 'success';
+ fixture.detectChanges();
+ expect(componentElement.classList).toContain('ant-transfer-status-success');
+
+ testComponent.feedback = false;
+ fixture.detectChanges();
+ expect(componentElement.classList).not.toContain('ant-transfer-has-feedback');
+ });
+ });
+
class TransferPageObject {
[key: string]: any;
@@ -648,3 +681,19 @@ export class NzTestTransferRtlComponent {
export class NzTestTransferStatusComponent {
status: NzStatus = 'error';
}
+
+@Component({
+ template: `
+
+ `
+})
+export class NzTestTransferInFormComponent {
+ status: NzFormControlStatusType = 'error';
+ feedback = true;
+}
diff --git a/components/tree-select/tree-select.component.ts b/components/tree-select/tree-select.component.ts
index 13bcd9a2a7..ef36b333d9 100644
--- a/components/tree-select/tree-select.component.ts
+++ b/components/tree-select/tree-select.component.ts
@@ -30,10 +30,11 @@ import {
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { merge, of as observableOf, Subject } from 'rxjs';
-import { filter, takeUntil, tap } from 'rxjs/operators';
+import { distinctUntilChanged, filter, map, takeUntil, tap, withLatestFrom } from 'rxjs/operators';
import { slideMotion } from 'ng-zorro-antd/core/animation';
import { NzConfigKey, NzConfigService, WithConfig } from 'ng-zorro-antd/core/config';
+import { NzFormNoStatusService, NzFormStatusService } from 'ng-zorro-antd/core/form';
import { NzNoAnimationDirective } from 'ng-zorro-antd/core/no-animation';
import { reqAnimFrame } from 'ng-zorro-antd/core/polyfill';
import {
@@ -50,6 +51,7 @@ import {
NgStyleInterface,
NzSizeLDSType,
NzStatus,
+ NzValidateStatus,
OnChangeType,
OnTouchedType
} from 'ng-zorro-antd/core/types';
@@ -180,7 +182,15 @@ const TREE_SELECT_DEFAULT_CLASS = 'ant-select-dropdown ant-select-tree-dropdown'
>
-
+
+
+
+
+
;
@Input() nzNotFoundContent?: string;
- @Input() nzNodes: Array = [];
+ @Input() nzNodes: NzTreeNodeOptions[] | NzTreeNode[] = [];
@Input() nzOpen = false;
@Input() @WithConfig() nzSize: NzSizeLDSType = 'default';
@Input() nzPlaceHolder = '';
@@ -296,7 +307,8 @@ export class NzTreeSelectComponent extends NzTreeBase implements ControlValueAcc
prefixCls: string = 'ant-select';
statusCls: NgClassInterface = {};
- nzHasFeedback: boolean = false;
+ status: NzValidateStatus = '';
+ hasFeedback: boolean = false;
dropdownClassName = TREE_SELECT_DEFAULT_CLASS;
triggerWidth?: number;
@@ -332,7 +344,9 @@ export class NzTreeSelectComponent extends NzTreeBase implements ControlValueAcc
private elementRef: ElementRef,
@Optional() private directionality: Directionality,
private focusMonitor: FocusMonitor,
- @Host() @Optional() public noAnimation?: NzNoAnimationDirective
+ @Host() @Optional() public noAnimation?: NzNoAnimationDirective,
+ @Optional() public nzFormStatusService?: NzFormStatusService,
+ @Optional() private nzFormNoStatusService?: NzFormNoStatusService
) {
super(nzTreeService);
@@ -341,6 +355,19 @@ export class NzTreeSelectComponent extends NzTreeBase implements ControlValueAcc
}
ngOnInit(): void {
+ this.nzFormStatusService?.formStatusChanges
+ .pipe(
+ distinctUntilChanged((pre, cur) => {
+ return pre.status === cur.status && pre.hasFeedback === cur.hasFeedback;
+ }),
+ withLatestFrom(this.nzFormNoStatusService ? this.nzFormNoStatusService.noFormStatus : observableOf(false)),
+ map(([{ status, hasFeedback }, noStatus]) => ({ status: noStatus ? '' : status, hasFeedback })),
+ takeUntil(this.destroy$)
+ )
+ .subscribe(({ status, hasFeedback }) => {
+ this.setStatusStyles(status, hasFeedback);
+ });
+
this.isDestroy = false;
this.subscribeSelectionChange();
@@ -383,9 +410,13 @@ export class NzTreeSelectComponent extends NzTreeBase implements ControlValueAcc
this.closeDropDown();
}
- private setStatusStyles(): void {
+ private setStatusStyles(status: NzValidateStatus, hasFeedback: boolean): void {
+ // set inner status
+ this.status = status;
+ this.hasFeedback = hasFeedback;
+ this.cdr.markForCheck();
// render status if nzStatus is set
- this.statusCls = getStatusClassNames(this.prefixCls, this.nzStatus, this.nzHasFeedback);
+ this.statusCls = getStatusClassNames(this.prefixCls, status, hasFeedback);
Object.keys(this.statusCls).forEach(status => {
if (this.statusCls[status]) {
this.renderer.addClass(this.elementRef.nativeElement, status);
@@ -405,7 +436,7 @@ export class NzTreeSelectComponent extends NzTreeBase implements ControlValueAcc
this.dropdownClassName = className ? `${TREE_SELECT_DEFAULT_CLASS} ${className}` : TREE_SELECT_DEFAULT_CLASS;
}
if (nzStatus) {
- this.setStatusStyles();
+ this.setStatusStyles(this.nzStatus, this.hasFeedback);
}
}
diff --git a/components/tree-select/tree-select.module.ts b/components/tree-select/tree-select.module.ts
index f609c7574a..583652df2a 100644
--- a/components/tree-select/tree-select.module.ts
+++ b/components/tree-select/tree-select.module.ts
@@ -9,6 +9,7 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
+import { NzFormPatchModule } from 'ng-zorro-antd/core/form';
import { NzNoAnimationModule } from 'ng-zorro-antd/core/no-animation';
import { NzOverlayModule } from 'ng-zorro-antd/core/overlay';
import { NzEmptyModule } from 'ng-zorro-antd/empty';
@@ -29,7 +30,8 @@ import { NzTreeSelectComponent } from './tree-select.component';
NzIconModule,
NzEmptyModule,
NzOverlayModule,
- NzNoAnimationModule
+ NzNoAnimationModule,
+ NzFormPatchModule
],
declarations: [NzTreeSelectComponent],
exports: [NzTreeSelectComponent]
diff --git a/components/tree-select/tree-select.spec.ts b/components/tree-select/tree-select.spec.ts
index 96c7cd91ea..bc59822d86 100644
--- a/components/tree-select/tree-select.spec.ts
+++ b/components/tree-select/tree-select.spec.ts
@@ -17,6 +17,7 @@ import {
} from 'ng-zorro-antd/core/testing';
import { NzTreeNode, NzTreeNodeOptions } from 'ng-zorro-antd/core/tree';
import { NzStatus } from 'ng-zorro-antd/core/types';
+import { NzFormControlStatusType, NzFormModule } from 'ng-zorro-antd/form';
import { NzTreeSelectComponent } from './tree-select.component';
import { NzTreeSelectModule } from './tree-select.module';
@@ -29,13 +30,14 @@ describe('tree-select component', () => {
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
- imports: [NzTreeSelectModule, NoopAnimationsModule, FormsModule, ReactiveFormsModule],
+ imports: [NzTreeSelectModule, NoopAnimationsModule, FormsModule, ReactiveFormsModule, NzFormModule],
declarations: [
NzTestTreeSelectBasicComponent,
NzTestTreeSelectCheckableComponent,
NzTestTreeSelectFormComponent,
NzTestTreeSelectCustomizedIconComponent,
- NzTestTreeSelectStatusComponent
+ NzTestTreeSelectStatusComponent,
+ NzTestTreeSelectInFormComponent
],
providers: [
{
@@ -631,6 +633,33 @@ describe('tree-select component', () => {
expect(treeSelect.nativeElement.className).not.toContain('ant-select-status-warning');
});
});
+
+ describe('in form', () => {
+ let fixture: ComponentFixture;
+ let treeSelect!: HTMLElement;
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(NzTestTreeSelectInFormComponent);
+ treeSelect = fixture.debugElement.query(By.directive(NzTreeSelectComponent)).nativeElement;
+ });
+
+ it('should className correct', () => {
+ fixture.detectChanges();
+ expect(treeSelect.classList).toContain('ant-select-status-error');
+
+ fixture.componentInstance.status = 'warning';
+ fixture.detectChanges();
+ expect(treeSelect.classList).toContain('ant-select-status-warning');
+
+ fixture.componentInstance.status = 'success';
+ fixture.detectChanges();
+ expect(treeSelect.classList).toContain('ant-select-status-success');
+
+ fixture.componentInstance.feedback = false;
+ fixture.detectChanges();
+ expect(treeSelect.querySelector('nz-form-item-feedback-icon')).toBeNull();
+ });
+ });
});
@Component({
@@ -926,3 +955,19 @@ export class NzTestTreeSelectStatusComponent {
}
];
}
+
+@Component({
+ template: `
+
+ `
+})
+export class NzTestTreeSelectInFormComponent {
+ status: NzFormControlStatusType = 'error';
+ feedback = true;
+}