1

I have a news article component which as a template, has four different images attached to it: Header, Avatar, and Featured Image and Advertising Banner. I am writing in Angular. The images are posted to a files endpoint (which is the files directory) and once I upload them, the four images need to be defined as key values within the news article object: 'header_image', 'author_avatar', 'featured_image' and 'advertising_banner'.

I pass the image file and key directly into the upload function like this in order to ensure I get the right kv from the image upload. When console logged, the data blob for the image logs and so does the key correctly, so they are defined in some way:

.component.html

<input type="file" name="header_image" (change)="fileProgress($event)"
        [(ngModel)]="header_image" accept=".jpg,.svg,.png,.jpeg">
<button class="update" (click)="uploadImage('header_image', header_image)">Upload</button>

.component.ts

uploadImage(key: string, imageFile) {
    if (null) {
      return alert('Please choose an image first')
    }
    imageFile = this.fileData;
    let reader = new FileReader();
    reader.readAsDataURL(imageFile);
    reader.onload = () => {
      let data = reader.result;
      this.configService.uploadImage({
        title: imageFile.name,
        name: imageFile.name,
        type: imageFile.type,
        size: imageFile.size, data
      }).subscribe(response => {
        imageFile = response.data;
        console.log(key, imageFile)//key values print exactly as expected
      });
    }
  };

article.ts

import { Image } from "../settings/image";
import { Feed } from "./feed";

export class Article {
    id: number;
    feed_type: {
        data: Feed;
    }
    title: string;
    description: string;
    text_content: string;
    author_name: string;
    author_avatar: {
        data: Image; //how do you post this image to files?
    }
    video_url: string;
    header_image: Image; //and this image to files?
    featured_image: Image; //and this image to files?
    twitter: string;
    facebook: string;
    instagram: string;
    advertising_banner: {
        data: Image; //and also this image to files at the time when createArticle() is clicked?
    }
    ad_link: string;
    likes: number;
    shares: number;
    views: number;
}

image.ts

export interface Image {
    id: number;
    date_uploaded: Date;
    url: string;
    name: string;
    title: string;
}

The user journey when creating an article is as follows:

  • user writes text
  • user picks images and uploads them
  • user publishes the article

Once the images have been uploaded via the uploadImage() function, there is a final create article button which triggers this:

createArticle(article) {
    this.articleService.postArticle(article)
      .subscribe(
        response => {
          this.startLoading();
          if (response) {
            this.article = response.data;
            this.stopLoading();
          } else {
            this.stopLoading();
          }
        }
      )
}

This then posts the article object to the articles array on the server, but the images are not going up with it. I have come to SO to see if anyone has created something similar to this before and has a trick they could share to ensure that once the images have been uploaded, I can then model them to the individual image keys within the article object. Then send the whole object up as a new news article object.

Apex
  • 227
  • 4
  • 15

1 Answers1

0

Ok, after some time away from the laptop, I have figured out how to do it. Principally, the concept was correct to upload the image first then set that image as a value for the eventual article post request.

I now have the article posting correctly using 4 different images which are now defined individually and I will explain below how I did it, should anyone need this in the future.

The first thing I needed to do was to define the selected images as types using the Image Class - this then assigns the image as an object and not a fake path string:

selectedAvatarImage: Image;
selectedFeaturedImage: Image;
selectedAdvertImage: Image;
selectedHeaderImage: Image;

I then needed to create a new object which will eventually use the above properties and the existing key values for the article object. These need defining as types also, to ensure that we know what we are trying to send up:

title: string;
sub_title: string;
description: string;
text_content: string;
author_name: string;
twitter: string;
facebook: string;
instagram: string;
video_url: string;
ad_link: string;

When selecting an image we need to upload it, but also ensure that it gets set to the right selected image. So with there being 4, a little switch comes in handy on the response of the data after the upload:

uploadImage Function

uploadImage(key: string, imageFile) {
    if (null) {
      return alert('Please choose an image first')
    }
    imageFile = this.fileData;
    let reader = new FileReader();
    reader.readAsDataURL(imageFile);
    reader.onload = () => {
      let data = reader.result;
      this.configService.uploadImage({
        title: imageFile.name,
        name: imageFile.name,
        type: imageFile.type,
        size: imageFile.size, data
      }).subscribe(response => {
        imageFile = response.data;
        switch (key) {
          case 'author_avatar': this.selectedAvatarImage = response.data; console.log('avatar',this.selectedAvatarImage);
            break;
          case 'featured_image': this.selectedFeaturedImage = response.data; console.log('feature',this.selectedFeaturedImage);
            break;
          case 'advertising_banner': this.selectedAdvertImage = response.data; console.log('advert',this.selectedAdvertImage);
            break;
          case 'header_image': this.selectedHeaderImage = response.data; console.log('header',this.selectedHeaderImage);
        }
      });
    }
  };

this successfully identifies which image has been uploaded.

then in the CreateFile function as so:

createArticle() {
    let newArticle = {
      feed_type: this.selectedFeed,//image one
      title: this.title,
      sub_title: this.sub_title,
      description: this.description,
      text_content: this.text_content,
      author_name: this.author_name,
      author_avatar: this.selectedAvatarImage,//image two
      video_url: this.video_url,
      header_image: this.selectedHeaderImage,//image three
      featured_image: this.selectedFeaturedImage,//image four
      twitter: this.twitter,
      facebook: this.facebook,
      instagram: this.instagram,
      advertising_banner: this.selectedAdvertImage,
      ad_link: this.ad_link,
    }

    this.articleService.postArticle(newArticle)
      .subscribe(
        this.startLoading();
        response => {
          if (response) {
            this.article = response.data;
            this.stopLoading();
          } else {
            this.stopLoading();
          }
        }
      )
  }

in the component .html

I needed to remove [(ngModel)] from the file inputs to ensure that I didn't get the same errors this poor soul was experiencing: Link

<input 
type="file" 
name="header_image" 
(change)="fileProgress($event)" 
accept=".jpg,.svg,.png,.jpeg">
<button class="update" (click)="uploadImage('header_image', selectedHeaderImage)">Upload</button>

This then posts all the right stuff to all the right places.

As a note, I think the major hold up for me was trying to think about elegance rather than the result. I appreciate that this process is quite lengthy and I'm sure that there will be a cleaner more dynamic way to do this moving forward. But for what is required for the feature, this works and that is what has been most important considering delivering.

I hope this helps someone in the future.

Apex
  • 227
  • 4
  • 15