本篇教程将以上传头像为例说明如何预览图片并上传给服务器端。
- 首先,我们需要在界面上放置一个选取本地图片的控件。html中的表单输入控件input,type为file可以满足这一需求。而ng-zorro的框架在这一基本控件的基础上封装了一个专门用于上传图片的控件即上传upload。但是文档上提供的这个控件用法默认是选取图片后直接上传的,而我希望选取图片后先显示预览,待用户点击提交后再以整个表单的形式上传给服务器端。通过查询可知input,type为file的控件默认有预览的写法。相当于用户选择了图片后就把图片缓存到一个临时的路径下面,访问这个路径就能读取到图片。
修改teacher-form.component.html,在之前的输入年龄的nz-form-item和提交的nz-form-item中间插入如下代码
<nz-form-item>
<nz-form-label [nzSpan]="3" nzFor="file">头像</nz-form-label>
<nz-form-control [nzSpan]="8">
<nz-upload
nzListType="picture-card"
[nzBeforeUpload]="beforeUpload"
>
<ng-container *ngIf="!avatarUrl">
<i class="upload-icon" nz-icon [nzType]="'plus'"></i>
<div class="ant-upload-text">Upload</div>
</ng-container>
<img *ngIf="avatarUrl" [src]="domSanitizer.bypassSecurityTrustUrl(avatarUrl)" style="width: 100%" />
</nz-upload>
</nz-form-control>
</nz-form-item>
这是在官方提供的upload控件基础上稍作修改,nzListType是指定上传列表的内建样式,这里必须指定为picture-card才能显示出照片墙的样式。nzBeforeUpload则是在图片上传前处理的方法,这个我们会在代码中实现。avatarUrl是选择图片的临时url,这个值一开始为空,那么就显示ng-container,就是加号和upload字符,选择图片后就显示img。这里img的路径src为什么不是直接使用avatarUrl而是要加上domSanitizer.bypassSecurityTrustUrl这个方法呢,这是因为
接着在teacher-form.component.ts中实现beforeUpload这个方法,先添加2个全局变量
avatarUrl: string;
avatarFile: File;
然后给constructor方法添加2个依赖注入的参数变为
constructor(private fb: FormBuilder, private router: Router, private teacherService: TeacherService,
private location: Location, private subjectService: SubjectService, private route: ActivatedRoute,
private msg: NzMessageService, public domSanitizer: DomSanitizer) {}
最后加上beforeUpload的方法
beforeUpload = (file: File) => {
return new Observable((observer: Observer<boolean>) => {
const isJPG = file.type === 'image/jpeg';
if (!isJPG) {
this.msg.error('You can only upload JPG file!');
observer.complete();
return;
}
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isLt2M) {
this.msg.error('Image must smaller than 2MB!');
observer.complete();
return;
}
// check height
this.checkImageDimension(file).then(dimensionRes => {
if (!dimensionRes) {
this.msg.error('Image only 300x300 above');
observer.complete();
return;
}
this.avatarUrl = window.URL.createObjectURL(file);
this.avatarFile = file;
// observer.next(isJPG && isLt2M && dimensionRes);
observer.complete();
});
});
};
private checkImageDimension(file: File): Promise<boolean> {
return new Promise(resolve => {
const img = new Image(); // create image
img.src = window.URL.createObjectURL(file);
img.onload = () => {
const width = img.naturalWidth;
const height = img.naturalHeight;
window.URL.revokeObjectURL(img.src!);
resolve(width === height && width >= 300);
};
});
}
这个方法也主要是参考官方的示例代码写的,不同的地方在于在checkImageDimension完成的回调中把file的预览地址window.URL.createObjectURL(file)赋给了avatarUrl,把file本身赋给了avatarFile。这时我们回过去看teacher-form.component.html中的代码,为什么img控件的src地址不是直接为avatarUrl而是要加上domSanitizer.bypassSecurityTrustUrl呢,这就是因为我们使用的window.URL.createObjectURL。如果不加,那么就会提示sanitizing unsafe URL value的错误,显然这个提示告诉我们直接使用这个url显示图片是不安全的做法(即可能受到XSS攻击)。为了提高安全性,angular提供了DomSanitizer中的一系列方法转化为安全的脚本。这里我们就使用bypassSecurityTrustUrl把图片的临时url转为可信任的url再给img控件显示。
到这里,选择图片和预览已经可以了,接下来就是如何上传了。
- 我们先要给teacher.service.ts中的addTeacher和editTeacher两个方法添加一个上传的参数file,这个允许它可选,所以这两个方法修改如下
editTeacher(
id: string,
name: string,
gender: number,
age: number,
subjectIds: any[],
file: File
): Observable<any> {
const options = { id, name, gender, age, subjectIds };
const formData = new FormData();
formData.append('id', id);
formData.append('name', name);
formData.append('gender', gender.toString());
formData.append('subjectIds', subjectIds.toString());
formData.append('age', age.toString());
if (file != null) {
formData.append('file', file);
}
return this.http.post(`${this.teacherUrl}editTeacher`, formData, {});
}
addTeacher(
name: string,
gender: number,
age: number,
subjectIds: [],
file: File
): Observable<any> {
// const options = { name, gender, age, subjectIds };
const formData = new FormData();
formData.append('name', name);
formData.append('gender', gender.toString());
formData.append('subjectIds', subjectIds.toString());
formData.append('age', age.toString());
if (file != null) {
formData.append('file', file);
}
return this.http.post(`${this.teacherUrl}addTeacher`, formData, {});
}
接着teacher-form.component.ts中相应调用这两个方法的地方也要修改,即修改submitForm方法如下
submitForm(): void {
for (const i in this.teacherForm.controls) {
this.teacherForm.controls[i].markAsDirty();
this.teacherForm.controls[i].updateValueAndValidity();
if (this.teacherForm.controls[i].invalid) {
return;
}
}
if (this.teacherId == null) {
this.teacherService.addTeacher(this.teacherForm.value.name, this.teacherForm.value.gender,
this.teacherForm.value.age, this.teacherForm.value.subjectIds, this.avatarFile)
.subscribe(result => this.location.back());
} else {
this.teacherService.editTeacher(this.teacherId, this.teacherForm.value.name, this.teacherForm.value.gender,
this.teacherForm.value.age, this.teacherForm.value.subjectIds, this.avatarFile)
.subscribe(result => this.location.back());
}
}
其实就是之前我们把选择的图片对象file赋给了avatarFile,这里就用它来提交给服务器。
最后,因为服务器端把提交的图片在teacherDetail的api中返回了,所以显示详情的时候也可以显示出来,就需要修改getTeacherDetail方法如下
getTeacherDetail(id: string): void {
this.teacherService.getTeacherDetail(id)
.subscribe(result => {
const data = result.data.teacher;
const array = [];
if (data.subjectIds != null) {
const subjectIds = data.subjectIds.split(',');
subjectIds.map((item) => {
array.push(Number(item));
});
}
this.teacherForm.setValue({
name: data.name,
gender: data.gender,
age: data.age,
subjectIds: array
});
if (data.imageUrl != null) {
this.avatarUrl = data.imageUrl;
}
});
}
到这里选择图片预览及上传到服务器端的功能就完成了,代码依然可以参考https://github.com/ahuadoreen/studentmanager-cli。