Previous page: Retrieve a list of messages from Slack to build a GraphQL query
Create Angular Components
Display a list of messages with Angular
We will focus on building the user interface to display a list of messages, we will create a new Angular Module to hold our new feature components and focus on presentation.
First we will need to do a bit of wiring up.
Setup the new feature module
Change directory to web-app/src/app
from within your project folder.
cd <project-folder-path>/web-app/src/app
We will use the Angular CLI to create a new module that will contain all the artifacts of our new messages feature with routing.
ng generate module messages --routing=true
The CLI will create a new module folder and file called messages.module.ts
that will be used to register all the items we will create with Angular's
dependency injection system.
Next, we will create several user interface Components that will be responsible for different areas of the page display and interaction to give a good separation of concerns (SoC). This will hopefully make the application easier to understand from just looking at the file system arrangement.
Create the Components
CreateMessageComponent
that will allow a user to type and send a message.ViewConversationComponent
that has the responsibility of displaying an interactive conversation of messages.MessagesComponent
to tie all components together.
ng generate component messages/create-message
ng generate component messages/view-conversation
ng generate component messages/messages
If you open the messages.module.ts
file, you'll see our new components
registered with Angular:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MessagesComponent } from './messages/messages.component';
import { CreateMessageComponent } from './create-message/create-message.component';
import { ViewConversationComponent } from './view-conversation/view-conversation.component';
@NgModule({
declarations: [
MessagesComponent,
CreateMessageComponent,
ViewConversationComponent
],
imports: [
CommonModule
]
})
export class MessagesModule { }
The declarations
array defines all the components that can be used within
the scope of the module.
Routing
Our application is very simple with regards to routing. There will be just one
route to define. Open the file
web-app/src/app/messages/messages-routing.module.ts
.
Angular best practice is to define routing in a separate module file because
routing code can become very verbose for many applications. To define a route,
we simply need to add an item to the routes
array that's registered with
the NgModule
imports
metadata in this file.
To create a route to our messages feature, we need to specify a relative route
path and a component to render when a user navigates to the URL route. At
the top of the file we will import the MessagesComponent
that we are
going to route to.

import { MessagesComponent } from './messages/messages.component';
Then replace const routes: Routes = [];
with the following:

const routes: Routes = [
{ path: '', component: MessagesComponent}
];
Now that we have created our routes, we need to tell Angular to use compile this
new module and route in the application. The AppModule is the top-level
module that is bootstrapped and initialised by Angular so this is where we
will import our new messages feature module. Open file
web-app/src/app/app.module.ts
and add an import for our new feature at the
top of the file.

import { MessagesModule } from "./messages/messages.module";
And add the MessagesModule
module to the to the NgModule
imports
array.

@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
MessagesModule, // <- Add new module
GraphQLModule,
HttpClientModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Within the same directory, our app.component.html
HTML page currently still
displays the Angular CLI generated code. We need to update this to allow
routed content to display within a designated area of our page. Replace the
contents of this file with the following:

<router-outlet></router-outlet>
If we run ng serve
, we will be able to see the contents from our routed
messages/messages/messages.component.html
file within our new messages
feature module.
Building the user interface
Now that we've done all the wiring up, we can now focus on creating the user
interface. Open the messages/messages/messages.component.html
file and
insert the following code:

<div class="messages">
<header class="header">
<img class="slack-image" src="assets/logo.png" />Slack messaging
</header>
<div class="messages__conversation">
<app-view-conversation></app-view-conversation>
<app-create-message></app-create-message>
</div>
</div>
Open the file messages/messages/messages.component.ts
and take a look at the
@Component
metadata.
A component can bind to a HTML template by registering a template
or
templateUrl
property within the @Component
metadata. Angular components let
us specify a custom HTML tag to allow components to reference eachother
within their HTML template. This is defined by using a property called
selector
to associate it with a component.
In our example, the code structure uses a messages
class to wrap our entire
feature component, a header and a div with class
messages__conversation
that contains the component selectors
app-view-conversation
from the view-conversation.component.ts
file and
app-create-message
from the create-message.component.ts
file that we
created earlier. Recall we previously added ViewConversationComponent
and
CreateMessageComponent
to our module declarations array in
messages.module.ts
. By doing this, we are able to use the selectors
to reference our components anywhere within our feature module.
If you would like to include a image such as a logo.png
file at this point,
you can add one in the assets folder located at: wep-app/assets
.
If you're curious about the underscore convention used in our HTML classes. This is a common pattern called Block, Element, Modifier (BEM), used for specifying composition relationships within HTML documents and is used throughout this tutorial.
If we check our page in the web-browser, we can see content shown in all our components from using component selectors.
Display the list of messages
To create the user interface for a list of messages, we will need to change a triad of closely related files.
- A Typescript file to hold all component behaviours and data.
- An HTML file to describe our component layout with data.
- An SCSS styles file to define the look and feel for our layout of messages.
The Typescript Component file
The component file (ending in component.ts) holds all the behaviours of the components interface. It provisions data in the template using data-binding and handles all events, whether the event is a new message sent back from the server or a users click on a 'send' button.
Open the messages/view-conversation/view-conversation.component.ts
typescript file. After the
import statements, we will first need to define a
view-model
for our HTML template. This interface will represent the shape of data for a
single message that our template expects to bind to.

import { Component, OnInit } from '@angular/core';
interface MessageViewModel {
id: string;
sent: Date;
text: string;
user: {
name: string;
avatarUrl: string;
colour: string;
};
fromYou: boolean;
}
@Component({
//...
})
// Existing code...
Then we need to define a messages component property that will hold the
actual data of all our messages in an array. Each message object in the array
will be structured with the above view-model interface, which is defined by
using a typed array Array<MessageViewModel>
.

interface MessageViewModel {
//...
}
@Component({
selector: 'app-view-conversation',
templateUrl: './view-conversation.component.html',
styleUrls: ['./view-conversation.component.scss']
})
export class ViewConversationComponent implements OnInit {
messages: Array<MessageViewModel> = []; // <- New property to hold messages
constructor() {}
ngOnInit() {}
}
The HTML template file
Next we need to create the markup that we will later use to format the layout of the array of messages held in the component. First we will create a static template but without binding to component data.
Open the messages/view-conversation/view-conversation.component.html
file
and replace the contents with the following:

<div class="view-conversation">
<div class="view-conversation__list">
<div class="view-conversation__item"
[ngClass]="{'view-conversation__item--you': false}"> <!-- <- Directive binding -->
<span class="view-conversation__time">9:43 AM</span>
<div class="view-conversation__message-animation"
[ngStyle]="{'border-color': '#c16fe0'}"><!-- <- Directive binding -->
<div class="view-conversation__message">
<img class="view-conversation__avatar"
[src]="'https://lh3.googleusercontent.com/a-/AAuE7mAuiepkKGsj-1L9lhHSHtnQsXlVqHEQfJqzOoGz=s96-cc-rg'" /><!-- <- Directive binding -->
<strong class="view-conversation__name">
Shane Edwards
</strong><br />
Hi, how's it going?
</div>
</div>
</div>
</div>
</div>
Again we've used BEM to structure our HTML. We have an element with class
view-conversation
, which is a block to surround everything in our component
and then various elements within this block denoted with class names prefixed
with view-conversation__
.
You may have noticed that not all of this template is plain old HTML. We have
Angular built-in directive
bindings such as [ngClass]
, [ngStyle]
and [src]
within the template that
enables the values of each attribute to change when the corresponding
properties that they are bound to change from within the Typescript component.
If we open the browser window again at http://localhost:4200, we can see the changes to our UI that show our hard-coded message, avatar and time sent (currently not so exciting, but we have to start somewhere). Lets add some styles to our user interface.
The SCSS styles file
The styles for our component will be written with SCSS (aka SASS), a preprocessor scripting language to compile CSS that helps with maintaining complex stylesheets. The style rules are again formatted with BEM. Class names when compiled will correspond to HTML class names.
Styles are associated with a component by specifying a styleUrls
property
within a @Component
's metadata. This is automatically set up when creating a
new component with the Angular CLI.
Replace the contents of the file messages/view-conversation/view-conversation.component.scss
with the following:

.view-conversation {
display : flex;
flex-direction : column;
margin : 0;
padding : .5rem;
padding-bottom : 0;
/*max-height : 400px;*/
overflow-y : auto;
list-style-type : none;
background-color : #f5f5f5;
* {
line-height : 1.4rem;
box-sizing: border-box;
font-family: 'Helvetica Neue', Arial;
}
h1 {
font-weight : normal;
}
&__heading {
font-size : 1.8rem;
}
&__list {
position : relative;
}
&__item {
align-self : flex-start;
margin-left : 5%;
width : 95%;
+ .view-conversation__item {
margin-top : 1rem;
}
&--you {
margin-left : 0;
}
}
&__time {
display : block;
margin-bottom : .3rem;
font-size : .8rem;
color : #666;
}
&__message {
padding : 1rem;
border-radius : 3px;
}
&__message-animation {
position : relative;
overflow : hidden;
border-radius : 3px;
background-color : #fff;
border-left : 5px solid #dca629;
}
&__message-cover {
position : absolute;
top : 0;
right : 0;
bottom : 0;
left : 0;
opacity: 0;
}
&__avatar {
float : right;
margin-left : 1rem;
margin-top : -.4rem;
margin-right : -.4rem;
max-width : 3.5rem;
}
&__name {
display : inline-block;
margin-bottom : .2rem;
}
}
In our new SCSS file we have a top-level Block class selector called
.view-conversation
, which wraps all other styles within the scope of our
component. The Element classes that we wrote in the HTML file are created by
prepending the Block name (using &
) followed by two underscores and the
Element name. Modifiers can be used by prepending the Block name followed by
two dashes and the modifier name.
BEM examples with SCSS
The following is an example SCSS block with an element and modifiers.
/* Block */
.block-name {
font-size : 1.2rem;
/* Element */
&__element-name {
display : inline-block;
margin-bottom : .2rem;
/* Optional element modifier */
&--element-modifier-name {
color : red;
}
}
/* Optional block modifier */
&--block-modifier-name {
border : 1px solid red;
}
}
When compiled, would create the following CSS rules:
/* Block compiled */
.block-name {
font-size : 1.2rem;
}
/* Element compiled */
.block-name__element-name {
display : inline-block;
margin-bottom : .2rem;
}
/* Element modifier compiled */
.block-name__element-name--element-modifier-name {
color : red;
}
/* Block modifier compiled */
.block-name--modifier-name {
border : 1px solid red;
}
Then when we want to use the Block and Element structure in our HTML layout it could look similar to this.
<div class="block-name">
<div class="block-name__element-name">
Element of block-name
</div>
</div>
And when used with optional modifiers.
<div class="block-name block-name--modifier-name">
<div class="block-name__element-name block-name__element-name--element-modifier-name">
Element of block-name
</div>
</div>
Modifiers are useful when you want to create variants of block or element styles such as visually indicating different states of system feedback to a user for things like error or success state.
Go back to your browser window, you should see the messages now with our new styles applied.
Binding the template to component state
In order to make our HTML template display messages from our component (and not
just hard-coded within our template file), we need to bind the template to the
corresponding state held in the component. Recall that we created a new
property that holds an array of messages on our component in the file
view-conversation.component.ts
. We're now going to add some fake message data within
this array to help us bind the template to the component.
Open file messages/view-conversation/view-conversation.component.ts
and
change the following code from:

export class ViewConversationComponent implements OnInit {
messages: Array<MessageViewModel> = []; // <-- Change this
// Existing code - don't change...
}
to add the following code (no code needs to be deleted):

export class ViewConversationComponent implements OnInit {
messages: Array<MessageViewModel> = [
// Include this
{
id: '1',
sent: new Date(),
text: 'This message is bound to component data',
user: {
name: 'Shane Edwards',
avatarUrl: 'https://lh3.googleusercontent.com/a-/AAuE7mAuiepkKGsj-1L9lhHSHtnQsXlVqHEQfJqzOoGz=s96-cc-rg',
colour: '0080a2'
},
fromYou: false
},
{
id: '2',
sent: new Date(),
text: 'Cool',
user: {
name: 'You',
avatarUrl: 'https://angular.io/assets/images/logos/angular/shield-large.svg',
colour: 'c16fe0'
},
fromYou: true
}
];
// Existing code - don't change...
}
We now have data that we can bind to within our template. Open file
messages/view-conversation/view-conversation.component.html
, we are going to
add the bindings by adding the code:
Note: Please take some time to read the annotations in <!-- comments -->
.

<div class="view-conversation">
<div class="view-conversation__list">
<!-- *ngFor reapeting directive for messages property -->
<div *ngFor="let message of messages"
class="view-conversation__item"
[ngClass]="{'view-conversation__item--you': message.fromYou}"><!-- Directive binding to message.fromYou -->
<span class="view-conversation__time">
<!-- Data binding to message.sent with date pipe -->
{{message.sent | date: 'shortTime'}}
</span>
<div class="view-conversation__message-animation"
[ngStyle]="{'border-color': '#' + message.user.colour}"><!-- Directive binding to message.user.colour -->
<div class="view-conversation__message">
<img class="view-conversation__avatar"
[src]="message.user.avatarUrl" /><!-- Directive binding to message.user.avatarUrl -->
<strong class="view-conversation__name">
<!-- Data binding to message.user.name -->
{{message.user.name}}
</strong><br />
<!-- Data binding to message.text -->
{{message.text}}
</div>
</div>
</div>
</div>
</div>
There are three ways we bind data from a message within this template.
ngFor directive
The *ngFor directive allows us to iterate over an array and copy the contents of the template it's attached to as many times as there are items in the array.
Inside the *ngFor=""
directive value, we have let message of messages
. What
we're doing here is binding to our messages
property in our Typescript
component (by using of messages
) and specifying its value to iterate over.
This allows each item in the array to be accessed with the message
template
variable (by using let message
).
If you recall, we added two items to our messages array. So our template will be copied two times.
Attribute directive
We briefly touched on attribute directives earlier. These directives require a map of keys (that represent things like HTML classes or CSS style rules) and boolean values that determine when the keys are used in the HTML.
The values in our case are taken from properties on the object held within the
message
variable, which we can access thanks to the *ngFor
directive.
For example, the code [ngClass]="{'new-class-name': message.fromYou}">
will
add the class new-class-name
to the element it's attached to when
message.fromYou
is a true value.
Interpolation
Interpolation allows us to simply embed expressions into HTML to calculate values. This can be used to write or bind data anywhere within our template between markup and can also be used for attribute values.
An example of interpolation between markup:
<p>{{message.interpolatedValue1}}<br /> {{message.interpolatedValue2}}</p>
We also use a date pipe in our template to format a date object into a nice readable text string. Pipes are great for transforming data reactively--given an input they return a predictable output.
If you now go back into your browser, you should see our template bound to
the messages
component data with two messages displayed.
Next, lets display the result of a GraphQL query in Angular components
Previous page: Retrieve a list of messages from Slack to build a GraphQL query