Twitter LogoFacebook Logo
Angular Project 1: Building a Social Media Site Part 7: Create Post Component
Hello, welcome to another tutorial of the Complete Angular Course. In this video, we’ll begin building the page for the post feed.
By: King

Hello, welcome to another tutorial of the Complete Angular Course. We’ll begin building the page for the post feed. 

We’ll have a page to display and create posts with a button at the bottom right corner of the window. When we click on the button, a dialog will pop up for us to create a post. 

If we select an image, a preview will appear.

Image from Codeible.com

In addition to the Angular Material UI components that were used so far, I’ll be using the Dialog and Icon components.


If you have not already, subscribe to the channel to get updates on the next video for this project.

Adding the Angular Material Dialog and Icon Modules

To begin, open the project from where we left off in the last session and log out. 


Go to the App Module file and add the Material Dialog and Icon Modules to the project. 

...
import { MatDialogModule } from '@angular/material/dialog';
import { MatIconModule } from '@angular/material/icon';


@NgModule({
  declarations: [
    AppComponent,
    ...
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    ...
    MatDialogModule,
    MatIconModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { 
  ..
}

Creating the PostFeed Component

Save the project and open the terminal to create the PostFeed component. 


Run the ng generate component command and place it in the directory call pages. 

ng generate component pages/PostFeed

Go to the app routing module file and add a route. Use postfeed for the path and the PostFeed component for the component

const routes: Routes = [
  {path: "", component: HomeComponent},
  {path: "emailVerification", component: EmailVerificationComponent},

  {path: "postfeed", component: PostFeedComponent},

  {path: "**", component: HomeComponent}
];

Go to the App Component TypeScript file and locate the getUserProfile() function. 


Inside the onUpdate() callback function, use the if statement to check if the user has a profile. 

If they have a profile, navigate to the post feed page. Grab the router object and then call the navigate() method.

getUserProfile(){
    this.firestore.listenToDocument(
      {
        name: "Getting Document",
        path: ["Users", this.auth.getAuth().currentUser.uid],
        onUpdate: (result) => {

          this.userDocument = <UserDocument>result.data();
          this.userHasProfile = result.exists

          if(this.userHasProfile) {
            this.router.navigate(["postfeed"]);
          }

        }
      }
    );
  }

If we take a look at the run down of our app, when a visitor visits our web site, 


1) It will check if they are logged in. If not, nothing will happen. 

2. If they are, it’ll check if their account is verified.  If it is, it’ll check if the user has a profile. 

3. If they have one, it’ll go to the Post Feed page.

If we run the app, nothing will happen, since we are logged out. If we log in, we’ll be directed to the post feed page.

Adding the create post button

Go to the Post Feed component HTML page. 


Remove the default code and add a button.  Use the material add icon for the content, and apply the mat-fab style from Angular Material. Lastly, set the color to warn and call it post-button. 

<button mat-fab color="warn" id="post-button">
   <mat-icon>add</mat-icon>
</button>

Go to the Post Feed CSS file and add the selector for the post-button. The button will have a fixed position, at the bottom right corner, 2em away.

#post-button {
    position: fixed;
    bottom: 2em;
    right: 2em;
}

If we go to our app, the button will appear at the bottom right corner.

Creating the Create Post Component

Let’s add the dialog to create a post. Go to the Post-Feed HTML page and attach a click event to the button. Then go to the Post Feed TypeScript file and define the function.

Post Feed HTML

<button mat-fab color="warn" id="post-button"
   (click)="onCreatePostClick()"
>
   <mat-icon>add</mat-icon>
</button>

Post Feed TypeScript

onCreatePostClick(){

}

Import MatDialog from Angular Material and then inject it inside the constructor. 

import { MatDialog } from '@angular/material/dialog';

constructor(private dialog: MatDialog) { }

Similar to the Bottom Sheet, we can use the Material Dialog object to open a dialog. 


In the createPostClick function, grab the dialog object and call the open() method. For the parameter, we need to pass in the component that we want to use for the dialog content. Since we do not have one created, we cannot pass in anything in yet.

onCreatePostClick(){
    this.dialog.open();
}

In the terminal, create another component call CreatePost and place it in the tools directory. 

ng generate component tools/CreatePost

Once the component is created, pass the component inside the open() method.

onCreatePostClick(){
    this.dialog.open(CreatePostComponent);
}

If we go to the app and click on the add post button, a dialog will show up.

Image from Codeible.com

Now let’s add the content for the component.

Adding the content to the Create Post Component

Go to the create post component HTML page. 


Remove the default code and add the card component. 

Inside the card, add the header by using the mat-card-header element. Then add the title using the mat-card-title element.

<mat-card>
    <mat-card-header>
        <mat-card-title>Create Post</mat-card-title>
    </mat-card-header>
</mat-card>

For the content, add a div element underneath the header element and place a textatrea inside. 

<mat-card>
    <mat-card-header>
        <mat-card-title>Create Post</mat-card-title>
    </mat-card-header>
    <div>
        <textarea  placeholder="Type Message..."></textarea>
    </div>
</mat-card>

Lastly, add the action bar. Use the mat-card-actions element and align the items to the right by using the align attribute and setting it to end.

Inside the action bar, add 2 buttons. 

For the first button, apply the mat-button style and set the color to warn. For the content, add a label element and set the for attribute to photo-upload. 

Inside the label add an input element and a material insert photo icon. Call the input photo-upload so the label will use it as a reference, then set the type of the input to file so it will open a dialog to select files and have it accept images only. 

The label will allow us to replace the default file input element with the icon.

For the second button, put Post for the content, then apply the mat-flat-button style and set the color to warn.

<mat-card>
    <mat-card-header>
        <mat-card-title>Create Post</mat-card-title>
    </mat-card-header>
    <div>
        <textarea  placeholder="Type Message..."></textarea>
    </div>

    <mat-card-actions align="end">

        <button mat-button color="warn">
            <label for="photo-upload">
                <input #photoSelector 
                id="photo-upload" type="file" accept="image/*"/>
                <mat-icon>insert_photo</mat-icon>
            </label>
        </button>

        <button mat-flat-button color="warn">Post</button>

    </mat-card-actions>

</mat-card>

If we go the app and open the dialog, we’re almost there. We just need to hide the input element and set the styles for the textarea.

Image from Codeible.com

Go to the Create Post component CSS file and add the selectors for photo-upload and textarea. 

#photo-upload {

}

textarea {

}

For photo-upload, set the display to none so it will not be visible.


For the textarea, set the font-size to 1.5em, padding to 1em, resize to none so it cannot be resized, width to 250 pixels, min-height to 200 pixels, and then remove the border and outline. 

#photo-upload {
    display: none;
}

textarea {
    font-size: 1.5em;
    padding: 1em;
    resize: none;
    width: 250px;
    min-height: 200px;
    border: none;
    outline: none;
}

If we go to our app again, and open the dialog, it will look closer to how we want it. If we click on the icon button, it will open a dialog to choose our image. 

Image from Codeible.com

Adding the image preview section

The next step is to add the preview section so the chosen image will be displayed. 


Go to the create-post HTML page. Underneath the content div, add another div and call it post-preview. Inside the div, add an img element and call it post-preview-image.

<mat-card>
    <mat-card-header>
        <mat-card-title>Create Post</mat-card-title>
    </mat-card-header>
    <div>
        <textarea  placeholder="Type Message..."></textarea>
    </div>

    <div id="post-preview">
        <img id="post-preview-image"/>
    </div>

    <mat-card-actions align="end">

        <button mat-button color="warn">
            <label for="photo-upload">
                <input #photoSelector 
                id="photo-upload" type="file" accept="image/*"/>
                <mat-icon>insert_photo</mat-icon>
            </label>
        </button>

        <button mat-flat-button color="warn">Post</button>

    </mat-card-actions>

</mat-card>

Go to the create-post CSS file, and add the selectors for post-preview and post-preview-image.

For post-preview, set text-align to right and give it a margin-top of 1em. 

For post-preview-image, set object-fit to cover and object-position to center. This will center the image and crop the image to fit the container. 

Set the width to 150 pixel and height to 100 pixels to get the rectangular shape.

 Lastly, set the border-radius to 1em to get the rounded edges and the border to 1px dashed gray.

#post-preview {
    text-align: right;
    margin-top: 1em;
}

#post-preview-image {
    object-fit: cover;
    object-position: center;
    width: 150px;
    height: 100px;
    border-radius: 1em;
    border: 1px dashed gray;
}

Go to the create-post HTML page. 


Locate the input for the post image, then give it a template reference and attach a change event to it. For the parameter pass in the input reference.

<button mat-button color="warn">
    <label for="photo-upload">
         <input #photoSelector (change)="onPhotoSelected(photoSelector)"
                id="photo-upload" type="file" accept="image/*"/>
         <mat-icon>insert_photo</mat-icon>
    </label>
</button>

Go to the Create Post TypeScript file and define the function for the change event. 


Declare a file  variable call selectedImageFile to represent the current selected image file.

onPhotoSelected(photoSelector: HTMLInputElement) {

}

selectedImageFile: File;

Inside the onPhotoSelected function, get the selected file by taking the input and accessing the files property. 


The files property returns an array of selected images so we need to use the square brackets to get the first image selected. Then pass it into the selectedImageFile variable.

onPhotoSelected(photoSelector: HTMLInputElement) {

  this.selectedImageFile = photoSelector.files[0];

}

Once we have the selected image file, we need to convert the file into a readable string for the preview image container.

To do this, create a FileReader object. Then grab the FileReader object and call the readAsDataURL() method. 

For the perameter, pass in the selected image file. Grab the FileReader object again and attach the loadend event listener to it. 

Inside the callback function, get the result of the converted data from the FileReader object and pass it into a variable. 

onPhotoSelected(photoSelector: HTMLInputElement) {

  this.selectedImageFile = photoSelector.files[0];

  let fileReader = new FileReader();
  fileReader.readAsDataURL(this.selectedImageFile);
  fileReader.addEventListener(
      "loadend",
      ev => {
        let readableString = fileReader.result.toString();
      }
    );
}

If we go back to the HTML page, the preview image container element is an image element call post-preview-image. 


To get the reference for it, grab the document object and call the getElementById() function. Then pass in the id of the container. 

Once we have the image element reference, set the src property to the readable string data.

onPhotoSelected(photoSelector: HTMLInputElement) {

  this.selectedImageFile = photoSelector.files[0];

  let fileReader = new FileReader();
  fileReader.readAsDataURL(this.selectedImageFile);
  fileReader.addEventListener(
      "loadend",
      ev => {
        let readableString = fileReader.result.toString();
        let postPreviewImage = <HTMLImageElement>document.getElementById("post-preview-image");
        postPreviewImage.src = readableString;
      }
   );
}

To stop this code from crashing because no image is selected, add an if statement above the FileReader object and return out of the method when selectedImageFile is null.

onPhotoSelected(photoSelector: HTMLInputElement) {

  this.selectedImageFile = photoSelector.files[0];

  if(!this.selectedImageFile) return;  

  let fileReader = new FileReader();
  ...
}

Lastly, go to the create-post HTML page and locate the post-preview div. We only want to show the preview if there is a selected image file. 

<mat-card>
    <mat-card-header>
        <mat-card-title>Create Post</mat-card-title>
    </mat-card-header>
    <div>
        <textarea  placeholder="Type Message..."></textarea>
    </div>

    <div *ngIf="selectedImageFile" 
        id="post-preview">
        <img id="post-preview-image"/>
    </div>

    <mat-card-actions align="end">

        <button mat-button color="warn">
            <label for="photo-upload">
                <input #photoSelector 
                id="photo-upload" type="file" accept="image/*"/>
                <mat-icon>insert_photo</mat-icon>
            </label>
        </button>

        <button mat-flat-button color="warn">Post</button>

    </mat-card-actions>

</mat-card>

If we go to the app and open the dialog, the preview does not show. 

If we selected an image, the preview would appear. If we cancel the selection, our code will not crash, since we handled that in our code.

That is all for this video. If you find this video helpful, please like, share, and subscribe to support the channel. In the next video, we’ll take the data from the component and upload the files to our database so we can retrieve it later. If you have questions, leave a comment. 

See you in the next video.


Sign In