$30
!.
Project 3: Markdown Editor Using Angular
Change history
02/12/2018 6B10PM Added clarification on when to save user's unsaved
modification and information on how it may be implemented.
Overview
In this project, you will learn and use Angular, a popular front-end Webdevelopment framework, to develop a more advanced and dynamic version of
markdown editor client. The implementation in Project 3 will save all blog posts
created by the user locally within the browser, without any interaction with the
server. In Project 4, you will then implement the server-side code, on Node.js and
Express, to store and publish the user's posts online. Combined together, you will
go through a full-stack Web-development cycle on the MEAN stack, an approach
popular among many Web start-ups today.
Note that your implementation of Project 3 will be used for your Project 4 as well.
Since Project 4 is dependent on Project 3, it is important that you follow
instructions on this spec exactly to avoid any potential issues later in Project 4.
Development Environment
All development for Projects 3 (and 4) will be done on a Docker container based
on the "junghoo/cs144-mean" image:
$ docker run -it -p3000U3000 -p4200U4200 -p8080U8080 -p49152U49152 -v
{host_shared_dir}:/home/cs144/shared --name mean junghoo/cs144-mean
Make sure to replace {host_shared_dir} with the name of the shared directory on
your host. The above command creates a docker container named mean with
appropriate port forwarding and directory sharing. Once created, you can start the
container simply by issuing the following command in a terminal window:
$ docker start -i mean
This container has Node.js (v8.9.4), MongoDB (v3.6.2), and Angular CLI (v1.6.6)
pre-installed. You can check their versions by the following commands.
$ node --version
$ ng --version
●
In writing the code for Project 3, you are likely to encounter bugs in your code and
need to figure out what went wrong. Chrome Developer Toolsis a very popular tool
among Web developers, which allows them to investigate the current state of any
web application using an interactive UI. We strongly recommend it for Project 3
and make it part of your everyday tool set. There are many excellent online
tutorials on Chrome Developer Tools such as this one.
Part A: Learn Angular and Basic Concepts
Angular is a front-end Web-development framework that makes it easy to build
applications for the Web. Angular combines declarative templates, dependency
injection, end-to-end tooling, and integrates best development practices to solve
challenges in Web front-end development. Angular empowers developers to build
applications that live on the Web, mobile, or the desktop.
Before starting on any materials related to Angular, first get yourself familiar
with JavaScript, and new language constructs from recent JavaScript
standards such as classes and modules. The latest Angular version
uses TypeScript, an extended version of JavaScript, as its primary language.
Fortunately, most Angular code can be written with just the latest JavaScript, with
a few additions like types for dependency injection, and decorators for metadata.
We will go over essential TypeScript for Angular in class, but you may want to go
over the class lecture notes now to learn the basic TypeScript.
Angular official website provides an excellent introductory tutorial on Angular
development: Tour of Heroes tutorial. It introduces the fundamental concepts for
Angular development by building a simple demo application.
Tour of Heroes tutorial
It may take some time to finish this tutorial, but we believe following this tutorial
is still the most effective and time-saving way to get yourself familiar with
the Angular development. Please note that when you follow the tutorial using the
Angular CLI preinstalled in our container, you will need to use the following
command to "run" your Angular code:
$ ng serve --host 0.0.0.0
not ng serve --open as described in the tutorial.
Note on --host option: By default, Angular HTTP server binds to only "localhost".
This means that if any request comes from other than localhost, it does not get it.
When Angular runs on the same machine as the browser, this is not a problem.
Angular binds to localhost and the browser sends a request to localhost. But when
Angular runs in a docker container, the localhost of Angular is different from the
●
●
●
●
●
●
●
localhost of the browser. Angular sees localhost of container and browser sees the
localhost of the host. By adding "--host 0.0.0.0", we instruct Angular to bind to all
network interfaces within the container, not just localhost, so that Angular is able
to get and respond to a request forwarded by Docker through network forwarding.
If you have previous Angular or similar Web-framework development experience,
you can choose to read the Angular documentation directly instead. However, for
most students who have not worked with Angular extensively before, reading the
documentation may take more time than following the step-by-step tutorial. Thus,
our recommendation is to start with the tutorial and then go over the
documentation after you get familiar with the basics.
Some caveats: Do not confuse Angular with AngularJS! When you search for
Angular related issues on the Internet, please use Angular 2 or Angular CLI as
search keywords, not AngularJS. AngularJS is an older version of the Angular
framework and is no longer a recommended version. The difference between the
"old" AngularJS and the "new" Angular is quite extensive, as you can read
from more detailed comparison articles on the Web. For our project, you may
ignore previous AngularJS versions and just learn the latest Angular CLI using the
links and tutorials provided in this spec.
After you finish the tutorial, go over the following questions and make sure you can
answer them by yourself.
What is a Component in Angular?
What is a Template? What are Directives in a Template?
What is a Module in Angular? How is NgModule different from a JavaScript
module?
How does Angular support Data binding?
What is a Service and how is Dependency injection done in Angular?
How is Routing done in Angular?
What are commonly used Angular CLI commands, such as generating a
component or service?
Please read corresponding sections in the Angular documentation for review if the
answer to any question is not clear.
Now you have equipped with enough Angular knowledge to get started with
Project 3. Good Luck!
Part B: Project Demo and Requirements
Project 3 is all about a front-end markdown blog editor and previewer. It should be
implemented as a single-page application (SPA), which means that your entire
application runs on a "single page." The website interacts with the user by
●
●
●
●
●
●
●
●
dynamically updating only a part of the page rather than loading an entirely new
page from the server. This approach avoids long waits between page navigation
and sudden interruptions in the user interaction, making the application behave
more like a traditional desktop application. A typical example of a SPA is Gmail.
An important feature of a SPA is that a specific state of the application is
associated with the corresponding url, so that a user does not accidentally exit
from the app by pressing a back button. When a user presses the browser back
button, the user should go to the previous state within the app (unless the user
just opened the app) as opposed to exiting from the app and go to the page
visited before the app. As an example, open Gmail, click on a few mail messages
and/or folder labels, and then press the browser back button. You will see that you
do not exit from the Gmail app, even though all your interaction in the app
happened on a single page, and, technically, the "previous page" in your visit
history should be the page that you visited before you opened the Gmail app. In
addition, if you cut and paste the Gmail's drafts folder URL https://gmail.com/
#drafts into the browser address bar, you will see that you directly land on the
draft folder of Gmail, not its generic start page. You will soon learn how to
implement this behavior by using the routing module of Angular.
We made a demo website of Project 3 available at http://oak.cs.ucla.edu/classes/
cs144/project3/demo/. It is rather simple and does not contain many CSS-styling
instructions, but it still meets the key requirements of this project.
In the first image, we show the edit view of the application, which allows the user
edit a post. In this view, we require you to implement the following functionalities:
The view shows one text input box for the title and one textarea for the body.
The "last modified" date and time is shown below the text boxes.
The view should contain at least three buttons, "save", "preview", and "delete".
The "save" button should be disabled (i.e., grayed out) unless the title or the
body has been modified by the user.
When the "save" button is enabled, pressing the button should permanently
save the post and update the last-modified date of the post to the current
time.
When the user "exits" from this view (by pressing the "preview" button, by
clicking on another post in the list, by pressing the browser "refresh" or "back"
buttons, or by typing a URL into the address bar), any unsaved changes
should be saved automatically, so that the app never loses the user's work.
When the "preview" button is clicked, the app should switch to the "preview
view" (see below).
When the "delete" button is clicked, the post should disappear from the "list
pane" (described below) and be permanently deleted.
●
●
●
●
●
●
●
●
In the second image, we show the preview view of the application. In this view, we
require you to implement the following functionalities:
The view shows an HTML-rendered preview of the current markdown post,
including its title and body.
There is an "edit" button to switch to the edit view.
In the third image, we show the "list pane", that should meet the following
requirements:
It shows the list of all blog posts that have been written by the user.
The posts in the list should be sorted by their "postid" (a unique integer
assigned to a post) in the ascending order.
The postid assigned to each post should start with 1 and increase linearly by
one.
Each post in the list must show the creation date & time of the post and the
title.
The user can add a new post anytime by clicking the "new post" button,
which opens the edit view of the newly created post on the right side.
The user can edit any of existing post by clicking its entry in the list,
which opens the edit view of the post on the right side.
Note that differently from Project 2, you are required to make the markdown editor
as a single-page application and the "list pane" should be always visible on the
left side of either edit or preview view.
In addition, when the user presses the browser's "back button", the user should go
to the "previous state" of the app, not to the page visited prior to your app. For
example, If the user opened the app, clicked on the first post in the list pane, and
pressed the "preview" button, the user should go to the state before the "preview"
button was pressed if the user clicks on the browser back button. It should also be
possible for the user to simply "bookmark" the current URL in the address bar, so
that the user can come back to exactly the same state of the application through
the bookmarked URL. In particular, you need to associate the three "states" of our
app with the following URL patterns:
URL state
/ This default path shows only the list
pane, without showing the edit or
preview view
/edit/:id This path shows the list pane and the
"edit view" for the post with postid=id
/preview/:id This path shows the list pane and the
"preview view" of the post
with postid=id
URL state
/ This default path shows only the list
pane, without showing the edit or
preview view
/edit/:id This path shows the list pane and the
"edit view" for the post with postid=id
/preview/:id This path shows the list pane and the
"preview view" of the post
with postid=id
Finally, all user-created blog posts must be stored locally in the browser
using html5 localStorage in Project 3 and the preview page should be generated
by a JavaScript code running inside the browser, using the commonmark.js library.
In short, there must be NO COMMUNICATIONbetween the browser and the server
once the app has been loaded, i.e., no GET or POST requests should be generated
by your app. Everything should be handled by the JavaScript code in the browser
with no interaction with the server.
Part C: Create Project Skeleton using Angular CLI
Now it is time to start working on the project using the Angular Command-Line
Interface (CLI). First create a new Angular application using the following
command:
$ ng new angular-blog
This may take a while since a lot of files are fetched and generated. When the
skeleton application is created successfully, we will see the following output.
Project 'angular-blog' successfully created.
You can launch the just-created application using ng serve --host 0.0.0.0 and
access it in your browser at http://localhost:4200/. Do not forget --host
0.0.0.0 as we are serving the Angular app from the container.
$ ng serve --host 0.0.0.0
The page you see is the application shell. The shell is controlled by an Angular
component named AppComponent.
Components are fundamental building blocks of any Angular application. They
display data on the screen, listen for user input, and take an action based on that
input.
The angular-blog directory that contains the initial skeleton code looks like the
following:
angular-blog
+- e2e
+- node_modules
+- src
●
●
●
+- app
+- app.component.css
+- app.component.html
+- app.component.spec.ts
+- app.component.ts
+- app.module.ts
+- assets
+- environments
+- index.html
+- styles.css
+- typings.d.ts
+- ...
+- package.json
+- README.md
+- ...
This may look like a lot of files at the first glance, but don't get overwhelmed. The
files you need to touch are all inside the src/app folder. Other files can be ignored
in most cases. In the src/app folder, the Angular CLI has created the root
module, AppModule, and the main application component, AppComponent.
You'll find the implementation of AppComponent distributed over three files:
app.component.ts — the component class file, written in TypeScript.
app.component.html — the component template, written in HTML.
app.component.css — the component's style, written in CSS.
The ".spec.ts" file is used for unit testing, and you can ignore it for now.
Refer to the Application Shell section of the tutorial if you still have confusions
about how these files work together to form a component.
Part D: Implement Blog Service
Now you create a "Blog Service" using the following command:
$ ng generate service blog --module=app
The option --module=app inserts the necessary imports and providers statements
to the AppModule, so that you don't have to add them yourself. You will see the
service files created in the terminal.
create src/app/blog.service.spec.ts (362 bytes)
create src/app/blog.service.ts (110 bytes)
!.
p.
●
The BlogService plays two important roles in our application.
It allows other components in the application to store and retrieve blog posts.
This service hides the exact storage mechanism of the blog posts, so that
other components can be implemented independently of the storage
mechanism. In fact, in Project 3, all blog posts are stored locally within the
browser using localStorage, while in Project 4, they will be stored in a remote
server. Despite this change, you won't have to change other code of your
application, except BlogService.
BlogService works as a "local memory cache" for blog posts, so that
(potentially) long delays of the underlying storage mechanism is hidden to
other components. While this is not an important issue for Project 3 because
blog posts is stored locally, this will become particularly important in Project 4
when posts are stored in a remote server.
Now, open the blog.service.ts file and declare a Post class with the following
properties:
export class Post {
postid: number;
created: Date;
modified: Date;
title: string;
body: string;
}
Note that we need to export this class, so that it can be imported and used by
other components of the application. postid is the unique id of the blog
post, created and modified are the post's creation and last modification date and
time, title and body are the actual content of the post formatted in markdown.
Add a private posts property to the BlogService class like the following:
private posts: Post[];
The property posts works as the "memory cache" of all blog posts. When the
application starts running, all blog posts by the user will be retrieved from the
underlying storage mechanism and be held here. In addition, any changes to a
post will be temporarily held here until they are permanently written to the
underlying storage.
Now implement the following methods in the BlogService class:
fetchPosts(): void - This method "populates" the posts property by retrieving
all all blog posts from localStorage. This function must be called inside the
●
●
●
●
●
●
!.
p.
t.
!.
constructor so that all posts are retrieved and be ready in memory
when BlogService is created.
getPosts(): Post[] - This method simply returns posts
getPost(id: number): Post - Find the post with postid=id from posts and return
it.
newPost(): Post - Create a new post with a new postid, an empty title and
body, and current creation and modification times, save it to localStorage, add
it to posts, and return it. The postid of a new post should start at 1 and
increase linearly.
updatePost(post: Post): void - From posts, find a post whose postid is the
same as post.postid, update its title and body with the passed-in values,
change its modification time to now, and update the post in localStorage. If no
such post exists, do nothing.
deletePost(id: number): void - From posts, find a post whose postid=id, delete
it from posts, and delete a corresponding post from localStorage. If no such
post exists, do nothing.
Notes
In Project 4, you will need to update the blog service to perform these
operations through HTTP on a remote server, as opposed to localStorage. To
make this later update contained, it is important that you implement the above
methods exactly as specified.
When you modify posts, an array of Post, in the above methods, keep in mind
that mutators, such as splice(), push(), and shift(), directly modify the
input array, while accessor methods, such as slice(), filter(), and map(),
don't. Accessor methods create a completely new copy of the output
array, leaving the original input array in tact.
To simplify things further, you may assume that all update/save operations
to localStorage are completed successfully without any error (e.g., you may
assume that you never get "out of space" error from update).
In the rest of the project spec, we describe more detailed guidance on how you
can implement the rest of Project 3. However, keep it mind that the rest of our
project description is a suggestion, not a requirement. As long as your code meets
the requirements in Part B and implements Post and BlogService exactly as
described in Part D (you can add additional properties and/or methods
to Post and BlogService, but you cannot change what is given), you can implement
the rest of your application however you want. We provide further description here
in case you need more guidance and help to finish this project.
Part E: Implement List and Edit Components
Roughly, our editor may be split into three different components:
List component: This component is responsible for displaying the "list pane".
!.
p.
t.
!.
p.
t.
u.
This component should be visible on the left side of the app all the time. The
user should be able to click on a post in the list to edit it.
Edit component: This component is responsible for the "edit view" of the app.
When the user clicks on a post in the list pane, this component should be
displayed on the right side of the list and let the user edit the title and body of
the post. It should also contain a buttons for "save", "delete", and "preview".
Preview component: This component is responsible for the "preview view" of
the app. When the user clicks on the "preview" button in the edit component,
this component should replace the edit component and show the HTML
version of the post.
Add the Edit Component
Now let us create the edit component:
$ ng generate component edit
Note that all components in our app, including EditComponent, needs to
use BlogService to retrieve and/or update blog posts. Thus, you will have to
import Post and BlogService classes in edit.component.ts through an import
statement. Also, you will need to make BlogServiceavailable
in EditComponent through dependency injection by modifying the component's
constructor signature. If this sounds confusing, go over the Angular tutorial again,
in particular the services section. The Angular documentation on dependency
injection can also be helpful.
Also, add post: Post as a property of EditComponent, which will hold a copy of the
post that is being currently edited.
Remember that EditComponent is responsible for the following user interactions:
The title and body of the current post should appear in text input and textarea,
respectively, so that the user can edit them.
When the user clicks on the "preview" button, any unsaved changes should be
saved, and the "preview view" should open.
The "save" button should be disabled by default until the user makes changes
to either the title or the body. When the user clicks on the "save" button, all
changes should be saved to localStorage (via BlogService) and the "save"
button should be disabled again.
When the user tries to leave the edit view, all unsaved changes should be
automatically saved to localStorage (via BlogService), so that no work is lost.
To support the above interactions, remove the auto-generated HTML code in
●
●
●
●
●
the template, edit.component.html, and add necessary HTML elements. In
modifying the template, you may find the following information useful:
Data can be dynamically exchanged between a template element and a
component property using Angular's two-way binding and
the ngModel directive (e.g., [(ngModel)]="post.title"). This mechanism can be
used, for example, to support displaying and editing the post's title. Note
that FormsModule needs to be imported in the RootModule, app.module.ts, if
you want to use the ngModel directive.
You can use interpolation (e.g., {{post.modified}}) if you want to display a
property value in the template.
You can use event binding (e.g., (click)="delete()"), to call a method of the
component for a triggered event.
You can disable a button by setting the disabled property of the button to
true, like [disabled]="true". You can check whether a value in a form input has
been modified using the dirty property of FormControl. You can clear
the dirty property by calling markAsPristine()of the FormControl. To learn how
you can create a FormControl in the component and how to associate it with
an input element in the template, the reactive forms page of the Angular
documentation will be helpful.
You can use the structural directive *ngIf or the safe navigation operator ?. to
guard against null or undefined values in a property.
Add CSS rules to edit.component.css to make the component look reasonable.
Once you finish updating the template and CSS style, implement the
functionalities described above to the component class, edit.component.ts. In
particular, you may want to add one "event handler" method per each button-click
event with the names like save(), delete(), and preview(). Note that you may not be
able to implement preview() method yet, since you need to implement the preview
component first to switch to the "preview view".
Test the Edit Component
In order to display EditComponent in your app, you must add it as a child
component of AppComponent. Add <app-edit</app-edit to src/app/
app.component.html as follow to test your implementation of EditComponent:
<h1{{title}}</h1
<app-edit</app-edit
Even after this addition, you may not see any output from EditComponent when
you run ng serve --host 0.0.0.0. This is because the postproperty
of EditComponent is never initialized to a value in your code yet. You will add the
proper initialization code for post after you implement other components. For now,
!.
p.
t.
assign a fake value to post like the following in, say, the constructor:
this.post = {
postid: 1,
created: new Date(),
modified: new Date(),
title: 'title',
body: 'body'
};
If you still have the ng serve --host 0.0.0.0 command running, you should now see
that the EditComponent displays the test data in the browser. After verifying that
your EditComponent is working fine, remove the fake initialization code
for post and remove <app-edit</app-edit from app.component.html.
Note: In case the ng serve does not auto-rebuild your app after a file is changed,
make sure that you forwarded port 49152 when you initially set up the container.
Otherwise auto-rebuild does not work. Some students also report that "ng serve"
does not auto-rebuild even with proper port forwarding, particularly on a Windows
machine. If that is the case, try ng serve --host 0.0.0.0 --poll=2000. The
development server will look for file changes every two seconds and compile if
necessary.
Add the List Component
We now create the second non-root component, the list component.
$ ng generate component list
Make Post and BlogService available in ListComponent by including an appropriate
import statement and modify its constructor signature to
inject BlogService through dependency injection.
Remember that the list component needs to take the following actions depending
on the user interaction:
It has to obtain all blog posts through BlogService and display them in a list on
the left side of the app.
When the user clicks on the "new" button, it has to create a new post
using BlogService and open the "edit view" for the post.
When the user clicks on a post in the list, it has to open the "edit view" for the
post.
To display the list component in our app, you need to make it a child
of AppComponent by including its component directive. You may also find
Angular's structural directives helpful in displaying the list of posts it obtains
from BlogService.
The third action, opening the "edit view" when the user clicks on a post can be
implemented in a number of different ways, but given our requirements in Part B,
the best way might be using a router, which will be explained in the next part.
Part F: Add the AppRoutingModule
As we mentioned earlier, an important feature of the single-page application is
that a specific state of the application is associated with the corresponding
url. In particular, you need to associate the three "states" of our app with the
following URL patterns:
URL state
/ This default path shows only the list
pane, without showing the edit or
preview view
/edit/:id This path shows the list pane and the
"edit view" for the post with postid=id
/preview/:id This path shows the list pane and the
"preview view" of the post
with postid=id
Associating a URL with a particular state of the application and displaying a
different component based on the state can be achieved through a router in
Angular. If you do not remember what a router is or how to use it, go over the
Angular tutorial again, in particular the routing section. It may be useful to look at
the routing & navigation section of the Angular documentation.
Angular's best practice is to load and configure the router in a separate, top-level
module that is dedicated to routing and import it in the root AppModule. By
convention, the class dedicated for a routing module is named
as AppRoutingModule and can be generated using the following CLI command:
$ ng generate module app-routing --flat --module=app
The --flat option creates app-routing.module.ts directly in src/app not in its own
subdirectory. The --module=app option adds necessary import statements to
the RootModule, app.module.ts. Open app-routing.module.ts and update its
content as follows:
i
mport { NgModule } from '@angular/core';
!.
p.
t.
u.
{.
!.
import { RouterModule, Routes } from '@angular/router';
import { EditComponent } from './edit/edit.component';
const routes: Routes = [
{ path: 'edit/:id', component: EditComponent }
];
@NgModule({
imports: [ RouterModule.forRoot(routes, {useHash: true}) ],
exports: [ RouterModule ]
})
export class AppRoutingModule { }
In the above code, the routes config maps the URL pattern edit/:id to
the EditComponent. (Mapping the the URL pattern preview/:id to
the PreviewComponent need to be added later after we implement the "preview
component". The path / does not need a mapping since the app does not need to
display any component other than the list component for /.)
Add a router outlet in app.component.html, so an appropriate component will be
displayed if the URL pattern matches to a route:
Now that we have the URL-to-component mapping in place through RouterModule,
we need to modify our code to implement the correct routing behavior. In
particular, we have to add the following logic to our code:
When the user clicks on a post in the list, our app should "navigate" to the
URL edit/:id, where :id is the postid of the clicked post.
When the app navigates to edit/:id and
the RouterModule displays EditComponent in the router outlet,
our EditComponent should retrieve the post whose postid is :id and update its
template elements accordingly.
When the user clicks the "delete" button in the EditComponent, the
component should delete the current post through BlogService and navigate
to the URL /, so that EditComponent is no longer displayed in the router outlet.
When the user "navigates away" from the "edit view", any unsaved work by the
user should be automatically saved.
When the user clicks on the "preview" button in the EditComponent, the
component should save any unsaved changes and navigate to the
URL preview/:id, where :id is the postid of the current post.
To implement the above functionality, you may find the following information
helpful:
Within your component method, you can "navigate" to a particular URL by
!.
p.
t.
u.
calling the navigate() method of the Router object, like router.navigate(['/']).
Inside a template, you can use the routerLink directive, like <a routerLink="/",
to have the same effect when the user clicks on the link.
You can obtain the :id part of an "activated URL" (or "activated route") with
the ActivatedRoute object, by
calling activatedRoute.snapshot.paramMap.get('id').
The EditComponent can "subscribe" to the "URL activation event" -- so that
whenever a new URL is activated, it can retrieve the correct post
from BlogService and update its elements -- using the ActivatedRoute object.
For example, the following code
activatedRoute.paramMap.subscribe(() = this.getPost());
will ensure getPost() method to be called whenever the route is activated.
To save the user's unsaved edits in the "edit view" when the user presses the
browser's refresh button, etc., you may want to intercept the
"beforeunload" (or "unload") event of the global window object. In Angular, a
method can be declared to be called for a general browser event by
decorating it with the @HostListener(event) decorator. For example, if you
decorate a method
of EditComponent with @HostListener('window:beforeunload'), the method
will be called whenever "beforeunload" event is triggered to the
global windowobject.
To be able to use Router and ActivatedRoute objects in EditComponent, update its
constructor signature to make the two classes available through dependency
injection.
If you use @HostListener decorator in EditComponent, you will need to import it
in edit.component.ts by including the following import statement:
import { HostListener } from '@angular/core';
Now your web application should have all functionalities implemented except
"preview".
Part G: Add the Preview Component
You should be fairly familiar with the Angular development process if you finished
all previous parts and reached here. In this part, most of the tasks are similar to
what you have done already, so we provide a minimal guidance.
First you need to add the preview component through the Angular Cli. Then add
the the route mapping from preview/:id to this component in your routing module.
For markdown to HTML rendering, we will use commonmark.js library, so install
the commonmark module through the following command:
$ npm install --save commonmark
Open preview.component.ts, add the following import statement
import { Parser, HtmlRenderer } from 'commonmark';
to use commonmark's Parser and HtmlRenderer objects in your code. Note that
commonmark.js's API is almost identical to the Java version, so you will find it easy
to use.
Update the constructor signature of PreviewComponent with necessary
dependencies and add the appropriate import statements. Subscribe to the URL
activation event, so that the correct post is retrieved from BlogService and
rendered as HTML when a "preview URL" is activated.
Edit preview.component.html and preview.component.css to add the HTML
elements and css rules needed for the component.
Finally, make sure that you update EditComponent, so that it correctly handles the
user's click event on the "preview" button.
Congratulations! You now have finished all the requirements and functionalities of
the project.
Part H: Test your project locally
To check the state of your web application in browser, you can open the angular
live development server using ng serve --host 0.0.0.0 and you can check your
work at http://localhost:4200/.
$ ng serve --host 0.0.0.0
Note you can leave the terminal open so that whenever you make any more
change to the project, the browser window will be automatically refreshed.
Please check your web application is shown in the browser correctly and you can
complete the post create, update, delete and preview tasks without any issues.
Also, the post list should be always be displayed on the left side of any page and
be updated whenever a new blog is created or an existing blog is updated or
deleted.
Further, please check that by typing a URL directly in the address bar, you can
directly jump to the corresponding edit or preview views. For example, http://
localhost:4200/#/edit/1 or http://localhost:4200/#/preview/1, should show the the
first post's edit or preview view, respectively.
Lastly, please verify your application does not throw any errors in the javascript
console. This can be checked in chrome by right clicking the browser window and
choose "inspect". Then choose the "Console" tab to verify if any errors are
appearing. Also, the saved blog posts should persist in the local storage. It can be
verified by quitting and restarting the browser and check if all saved blog posts
are still there.
Submit Your Project
After you have checked there is no issue with your project, you can package your
work by running our packaging script. Please first create the TEAM.txt file and put
your team's uid(s) in it. This file must include the 9-digit university ID (UID) of
every team member, one UID per line. No spaces or dashes. Just 9-digit UID per
line. If you are working on your own, include just your UID. Please make
sure TEAM.txt and package.sh are placed in the project-root
directory, angular-blog, like this:
angular-blog
+- e2e
+- node_modules
+- src
+- app
+- ...
+- assets
+- environments
+- index.html
+- styles.css
+- typings.d.ts
+- ...
+- package.sh
+- TEAM.txt
+- ...
When you execute the packaging script like ./package.sh, it will build a
deployment version of your project and package it together with your source code
and the TEAM.txt file into a single zip file named project3.zip. You will see
something like this if the script succeeds:
[SUCCESS] Created '/home/cs144/shared/angular-blog/project3.zip', please
submit it to CCLE.
●
●
●
●
●
●
●
●
Please only submit this script-created project3.zip to CCLE. Do not use any
other ways to package or submit your work!
Grading Criteria
We will grade your Project 3 mainly based on the functionalities. Specifically, we
will test following things:
Post List: Post list is always shown on the left side of any page, and is
updated whenever a blog is created, updated or deleted.
Post Operations: Create, Update and Delete blogs without any issues.
Post Preview: Render the markdown blog correctly and be able to return to
the edit view from the preview view.
URL Navigation: User can access a state of your application (e.g. edit or
preview a specific blog) by directly typing the url.
Local Storage: The blog entries should persist on the local storage no matter
the browser window is closed or re-opened.
User Interface: The User Interface should at least adopt the similar CSS
decorations like what is done in demo. Better decorations may gain extra
credit, but no more than 10%.
No Errors: JavaScript Errors are very severe issues, we will deduct 20% of
total points for each type of the error that appears on the browser console.
Other Requirements in Description: For example, auto-save behavior,
"disabled" save button if no modification, etc.