Skip to content

Styling and Refinements

We just have a few things to finish up in this lesson to polish off the application, these are:

  • Supplying the active user as an input to the message list component so we can provide different styles for the user’s own messages
  • Adding CdkScrollable from the Angular CDK to provide a better UX
  • General styling

Let’s get into it!

Active User Input

If you create a second account and add some more messages to the chat, you will notice that all of the messages have the same styling. Typically in a chat application you would have the users own messages appear on a different side of the screen.

activeUser = input.required<AuthUser>();
<ul class="gradient-bg">
@for (message of messages(); track message.created){
<li
[style.flex-direction]="
message.author === activeUser()?.email ? 'row-reverse' : 'row'
"
>

Now we are using the activeUser value to determine the flex-direction for the item. If the currently authenticated user matches the author of the message we will use row-reverse, otherwise we will use row. This will swap which side of the screen the message is aligned to.

<app-message-list
[messages]="messageService.messages()"
[activeUser]="authService.user()"
/>

General Styling

Things are generally functional now, but they don’t look good and there are still aspects that make the application awkward to use — the message input bar for example will go off the bottom of the screen if there are too many messages.

We are going to create a custom Angular Material theme for our application now — which will make things look nicer — and we will also add some styling to deal with the message bar situation.

When we first created the application we installed Angular Material and we chose the Custom theme option. If you take a look at your src/styles.scss file you might notice some interesting code like this:

// Custom Theming for Angular Material
// For more information: https://material.angular.io/guide/theming
@use '@angular/material' as mat;
html {
@include mat.theme((
color: (
theme-type: light,
primary: mat.$azure-palette,
tertiary: mat.$blue-palette,
),
));
}/* You can add global styles to this file, and also import other style files */

One of the key aspects we are going to configure here are our “palettes”. You can see we have a primary and tertiary palette by default.

There is some rather advanced customisations you can do with themeing here, but fortunately with Angular Material v3 a basic set up is now much easier than it used to be.

At a basic level, we can just swap out the default configuration with whatever colour palette we want to use.

// Custom Theming for Angular Material For more information:
// https://material.angular.io/guide/theming
@use "@angular/material" as mat;
html {
@include mat.theme(
(
color: (
theme-type: light,
primary: mat.$orange-palette,
tertiary: mat.$blue-palette,
),
)
);
}
html,
body {
height: 100%;
}
body {
margin: 0;
font-family: Roboto, "Helvetica Neue", sans-serif;
}

This lays the foundation for a nice colour scheme for our application to use, but we are still going to configure this further. In general, it is a good idea to stick to using a pre-defined palette and reference those values throughout your application, rather than hard coding colours using rgb or hex values throughout the application. This keeps everything much more consistent. What we are going to do is expose some of these Angular Material theme variables as CSS variables so that we can use them throughout the application.

To achieve this, we are going to make some changes to the way we set up our theme.

// Custom Theming for Angular Material
// For more information: https://material.angular.io/guide/theming
@use "@angular/material" as mat;
$theme: mat.define-theme(
(
color: (
theme-type: light,
primary: mat.$orange-palette,
tertiary: mat.$blue-palette,
),
)
);
:root {
--primary-color: #{mat.get-theme-color($theme, primary, 70)};
--primary-lighter-color: #{mat.get-theme-color($theme, primary, 90)};
--primary-darker-color: #{mat.get-theme-color($theme, primary, 50)};
--accent-color: #{mat.get-theme-color($theme, primary, 70)};
--accent-lighter-color: #{mat.get-theme-color($theme, primary, 90)};
--accent-darker-color: #{mat.get-theme-color($theme, primary, 50)};
--white: #ecf0f1;
}
html {
@include mat.all-component-themes($theme);
}

The general idea is that we are now defining our $theme upfront, we can then get the colours we want from the theme, and then assign that to various CSS variables. We also create our own custom white CSS variable.

The themes we use come with many variants between 0 and 100. As you can see we are creating a lighter and darker variation of both of our main colours with the values 50 for the darker variation and 90 for the lighter variation. You can play with these as you see fit or even use entirely different palettes.

Whilst we are here, let’s also add some global styles for the application.

html,
body {
height: 100%;
}
body {
margin: 0;
font-family: Roboto, "Helvetica Neue", sans-serif;
}
.container {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.gradient-bg {
background: linear-gradient(
138deg,
var(--primary-darker-color) 0%,
var(--primary-color) 100%
);
}
.spacer {
flex: 1 1 auto;
}

And finally, we will add some nice little CSS animations so that our chat messages animate onto the screen:

@keyframes animateInPrimary {
0% {
transform: translate3d(-100%, 0, 0);
}
100% {
transform: translate3d(0, 0, 0);
}
}
@keyframes animateInSecondary {
0% {
opacity: 0;
}
50% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.animate-in-primary {
animation: animateInPrimary;
animation: animateInPrimary;
animation-duration: 750ms;
}
.animate-in-secondary {
animation: animateInSecondary ease-in 1;
animation: animateInSecondary ease-in 1;
animation-duration: 750ms;
}

Home Component Styles

The styling mostly looks pretty good at this point, but the layout of the HomeComponent is still a bit wonky. We are going to add some component level styles now to fix that up.

styles: [
`
.container {
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
}
mat-toolbar {
box-shadow: 0px -7px 11px 0px var(--accent-color);
}
app-message-list {
height: 100%;
width: 100%;
}
app-message-input {
position: fixed;
bottom: 0;
}
`,
],

A lot of this is just general styling to make things look nice, but the most important part is the position: fixed that we are applying to app-message-input — this is what will make the chat message input stay fixed to the bottom of the screen.

Add CdkScrollable

Things are looking pretty good now! The only thing still obviously lacking is noticeable if we have a few messages. If we add a message we can’t actually see it until we manually scroll down. Also, if we log into the application we will be seeing the oldest messages at the top of the page until we manually scroll to the bottom.

To fix this we can use CdkScrollable from the Angular CDK.

import { CdkScrollable, ScrollingModule } from '@angular/cdk/scrolling';
imports: [ScrollingModule],
<ul cdkScrollable class="gradient-bg">

This will attach the behaviour for CdkScrollable to our ul. We are going to need a reference to this element in order to trigger some behaviour. Since we have attached CdkScrollable to this element, we can use that to get a reference to it with viewChild.

scrollContainer = viewChild.required(CdkScrollable);

Now the idea is that we will call the scrollTo method on the scrollContainer whenever our message list changes. This will cause the container to scroll to the bottom.

export class MessageListComponent {
messages = input.required<Message[]>();
activeUser = input.required<AuthUser>();
scrollContainer = viewChild.required(CdkScrollable);
constructor() {
effect(() => {
if (this.messages().length && this.scrollContainer()) {
this.scrollContainer().scrollTo({
bottom: 0,
behavior: 'smooth',
});
}
});
}
}

With that… our application should now be completed! Of course, it’s never really completed — there are always improvements to be made — so feel free to play around with this however you like.