Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions components/upload/demo/max-count.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
order: 13
title:
zh-CN: 限制数量
en-US: Max Count
---

## zh-CN

通过 `nzMaxCount` 限制上传数量。当为 1 时,始终用最新上传的代替当前。

## en-US

Limit files with `nzMaxCount`. Will replace current one when `nzMaxCount` is 1
44 changes: 44 additions & 0 deletions components/upload/demo/max-count.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Component, inject } from '@angular/core';

import { NzButtonModule } from 'ng-zorro-antd/button';
import { NzFlexModule } from 'ng-zorro-antd/flex';
import { NzMessageService } from 'ng-zorro-antd/message';
import { NzUploadModule, type NzUploadChangeParam } from 'ng-zorro-antd/upload';

@Component({
selector: 'nz-demo-upload-max-count',
imports: [NzUploadModule, NzButtonModule, NzFlexModule],
template: `
<div nz-flex nzVertical nzGap="2rem">
<nz-upload
nzAction="https://www.mocky.io/v2/5cc8019d300000980a055e76"
[nzHeaders]="{ authorization: 'authorization-text' }"
[nzMaxCount]="1"
(nzChange)="handleChange($event)"
>
<button nz-button>Upload (Max: 1)</button>
</nz-upload>
<nz-upload
nzAction="https://www.mocky.io/v2/5cc8019d300000980a055e76"
[nzHeaders]="{ authorization: 'authorization-text' }"
[nzMaxCount]="3"
(nzChange)="handleChange($event)"
>
<button nz-button>Upload (Max: 3)</button>
</nz-upload>
</div>
`
})
export class NzDemoUploadMaxCountComponent {
readonly #messageService = inject(NzMessageService);
handleChange(info: NzUploadChangeParam): void {
if (info.file.status !== 'uploading') {
console.log(info.file, info.fileList);
}
if (info.file.status === 'done') {
this.#messageService.success(`${info.file.name} file uploaded successfully`);
} else if (info.file.status === 'error') {
this.#messageService.error(`${info.file.name} file upload failed.`);
}
}
}
1 change: 1 addition & 0 deletions components/upload/doc/index.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Uploading is publishing information (web pages, text, pictures, video, etc.) to
| `[nzDisabled]` | disable upload button | `boolean` | `false` |
| `[nzFileList]` | List of files, two-way data-binding | `NzUploadFile[]` | - |
| `[nzLimit]` | limit single upload count when `nzMultiple` has opened. `0` unlimited | `number` | `0` |
| `[nzMaxCount]` | Limit the file displayed in uploaded files list. Will replace current one when maxCount is 1 unlimited | `number\|undefined` | `undefined` |
| `[nzSize]` | limit file size (KB). `0` unlimited | `number` | `0` |
| `[nzFileType]` | limit file type, e.g: `image/png,image/jpeg,image/gif,image/bmp` | `string` | - |
| `[nzFilter]` | Custom filter when choose file | `UploadFilter[]` | - |
Expand Down
1 change: 1 addition & 0 deletions components/upload/doc/index.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ description: 文件选择上传和拖拽上传控件。
| `[nzDisabled]` | 是否禁用 | `boolean` | `false` |
| `[nzFileList]` | 文件列表,双向绑定 | `NzUploadFile[]` | - |
| `[nzLimit]` | 限制单次最多上传数量,`nzMultiple` 打开时有效;`0` 表示不限 | `number` | `0` |
| `[nzMaxCount]` | 限制上传数量。当为 1 时,始终用最新上传的文件代替当前文件 | `number\|undefined` | `undefined` |
| `[nzSize]` | 限制文件大小,单位:KB;`0` 表示不限 | `number` | `0` |
| `[nzFileType]` | 限制文件类型,例如:`image/png,image/jpeg,image/gif,image/bmp` | `string` | - |
| `[nzFilter]` | 自定义过滤器 | `UploadFilter[]` | - |
Expand Down
27 changes: 24 additions & 3 deletions components/upload/upload.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
DOCUMENT,
EventEmitter,
inject,
input,
Input,
numberAttribute,
OnChanges,
Expand Down Expand Up @@ -124,6 +125,8 @@ export class NzUploadComponent implements OnInit, AfterViewInit, OnChanges {
@Input() nzIconRender: NzIconRenderTemplate | null = null;
@Input() nzFileListRender: TemplateRef<{ $implicit: NzUploadFile[] }> | null = null;

readonly nzMaxCount = input<number | undefined>(undefined);

@Output() readonly nzChange: EventEmitter<NzUploadChangeParam> = new EventEmitter<NzUploadChangeParam>();
@Output() readonly nzFileListChange: EventEmitter<NzUploadFile[]> = new EventEmitter<NzUploadFile[]>();

Expand Down Expand Up @@ -211,12 +214,17 @@ export class NzUploadComponent implements OnInit, AfterViewInit, OnChanges {
}

private onStart = (file: NzUploadFile): void => {
const maxCount = this.nzMaxCount();
if (!this.nzFileList) {
this.nzFileList = [];
}
const targetItem = this.fileToObject(file);
targetItem.status = 'uploading';
this.nzFileList = this.nzFileList.concat(targetItem);
if (maxCount === 1) {
this.nzFileList = [targetItem];
} else if (!maxCount || maxCount <= 0 || this.nzFileList.length < maxCount) {
this.nzFileList = [...this.nzFileList, targetItem];
}
this.nzFileListChange.emit(this.nzFileList);
this.nzChange.emit({ file: targetItem, fileList: this.nzFileList, type: 'start' });
this.detectChangesList();
Expand All @@ -225,10 +233,15 @@ export class NzUploadComponent implements OnInit, AfterViewInit, OnChanges {
private onProgress = (e: { percent: number }, file: NzUploadFile): void => {
const fileList = this.nzFileList;
const targetItem = this.getFileItem(file, fileList);

if (!targetItem) {
return;
}

targetItem.percent = e.percent;
this.nzChange.emit({
event: e,
file: { ...targetItem },
file: targetItem,
fileList: this.nzFileList,
type: 'progress'
});
Expand All @@ -238,10 +251,13 @@ export class NzUploadComponent implements OnInit, AfterViewInit, OnChanges {
private onSuccess = (res: {}, file: NzUploadFile): void => {
const fileList = this.nzFileList;
const targetItem = this.getFileItem(file, fileList);
if (!targetItem) {
return;
}
targetItem.status = 'done';
targetItem.response = res;
this.nzChange.emit({
file: { ...targetItem },
file: targetItem,
fileList,
type: 'success'
});
Expand All @@ -251,6 +267,11 @@ export class NzUploadComponent implements OnInit, AfterViewInit, OnChanges {
private onError = (err: {}, file: NzUploadFile): void => {
const fileList = this.nzFileList;
const targetItem = this.getFileItem(file, fileList);

if (!targetItem) {
return;
}

targetItem.error = err;
targetItem.status = 'error';
this.nzChange.emit({
Expand Down
150 changes: 150 additions & 0 deletions components/upload/upload.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,154 @@ describe('upload', () => {
expect(el != null).toBe(true);
expect((el.nativeElement as HTMLElement).textContent).toBe('asdf');
});

describe('[nzMaxCount]', () => {
it('should replace existing file when nzMaxCount is 1', () => {
instance.nzMaxCount = 1;
instance.nzFileList = [
{
uid: 1,
name: 'existing.png',
status: 'done'
} as NzSafeAny
];
fixture.detectChanges();

expect(instance.nzFileList.length).toBe(1);
expect(instance.nzFileList[0].name).toBe('existing.png');

// Upload a new file
pageObject.postSmall();
const req = httpMock.expectOne(instance.nzAction as string);
req.flush({});

// Should replace the existing file
expect(instance.nzFileList.length).toBe(1);
expect(instance.nzFileList[0].name).toBe('test.png');
});

it('should limit files when nzMaxCount is greater than 1', () => {
instance.nzMaxCount = 2;
instance.nzFileList = [];
fixture.detectChanges();

// Upload first file
pageObject.postSmall();
let req = httpMock.expectOne(instance.nzAction as string);
req.flush({});
expect(instance.nzFileList.length).toBe(1);

// Upload second file
pageObject.postFile([new File(['content'], 'second.png', { type: 'image/png' })]);
req = httpMock.expectOne(instance.nzAction as string);
req.flush({});
expect(instance.nzFileList.length).toBe(2);

// Upload third file - upload happens but file is not added to list due to max count
pageObject.postFile([new File(['content'], 'third.png', { type: 'image/png' })]);
req = httpMock.expectOne(instance.nzAction as string);
req.flush({});
// File list should still be 2 because max count prevents adding more files
expect(instance.nzFileList.length).toBe(2);
});

it('should allow unlimited files when nzMaxCount is undefined', () => {
instance.nzMaxCount = undefined;
instance.nzFileList = [];
fixture.detectChanges();

// Upload multiple files
for (let i = 0; i < 5; i++) {
pageObject.postFile([new File(['content'], `file${i}.png`, { type: 'image/png' })]);
const req = httpMock.expectOne(instance.nzAction as string);
req.flush({});
}

expect(instance.nzFileList.length).toBe(5);
});

it('should only accept positive values for nzMaxCount', () => {
// Test with positive value (should limit)
instance.nzMaxCount = 2;
instance.nzFileList = [];
fixture.detectChanges();

// Upload first file
pageObject.postSmall();
let req = httpMock.expectOne(instance.nzAction as string);
req.flush({});
expect(instance.nzFileList.length).toBe(1);

// Upload second file
pageObject.postFile([new File(['content'], 'second.png', { type: 'image/png' })]);
req = httpMock.expectOne(instance.nzAction as string);
req.flush({});
expect(instance.nzFileList.length).toBe(2);

// Upload third file - should not be added due to limit
pageObject.postFile([new File(['content'], 'third.png', { type: 'image/png' })]);
req = httpMock.expectOne(instance.nzAction as string);
req.flush({});
expect(instance.nzFileList.length).toBe(2); // Still 2, not 3
});

it('should handle edge cases for nzMaxCount', () => {
// Test with 0 (should behave like undefined - no limit)
instance.nzMaxCount = 0;
instance.nzFileList = [];
fixture.detectChanges();

pageObject.postSmall();
let req = httpMock.expectOne(instance.nzAction as string);
req.flush({});
expect(instance.nzFileList.length).toBe(1);

// Add another file to confirm no limit with 0
pageObject.postFile([new File(['content'], 'second.png', { type: 'image/png' })]);
req = httpMock.expectOne(instance.nzAction as string);
req.flush({});
expect(instance.nzFileList.length).toBe(2);

// Test with negative value (should behave like undefined - no limit)
instance.nzMaxCount = -1;
fixture.detectChanges();

pageObject.postFile([new File(['content'], 'third.png', { type: 'image/png' })]);
req = httpMock.expectOne(instance.nzAction as string);
req.flush({});
expect(instance.nzFileList.length).toBe(3);
});

it('should work with existing nzLimit when both are set', () => {
instance.nzMaxCount = 3;
instance.nzLimit = 2; // This should be overridden by nzMaxCount logic
instance.nzMultiple = true;
fixture.detectChanges();

// Upload files one by one to test the maxCount behavior
pageObject.postSmall();
let req = httpMock.expectOne(instance.nzAction as string);
req.flush({});
expect(instance.nzFileList.length).toBe(1);

pageObject.postFile([new File(['content'], 'second.png', { type: 'image/png' })]);
req = httpMock.expectOne(instance.nzAction as string);
req.flush({});
expect(instance.nzFileList.length).toBe(2);

pageObject.postFile([new File(['content'], 'third.png', { type: 'image/png' })]);
req = httpMock.expectOne(instance.nzAction as string);
req.flush({});
expect(instance.nzFileList.length).toBe(3);

// Fourth file - upload happens but file is not added to list due to max count
pageObject.postFile([new File(['content'], 'fourth.png', { type: 'image/png' })]);
req = httpMock.expectOne(instance.nzAction as string);
req.flush({});
// File list should still be 3 because max count prevents adding more files
expect(instance.nzFileList.length).toBe(3);
});
});
});

describe('CORS', () => {
Expand Down Expand Up @@ -1492,6 +1640,7 @@ describe('upload', () => {
[nzTransformFile]="nzTransformFile"
[nzIconRender]="nzIconRender"
[nzFileListRender]="nzFileListRender"
[nzMaxCount]="nzMaxCount"
(nzFileListChange)="nzFileListChange($event)"
(nzChange)="nzChange($event)"
>
Expand Down Expand Up @@ -1542,6 +1691,7 @@ class TestUploadComponent {
nzTransformFile!: (file: NzUploadFile) => NzUploadTransformFileType;
nzIconRender: NzIconRenderTemplate | null = null;
nzFileListRender: TemplateRef<{ $implicit: NzUploadFile[] }> | null = null;
nzMaxCount: number | undefined = undefined;
_onPreview = false;
onPreview = (): void => {
this._onPreview = true;
Expand Down