The main objective in this assignment is to make an incremental update to the solution that you created for your Assignment 4 (ie: the βBlogβ app).Β For this assignment, we will publish a back-end API to manage the blog posts in your existing MongoDB Atlas account as well as wire up our application to use a single service to manage the data.Β We will also enable users to view more than a single blog post by clicking on it from the main βblogβ page.Β A sample solution is provided here: https://limitlessβforestβ06256.herokuapp.com/
NOTE: If you are unable to start this assignment because there was an issue with your Assignment 4, please email your professor for a fresh (working) copy.
Specification:
Step 1 β The Database
Itβs time that we move our application data out of a .json file and onto MongoDB Atlas.Β Fortunately, everyone should have a working database instance from back in Assignment 1.Β For this step, we simply need to remove all unused databases, add a new βblogβ database with a βpostsβ collection, and populate it with data.
Β
Removing Unused Databases
Β
Log in to your MongoDB Atlas account and locate your βsample_suppliesβ database.Β It can be found by clicking the βcollectionsβ button for one of your clusters (if you have multiple):
Β
Β
Β
Once you have located it, you should see a number of other databases, ie: βsample_airbnbβ, βsample_analyticsβ, etc, etc.):
Β
Β
Β
Since we will not be using any of these other βsampleβ datasets this semester, you can go ahead and drop them to save space. Β This can be done by hovering over each database and clicking the βgarbage canβ icon:
Β
Β
Β
NOTE: Be sure not to drop βsample_suppliesβ as this will wipe out your βsalesβ data and your earlier assignments will cease to function.
Β
Once this is complete, you should be left with a single βsample_suppliesβ database in the list:
Β
Β
Β
Adding the βblogβ Database
Β
We can now go ahead and add in our new βblogβ database by clicking the β+ Create Databaseβ button.Β Go ahead and enter βblogβ for the database name and βpostsβ for the collection name:
Β
Β
Β
With this complete, we should now have two databases in our cluster: βblogβ and βsample_suppliesβ.
Β
Inserting Sample Posts
Β
Next, to ensure that our database has some content to work with, we must manually insert the same collection of βpostsβ from assignment 4, however without the β_idβ attributes.Β To save you the trouble of manipulating the .json file yourself, we have provided a clean version here:
Β
https://ict.senecacollege.ca/~patrick.crawford/shared/winterβ2020/web422/A5/blogData.json
Β
Note: Keep that page open in your browser for now, as we will have to select all the text and copy it into our new βpostsβ collection.
Β
To initiate this process, first click on your βpostsβ collection.Β You should see that it has no documents yet.Β To insert documents manually, click on the βinsert documentβ button towards the right edge of the screen:
Β
Β
Β
This should spawn a modal window titled: βInsert to Collectionβ.
Β
Before we can copy & paste all of our blogData (from the link above), we first have to switch the βviewβ.Β This can be achieved by toggling the button next to the text βVIEWβ in the modal window.Β This should change your view to look like this (enabling us to paste the full βblogDataβ array):
Β
Β
Β
Finally, delete lines 4 β 8 in the text box (effectively removing the suggested document) and proceed to copy the full content of the blogData.json file (from above) and paste it in to the window beneath the comment
Β
Β
Β
With this done, we can now click βinsertβ to add the data!
Β
The Connection String
Β
The connection string for this database should be identical to your previous connection string, except with one change:
Β
The text βsample_suppliesβ should be replaced with βblogβ, for example, if your connection string looks like the below example:
Β
mongodb+srv://yourUserName:yourPassword@cluster0-
3abc.mongodb.net/sample_supplies?retryWrites=true&w=majorityβ
Β
it should be changed to:
Β
mongodb+srv://yourUserName:[email protected]/blog?retryWrites=true&w=majorityβ Be sure to copy this value, as we will need it in the next step: βConfiguring / Publishing the Blog APIβ
Β
Β
Step 2: Configuring / Publishing the Blog API
For this next step, we must publish an API (similar to what was done in assignment 1).Β However for this assignment, rather than creating the API from scratch we can use a pre-configured βblogAPIβ.Β You can download a copy of it here:
Β
https://ict.senecacollege.ca/~patrick.crawford/shared/winterβ2020/web422/A5/blogβAPI.zip
Β
Once you have downloaded and extracted the above file, open the folder in Visual Studio Code and execute the command βnpm installβ to fetch the dependencies.
Β
Finally, before you can test the API locally, you must update line 1 of βserver.jsβ to include your new MongoDB Connection String (obtained above) for the βblogβ database, ie:
Β
mongodb+srv://yourUserName:[email protected]/blog?retryWrites=true&w=majorityβ
Β
Once you have saved the changes, start the server with the command βnode server.jsβ.Β With the server running, access the route:
Β
http://localhost:8080/api/posts?page=1&perPage=10
Β
You should now see your data!
Β
Please feel free to play around with the API at this point, you will find that you can access posts by id, as well as filter posts by category (ie: http://localhost:8080/api/posts?page=1&perPage=10&category=Adventure)Β or tag (ie: http://localhost:8080/api/posts?page=1&perPage=10&tag=lovedit) .
Β
Pushing to Heroku
Β
Now that our API is correctly configured, please push it to Heroku and save the link, as we will be using it throughout this assignment.
Β
Β
Step 3: Adding a βPostServiceβ to our App
Before we can start working on the Blog App for Assignment 5, we must first make a copy of our completed Assignment 4 App (once again: If you are unable to start this assignment because there was an issue with your Assignment 4, please email your professor for a fresh (working) copy).
Β
Open the folder containing the copy of Assignment 4 (now Assignment 5) in Visual Studio code and execute the βngβ command in the terminal to create a service with the class PostService (defined in post.service.ts).
Β
This service will be responsible for working with our blog API (now on Heroku) using the HttpClient.
Therefore, we must make a few updates to our existing code, ie:
- In ts remove the _id property (the data in MongoDB does not contain one)
- Import and add the βHttpClientModuleβ to the βimportsβ array in app.module.ts
Β
With the above configuration complete, we can now implement the following functions within our PostService:
Β
NOTE: Set a constant variable called βperPageβ at the top of the file and set its value to 6.Β This will be our maximum results βperPageβ
Β
getPosts(page, tag, category): Observable<BlogPost[]>
Β
This method will use HttpClient to return all of the posts available in the blogAPI for a specific page using the path: /api/posts?page=page&perPage=perPage where page is the first parameter to the function and perPage is the constant defined (above).
Β
Additionally, if the βtagβ parameter is not null / undefined, then add &tag=tag to the path.
Β
IMPORTANT NOTE: The API will not permit you to use β#β in the βtagβ parameter, ie: βtag=#scaryβ will not function, but βtag=scaryβ will.Β Therefore, never invoke this function with a tag value that includes β#β.
Β
Finally, if the βcategoryβ parameter is not null /undefined, then add &category=category to the path
Β
getPostbyId(id): Observable<BlogPost>
Β
This method will use HttpClient to return a single post available in the blogAPI for a specific page using the path: /api/posts/id where id is the single parameter to the function.
Β
getCategories(): Observable<any>
Β
This method will use HttpClient to return an array of βCategoriesβ in the format: {cat: string, num:
number} using the path /api/categories (Note: You should recognize this format, as it was the one used in assignment 4 for the βCategoriesComponentβ data.)
getTags: Observable<string[]>
This method will use HttpClient to return an array of βTagsβ (represented as strings) using the path /api/tags.
Β
Step 4: Refactoring the βBlogβ view (BlogComponent)
With all of our PostService methods in place, we can now start refactoring our code to start using it.
The best place to start is with the βBlogComponentβ, since this is the primary interface for interacting with the blog.
To begin, open up blog.component.ts and make the following updates:
- Remove the import line for the βblogData.jsonβ file (we will no longer be requiring it)
- Import & inject the βPostServiceβ service
- Import & inject the βActivatedRouteβ service
- Make sure that you do not initialize the βblogPostsβ property to anything by default (weβll be using our service instead)
- Add the following properties to the component
- page (type: number, initial value: 1)
- tag (type: string, initial value: null)
- category (type: string, initial value: null)
- querySub (type: any, [ no initial value])
- In the ngOnInit() function, add the following logic
this.querySub = this.route.queryParams.subscribe(params => {
Β
if(params[βtagβ]){Β Β Β Β this.tag = params[βtagβ];Β Β Β Β this.category = null;
}else{
this.tag = null;
}
Β
if(params[βcategoryβ]){
this.category = params[βcategoryβ];
this.tag = null;
}else{
this.category = null;
}
Β
this.getPage(+params[βpageβ] || 1);
Β
});
- Add a new getPage(num) function with the following logic:
- Get all of the blog posts using the values of num, this.tag and this.category.
- If the length of the data coming back > 0, set this.blogPosts with the data and this.page with num
Β
- In the ngOnDestroy() function, add the following logic: if(this.querySub) this.querySub.unsubscribe();
Step 5: The PagingComponent
With our βblogβ page now showing live data from our database, itβs time to develop a new component to help with our pagination tasks within the BlogComponent.Β To begin, create a new component called βPagingComponentβ with the following specification:
Β
- The following HTML can be used as its starting template:
Β
<nav aria-label=βPage navigationβ>
<ul class=βpagination pagination-template d-flex justify-content-centerβ>
<li class=βpage-itemβ><a class=βpage-linkβ> <i class=βfa fa-angle-leftβ></i></a></li>
<li class=βpage-itemβ><a class=βpage-linkβ>1</a></li>
<li class=βpage-itemβ><a class=βpage-linkβ> <i class=βfa fa-angle-rightβ></i></a></li>Β Β Β Β </ul>
</nav>
- This component will accept a single property βpageβ (type: number)
- It will emit (output) the event βnewPageβ which will contain the new page number obtained by clicking on one of the βleftβ or βrightβ βpage-itemβ buttons (details below)
- If the first βpage-itemβ button is clicked (ie: β<β ) , execute the following logic (placed in a separate βcallbackβ function):
- If the value of βpageβ is greater than 1 emit the βnewPageβ event with a value of βpageβ β 1
- If the last βpage-itemβ button is clicked (ie: β>β ) , execute the following logic (placed in a separate βcallbackβ function):
- emit the βnewPageβ event with a value of βpageβ + 1
Β
Once completed, the component must be placed beneath the <app-post-card></app-post-card> element in blog.component.html using the html:
Β
<div class=βw-100β³><app-paging></app-paging></div>
Β
Note: Do not forget to provide <app-paging> with a β[page]β input parameter (set to the value of βpageβ in the blog component), as well as wire up the emitted β(newPage)β event to execute βgetPage($event)β β declared above.
Β
Step 6: The CategoriesComponent
Now that weβre working with live blog data, we must refactor our CategoriesComponent to use it as well.Β Start with updating the class βCategoriesComponentβ (categories.component.ts) according to the following specification:
Β
- Remove the hardcoded data assigned to the βcategoriesβ array
- Import & inject the βPostServiceβ service
- In the ngOnInit() function, add logic to populate the βcategoriesβ array using the βPostServiceβ getCategories() function.
- Update the categories.component.html template to use the following βrouterLinkβ attribute (assuming that βcatβ is the current βcategoryβ:
- [routerLink]=β[β/blogβ]β [queryParams]=β{ category: cat.cat }β
Β
Step 7: The TagsComponent
We must refactor our TagsComponent to use our new service as well.Β Start with updating the class βTagsComponentβ (tags.component.ts) according to the following:
- Remove the hardcoded data assigned to the βtagsβ array
- Import & inject the βPostServiceβ service
- In the ngOnInit() function, add logic to populate the βtagsβ array using the βPostServiceβ getTags() function.
- Update the tags.component.html template to use the following βrouterLinkβ attribute (assuming that βtagβ is the current βtagβ. Note: we eliminate the leading β#β by using substring(1)):
- [routerLink]=β[β/blogβ]β [queryParams]=β{ tag: tag.substring(1) }β
Β
Β
Step 8: The LatestPostsComponent
Up until now our βLatestPostsComponentβ has been relying on a βpostsβ property for its data.Β However, now that we are using an external data source, we can refactor this component to fetch the data itself.Β First begin by opening βlatest-posts.component.tsβ and making the following changes:
Β
- Remove the @Input() decorator from βpostsβ
- Import & inject the βPostServiceβ service
- In the ngOnInit() function, add logic to populate the βpostsβ array using the βPostServiceβ getPosts(1, null, null) function. However, make sure that you only grab the first 3 results.Β This can be done by once again using the .slice(0,3) method on the returned array
Next, locate both instances of the <app-latest-posts> component (blog.component.html & post.component.html), it should look something like:
Β
- <app-latest-posts [posts]=βblogPosts.slice(0,3)β></app-latest-posts>.
Β
We simply want to remove the [posts] property, therefore it should be changed to
Β
Β Β Β Β Β Β Β β’ Β Β <app-latest-posts></app-latest-posts>
Β
Finally, in the template (latest-posts.component.html), update the routerLink attribute to link to the correct βpostβ, ie: [routerLink]=β[β/postβ, post._id]β
Β
Β
Step 9: The PostCardComponent & Routing Update
With our main βBlogβ page in fairly good working order,Β we must finally update our PostCardComponent so that we can click on a specific blog post from within the βBlogβ page and access it directly.Β The main task here is to open the template (post-card.component.html) and make the following key change:
Β
- Both routerLink=β/postβ properties within the template need to be changed to the below (assuming βpostβ is the current βpostβ object: [routerLink]=β[β/postβ, post._id]β
Β
Lastly, for this route to function properly, we need to update our βapp-routing.module.tsβ file to ensure that βpostβ has an βidβ route parameter, ie βpost/:idβ.
Β
Β
Step 10: The PostDataComponent
The last major component to update for this assignment is the βPostDataComponentβ.Β To begin, open the post-data.component.ts file and make the following changes:
Β
- Add the following property to the component
- querySub (type: any, [ no initial value])
- Remove the @Input() decorator from the βpostβ property as well as update any instance of the component that uses the [post] property binding, ie:
Β
<app-post-data [post]=βblogPosts[0]β></app-post-data>
Β
Should be changed to:
Β
<app-post-data></app-post-data>
Β
- Next, Import & inject the βPostServiceβ service
- Import & Inject the βActivatedRouteβ service
- In the ngOnInit() function, add the following logic assuming that βrouteβ references the ActivatedRoute service (NOTE: be sure to fill in the βTODOβ with your own code)
Β
this.querySub = this.route.params.subscribe(params =>{
//TODO: Get post by Id params[βidβ] and store the result in this.post
})
Β
- In the ngOnDestroy() function, add the following logic:
- if(this.querySub) this.querySub.unsubscribe();
Β
- Finally, open up the template (post-data.component.html) and locate the <div class=βpost-tagsβ> element. Inside, you will find an empty βrouterLinkβ that needs to be refactored to link to the βblogβ page with the correct tag filter, ie:
Β
- [routerLink]=β[β/blogβ]β [queryParams]=β{ tag: tag.substring(1) }β
Β
Β
Step 11: New FooterPostsComponent
Β
Weβre almost done with the major components for the Blog and Page views, however we are still showing static (unrelated) blog posts in the footer of our blog.Β To fix this, letβs add a new component:
βFooterPostsComponentβ (Note: this component is very closely related to our βLatestPostsComponentβ, so we will actually be recycling the code from that class)
Β
Once you have crated your βFooterPostsComponentβ, open the βfooter-posts.component.tsβ file and replace the code within the class with exact copy of the code within your βLatestPostsComponentβ class (this will include the βpostsβ property, the constructor, and the ngOnInit() function).
Β
Note:Β βBlogPostβ and βPostServiceβ will need to be imported once this is complete.
Β
Updating the Template
Β
The βstaticβ template for this component exists near the bottom of your βfooter.component.htmlβ file.Β You should see a <div class=βlatest-postsβ>β¦</div>.Β Delete this entire element.
Β
Now, instead of rendering the full <div class=βlatest-postsβ> element in the footer.component.html file, we will use the new βFooterPostsComponentβ, ie:
Β
<app-footer-posts></app-footer-posts>
Β
Using βpostsβ in the Template (footer-posts.component.html)
Β
For this template, you can use the following HTML for a single post (Note: You will have to repeat each <div class=βpostβ for each βpostβ)
Β
<div class=βlatest-postsβ>
<div class=βpostβ>
<a routerLink>
<div class=βpost d-flex align-items-centerβ>
<div class=βimageβ><img src=βfeaturedImageβ alt=ββ¦β class=βimg-fluidβ></div>
<div class=βtitleβ><strong>title</strong><span class=βdate last-metaβ>postDate</span></div>
</div>
</a>
</div>
</div>
Β
Β
- The βrouterLinkβ attribute should be changed to link to the correct post (using post._id)
- The <img> should use the βfeaturedImageβ for the post
- The βtitleβ should be the βtitleβ for the post
- The βpostDate should be the βpostDateβ for the post
Β
Step 12: Angular βDateβ Pipe
Finally, as a last step to clean our output, we will use Angularβs date pipe functionality. Β The way this works is that whenever a date is rendered, ie {{post.postDate}}, you will pipe the output to date: βmediumDateβ, ie: {{post.postDate}}Β becomes {{post.postDate | date:βmediumDateβ}}
Β
Note:Β Do not forget to βpipeβ the date for your post comments as well!
Β
Extra Challenge!
Β
When running our app, you might have noticed something slightly annoying from a usability perspective.Β When we change routes, our scroll position on the page remains the same!Β Ideally, we should be scrolled back to the top after every route change.Β As an extra challenge, try researching this topic for yourself and implementing it within your application.




