angular8教程(7)-图片预览并上传

本篇教程将以上传头像为例说明如何预览图片并上传给服务器端。

  1. 首先,我们需要在界面上放置一个选取本地图片的控件。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控件显示。
到这里,选择图片和预览已经可以了,接下来就是如何上传了。

  1. 我们先要给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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343