[Tutorial] How to install Angular CLI and show Steemit Posts in an Angular Web App
In this tutorial you will learn
- how to install Angular CLI on your machine
- initialize your app using Angular CLI
- create a Post class to describe particular posts
- create a PostDataService service to retrieve posts from steemit
- use the base AppComponent component to bring it all together and display the steemit posts
- style your app with CSS
- push your app to GitHub Pages
You will need an environment capable of runing Node.js and NPM.
This tutorial is suggested for basic to intermediate users.
Before we start, let's take a sneak peek at the end result.
See it in action on GitHub Pages: Steemit Posts or clone the repository from GitHub: Steemit Posts and run it on your machine, or checkout the following screenshot.
Ready? Let’s get started!
Note: Angular starting from version 2, is a completely new framework built on ideas and lessons learned from AngularJS which is version 1.
Install Angular CLI
The most comfortable way to start an Angular app is to use the Angular command line interface (CLI)
To install Angular CLI in your NodeJS environment, open your console and run
> npm install -g @angular/cli
after the installation, the ng
command will be globally available on your system.
To verify that the installation went through successfully, run:
> ng version
it will print something like this
Angular CLI: 1.7.2
Node: 9.6.1
OS: linux x64
Angular: 5.2.7
Now Angular CLI is installed on your machine, let’s head over and create your app.
Initialize your app using Angular CLI
The Angular CLI makes it easy to create an application that already works, right out of the box. Run:
> ng new steem-posts
Now, navigate into the project directory
> cd steem-posts
and generate your first class.
Generate the Post class
Angular CLI uses TypeScript, so we can use a class to describe the Post items.
Let’s generate a Post
class using Angular CLI
> ng generate class post
which will create
src/app/post.ts
Let's open the new class file and add the logic
export class Post {
title = '';
author = '';
body = '';
created = '';
images: Array<string> = [];
tags: Array<string> = [];
net_votes = 0;
pending_payout_value = 0;
constructor(values = {}) {
Object.assign(this, values);
}
}
In the Post
class you define that every Post
instance will contain these attributes
title: string, title of the post
author: string, author of the post
body: string, the actual content
created: string, creation date of the post
images: array of strings, images belong to the post
tags: array of strings, associated tags
net_votes: number, post upvotes count
pending_payout_value: number, value of the post
The attributes have initial values assigned already, so you know the types by declaration and don't have to specify them additionally. Except images
and tags
, there you define the array content type.
Along the class attributes, you also define the constructor that adds the ability to define values during instantiation, so you can create new Post
instances just like this
let post = new Post({
title: 'Beyond Awesome!',
author: 'RhinoTheHamster',
body: 'Rhino is awesome! He's so awesome! He is... he is beyond awesome! He's bey-awesome!',
// …
});
Now that you have a descriptive Post
class, let’s move on and generate a PostDataService
service to retrieve posts from steemit.
Generate the PostDataService service
The PostDataService
will handle the API requests and manage the Post
items.
Let’s install the steem API using NPM, so we have an interface to the steemit data
> npm install --save steem
and generate the PostDataService
service using Angular CLI
> ng generate service post-data
which will create
src/app/post-data.service.spec.ts
src/app/post-data.service.ts
Angular CLI also generated the spec file, which you can use to define unit tests. Let’s skip them for now and move straight to the service file and add the logic we need
import { Injectable } from '@angular/core';
import { api as steemapi } from 'steem';
import { Post } from './post';
@Injectable()
export class PostDataService {
constructor() { }
getPosts(): Promise<Post[]> {
return new Promise((resolve, reject) => {
steemapi.getDiscussionsByCreated({ limit: 10 }, (error, result) => {
if (error) {
reject(error);
} else {
const posts = [];
result.map((post) => {
const meta = JSON.parse(post.json_metadata);
posts.push(new Post({
title: post.title,
author: post.author,
body: post.body,
created: post.created,
images: meta.image,
tags: meta.tags,
net_votes: post.net_votes,
pending_payout_value: post.pending_payout_value,
}));
});
resolve(posts);
}
});
});
}
}
Et voilà! Now that we have a PostDataService
, let’s move on to the display part.
Hands on the AppComponent component
Angular CLI automatically generated AppComponent
for you when you created the project. You can find it’s parts here
src/app/app.component.css
src/app/app.component.html
src/app/app.component.ts
src/app/app.component.spec.ts
Let’s edit src/app/app.component.html
and replace the dummy content with
<article *ngFor="let post of posts; let even = even; let odd = odd" [ngClass]="{ odd: odd, even: even }">
<div class="image" [style.background-image]="'url('+(post.images? post.images[0] : '')+')'"></div>
<div class="body">
<h1 [innerText]="post.title"></h1>
<div class="meta">
By <a href="https://steemit.com/@{{post.author}}" target="_blank">{{post.author}}</a> • {{post.created}} • ${{post.pending_payout_value}} • {{post.net_votes}} votes
</div>
<div class="tags" *ngIf="post.tags.length > 0">
<a *ngFor="let tag of post.tags" href="https://steemit.com/trending/{{tag}}" target="_blank">{{tag}}</a>
</div>
<p [innerText]="(post.body.length > 300)? (post.body | slice:0:300)+'..': (post.body)"></p>
</div>
</article>
Have a look at Angular’s template syntax and directives. It makes our life a lot easier. Here's an excerpt
*ngFor="..."
- iterates an array of items and creates elements dynamically from a template element; exports values that can be aliased to local variables: e.g.even: boolean
,odd: boolean
*ngIf="expression"
- conditionally includes a template based on the value of an expression[property]="expression"
- sets the property of an element to the value of expression[class.class-name]="expression"
- addsclass-name
to the elements list of CSS class names when the value ofexpression
is truthy[style.property]="expression"
- sets an elements CSS style property to the value ofexpression
Want to know more? Head over to the official Angular template syntax documentation.
Now let's add the logic or the so called expression context. The expression context in this case is the component instance which is an instance of the class AppComponent
. So let's dive into the file src/app/app.component.ts
and add the logic.
import { Component, OnInit } from '@angular/core';
import { PostDataService } from './post-data.service';
import { Post } from './post';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers: [PostDataService]
})
export class AppComponent implements OnInit {
posts: Post[] = [];
constructor(private postDataService: PostDataService) { }
public ngOnInit() {
this.postDataService.getPosts().then((posts) => {
this.posts = posts;
}).catch((error) => {
console.log(error);
});
}
}
What do we have here? Let's have look
At first we import the Post
class and the PostDataService
service and define PostDataService
in the providers
array of the Compontent
decorator, so the dependency injector (DI) of AppComponent
knows, it has to create only a single instance whenever we ask for the service.
Wanna learn more about Angular’s dependency injection system? Head over to Angular’s official dependency injection documentation.
Now in the constructor
of AppComponent
we instruct the instance to provide PostDataService
service as an private attribute of the AppComponent
instance.
We also added a public property called posts
of type Post
, declared it's initial value to be an empty array and added the public method ngOnInit()
to ask PostDataService
to pull in steemit posts and assign it to this.post
, overwritting the empty array. Now when Angular initializes AppComponent
, ngOnInit()
gets executed and the train starts to move :)
Let's try it, run
> ng serve
this will start a local develompent server, which you can access by navigating your browser to http://localhost:4200/
When you change a file, your local develompent server will send a signal to your browser, so it can automatically reload the application.
Ok! Let's check the result.. Missing something? Oh yah! We miss the styling.
Style your app
Angular loads stylesheets which belong to components automatically, so just edit src/app/app.component.css
and add
@import url(https://fonts.googleapis.com/css?family=Roboto|Open+Sans);
article {
display: flex;
margin: 0 auto 10px;
height: 300px;
width: 800px;
font-family: 'Open Sans', sans-serif;
font-size: 14px;
line-height: 21px;
border-radius: 3px;
box-shadow: 0 3px 7px -3px rgba(0, 0, 0, 0.3);
}
article .image {
position: relative;
width: 300px;
height: 300px;
background-image: url('https://placeimg.com/300/300/tech');
background-size: cover;
background-repeat: no-repeat;
background-position: center center;
}
article .body {
width: 500px;
padding: 20px;
overflow: hidden;
background-color: #fff;
}
article .body h1 {
margin: 0 0 10px;
font-family: 'Roboto', sans-serif;
font-size: 28px;
font-weight: 700;
line-height: 32px;
letter-spacing: 2px;
}
article .body .meta,
article .body .tags {
font-family: 'Open Sans', sans-serif;
font-size: 14px;
font-weight: 400;
color: #9a9a9a;
}
article .body .tags {
margin-top: -2px;
}
article .body .tags a::before {
content: ', ';
}
article .body .tags a:first-child::before {
content: '';
}
article .body > p {
position: relative;
margin: 15px 0 0;
}
article .body > p::before {
content: "";
position: absolute;
top: -10px;
left: 0;
height: 5px;
width: 50px;
background-color: #689F38;
}
article.odd .image {
order: 2;
}
article.odd .image::before,
article.even .image::after {
content: "";
position: absolute;
top: 0px;
width: 22px;
height: 100%;
background-color: #fff;
transform: skewX(-4deg);
}
article.odd .image::before {
left: -11px;
}
article.even .image::after {
right: -11px;
}
Now open the application's global stylesheet src/styles.css
and add
html, body {
background-color: #f1f1f1;
}
a {
color: #689f38;
text-decoration: none;
}
Great! Save the files and watch the LiveReload system doing it's magic :)
Before we end this tutorial, let's take advantage of an awesome feature of Angular CLI.
Deploy your app to GitHub Pages
With Angular CLI it's super simple to deploy your work. The following command tells Angular CLI to build a static version of your app and save it to the docs/
directory in the root of your project.
> ng build --prod --output-path docs --base-href steemit-posts
Now push your project to GitHub into your repository's master branch and change the settings of this repository to serve from master branch /docs folder
by doing this:
- On GitHub, navigate to your GitHub Pages site's repository.
- Under your repository name, click Settings.
- Scroll to "GitHub Pages" and use the Select source drop-down menu to select
master branch /docs folder
as your GitHub Pages publishing source.
Note: Need more input on GitHub Pages and how to configure a publishing source for GitHub Pages? Read Configuring a publishing source for GitHub Pages
When all goes right, you should now see your new app hosted on GitHub Pages, like this
https://learnsteem.github.io/steemit-posts/
That's bey-awesome! Isnt't it?
That's it :)
Feel free to clone the source of this tutorial from GitHub: Steemit Posts
Thanks for reading, hope you enjoyed it :)
Posted on Utopian.io - Rewarding Open Source Contributors
this clearly tutorial what i looking for a long time. hope can help me thank you very much
Changed repository to angular:
Please change your tutorial to follow template provided in the editor:
Hi @laxam. Thanks for approving the tutorial. I've changed the text to include Requirements and Difficulty.
About changing the repo to angular: I would not suggest changing the repo to angular, since angular cli is a bootstrapper and development tool for angular projects. It copies template files from the angular cli repo, so it's easy to start a new project. So in this case angular cli is not the "original project" and the tutorial is not a "supplementary repository" but a whole new project created with tools from angular cli.
Hi @aley. Thanks for including Requirements and Difficulty.
I would argue that this tutorial teaches reader how to use angular and not steemit-posts. If you view it as a new project - there is a development section for such cases. You can send future contributions there if they follow the rules for that section.
@laxam, so in case I would make a tutorial for "how to program in javascript" I would add the javascript language repo as original project? It's a bit confusing, but I guess I got the idea. Thanks.
Thank you for the contribution. It has been approved.
You can contact us on Discord.
[utopian-moderator]
Hey @aley I am @utopian-io. I have just upvoted you!
Achievements
Community-Driven Witness!
I am the first and only Steem Community-Driven Witness. Participate on Discord. Lets GROW TOGETHER!
Up-vote this comment to grow my power and help Open Source contributions like this one. Want to chat? Join me on Discord https://discord.gg/Pc8HG9x