Theming Angular7 ✅with CSS Variables

Theming Angular7  ✅with CSS Variables

In this post, we will build a super simple cryptocurrency listing app using Angular 7 and CSS variables.

Table of Contents

Design

Feel free to skip this section if you’re only here for the code.

I designed this simple app using Figma.

Color Scheme

Our color scheme is made up of foreground, background, primary, and error colors. Each color group has several lighter and darker variants of the base color.

For our light/dark themes, the foreground and background colors will simply swap.

Components

Next up is to create the components. Since our app is pretty small, we only have a couple of components.

The nav component, which will let our user toggle the theme.

The tile component which will display coin info.

Putting it all together, we get our target designs.

Project Setup

We are going to scaffold our app using the Angular CLI. First, we need to install it.

npm install -g @angular/cli

Then create our app.

ng new project-crypto

And finally, generate a module to hold our theming logic.

cd project-crypto 
ng generate module theme 

Code

Alright, time for the good stuff.

Define CSS Variables

Let’s start by defining out initial CSS variables. We can set them initially to reflect our light theme. Since we want our theme to be global, I have defined it using the :root selector, which will match the html element. You could use the body or some other high-level element here if you wish.

@import url("https://fonts.googleapis.com/css?family=PT+Sans:400,700");
@import url("https://fonts.googleapis.com/css?family=Inconsolata:400,700");

:root {
  /* Colors */
  --foreground-default: #08090a;
  --foreground-secondary: #41474d;
  --foreground-tertiary: #797c80;
  --foreground-quaternary: #f4faff;
  --foreground-light: #41474d;

  --background-default: #f4faff;
  --background-secondary: #a3b9cc;
  --background-tertiary: #5c7d99;
  --background-light: #ffffff;

  --primary-default: #5dfdcb;
  --primary-dark: #24b286;
  --primary-light: #b2ffe7;

  --error-default: #ef3e36;
  --error-dark: #800600;
  --error-light: #ffcecc;

  /* Shadows */
  --background-tertiary-shadow: 0 1px 3px 0 rgba(92, 125, 153, 0.5);
}

body {
  background: var(--background-default);
}

html,
body {
  margin: 0;
  padding: 0;
  font-family: "PT Sans", sans-serif;
}

styles.css

Define the themes

Next, let’s define our themes in TypeScript. These will later be used to toggle the theme by an Angular service.

Under our newly created theme module, create a new file: theme.ts

export interface Theme {
  name: string;
  properties: any;
}

export const light: Theme = {
  name: "light",
  properties: {
    "--foreground-default": "#08090A",
    "--foreground-secondary": "#41474D",
    "--foreground-tertiary": "#797C80",
    "--foreground-quaternary": "#F4FAFF",
    "--foreground-light": "#41474D",

    "--background-default": "#F4FAFF",
    "--background-secondary": "#A3B9CC",
    "--background-tertiary": "#5C7D99",
    "--background-light": "#FFFFFF",

    "--primary-default": "#5DFDCB",
    "--primary-dark": "#24B286",
    "--primary-light": "#B2FFE7",

    "--error-default": "#EF3E36",
    "--error-dark": "#800600",
    "--error-light": "#FFCECC",

    "--background-tertiary-shadow": "0 1px 3px 0 rgba(92, 125, 153, 0.5)"
  }
};

export const dark: Theme = {
  name: "dark",
  properties: {
    "--foreground-default": "#5C7D99",
    "--foreground-secondary": "#A3B9CC",
    "--foreground-tertiary": "#F4FAFF",
    "--foreground-quaternary": "#E5E5E5",
    "--foreground-light": "#FFFFFF",

    "--background-default": "#797C80",
    "--background-secondary": "#41474D",
    "--background-tertiary": "#08090A",
    "--background-light": "#41474D",

    "--primary-default": "#5DFDCB",
    "--primary-dark": "#24B286",
    "--primary-light": "#B2FFE7",

    "--error-default": "#EF3E36",
    "--error-dark": "#800600",
    "--error-light": "#FFCECC",

    "--background-tertiary-shadow": "0 1px 3px 0 rgba(8, 9, 10, 0.5)"
  }
};

theme.ts

We can add as many themes as we like here. For now, let’s just stick with light and dark themes.

Create the theme service

Our service will be responsible for: tracking the active theme, and updating the CSS variables based on the active theme.

We can use the CLI to generate our new service. Under /src/app/theme run

ng generate service theme
import { Injectable } from "@angular/core";
import { Theme, light, dark } from "./theme";

@Injectable({
  providedIn: "root"
})
export class ThemeService {
  private active: Theme = light;
  private availableThemes: Theme[] = [light, dark];

  getAvailableThemes(): Theme[] {
    return this.availableThemes;
  }

  getActiveTheme(): Theme {
    return this.active;
  }

  isDarkTheme(): boolean {
    return this.active.name === dark.name;
  }

  setDarkTheme(): void {
    this.setActiveTheme(dark);
  }

  setLightTheme(): void {
    this.setActiveTheme(light);
  }

  setActiveTheme(theme: Theme): void {
    this.active = theme;

    Object.keys(this.active.properties).forEach(property => {
      document.documentElement.style.setProperty(
        property,
        this.active.properties[property]
      );
    });
  }
}

theme.service.ts

Some things to note here:

  1. We import our theme definitions that we just created, on line 2.
  2. Lines 34–39 update our CSS variables defined in the theme. This is essentially where the magic is happening.
  3. Since, in this example app, we only have two themes, I have added some convenience functions to set the theme to light and dark directly. You can use the getAvailableThemes and setActiveTheme functions to change the theme dynamically based on user input instead.

Components

The hard work is done. Now we just need to put our building blocks together. Well, actually, first we need to create the building blocks 😁. Let’s create the components.

We will start with the nav component. Again, we can use the Angular CLI to give us a head start.

ng generate component nav
import { Component, OnInit } from "@angular/core";
import {
  faLightbulb as faSolidLightbulb,
  faDollarSign,
  IconDefinition
} from "@fortawesome/free-solid-svg-icons";
import { faLightbulb as faRegularLightbulb } from "@fortawesome/free-regular-svg-icons";
import { ThemeService } from "src/app/theme/theme.service";

@Component({
  selector: "app-nav",
  templateUrl: "./nav.component.html",
  styleUrls: ["./nav.component.css"]
})
export class NavComponent implements OnInit {
  faLightbulb: IconDefinition;
  faDollarSign = faDollarSign;

  constructor(
    private themeService: ThemeService
  ) {}

  ngOnInit() {
    this.setLightbulb();
  }

  setLightbulb() {
    if (this.themeService.isDarkTheme()) {
      this.faLightbulb = faRegularLightbulb;
    } else {
      this.faLightbulb = faSolidLightbulb;
    }
  }

  toggleTheme() {
    if (this.themeService.isDarkTheme()) {
      this.themeService.setLightTheme();
    } else {
      this.themeService.setDarkTheme();
    }

    this.setLightbulb();
  }
}

nav.component.ts

Note: I have used Font Awesome for the icons on the nav bar. If you want to do the same, you will need to install Font Awesome for Angular.

The logic for our nav component is pretty straight forward. We set our icon depending on the theme on initialization (line 22). Then we set up an event handler to toggle the theme. You can see it’s usage in the HTML below.


  
  ##### ProjectCrypto

  

nav.component.html

nav {
  height: 4rem;
  display: flex;
  align-items: center;
  padding-left: 1rem;
  padding-right: 1rem;
  background-color: var(--background-tertiary);
  color: var(--foreground-quaternary);
  font-size: 1rem;
}

nav .title {
  margin-left: auto;
  margin-right: auto;
}

nav.component.css

Notes on the nav component CSS:

  1. Line 7, and 8 are the important ones here. These are the two lines that use our previously defined CSS variables, and make this component themeable.

Next, the tile component.


  ##### {{ name }}

  
    
    {{ price | number }}
  
  <fa-icon
    [icon]="faHeart"
    (click)="onToggleFavourite()"
    class="favouriteIcon icon"
    [ngClass]="{ isFavourite: isFavourite }"
  >

tile.component.html

.container {
  display: grid;
  grid-template-columns: 0.5fr 1fr 0.5fr;
  align-items: center;
  border-radius: 0.5rem;
  background: var(--background-light);
  color: var(--foreground-tertiary);
  padding-left: 0.5rem;
  padding-right: 0.5rem;
  margin-bottom: 1rem;
  min-height: 8rem;
  box-shadow: var(--background-tertiary-shadow);
}

.container .name {
  justify-self: start;
}

.container .price {
  justify-self: center;
}

.container .icon {
  justify-self: end;
}

.favouriteIcon {
  font-size: 1.5rem;
}

.isFavourite {
  color: var(--primary-default);
}

tile.component.css
Notes:

  1. The TypeScript for our tile component doesn’t have any theming logic, so I have omitted it here.
  2. Lines 6, 7, 12, and 32 are what enable our tile component to be themeable.

Conclusion, Repo, and Further Reading

And that’s it! You now have the components and theme created. You can add the components to your base app component to wire everything up with some test data.

You can find the repo here.

Learn more about CSS Variables here.

Thanks for reading!

30s ad

Angular (Full App) with Angular Material, Angularfire & NgRx

Angular 7 with Angular Material and Firebase Cloud Firestore

Angular 5 - A 3-Step Process to Master Angular for Beginners

Angular 6 (Angular 2+) & React 16 - The Complete App Guide

Full Stack development: Angular 2/4/5/6/7 - Reusable Custom Bootstrap Modal Dialog Angular Component(3 ways)

Angular 2/4/5/6/7 - Reusable custom Bootstrap Modal dialog angular component-3 ways(code snippet with eg)&nbsp;

Angular 2/4/5/6/7 - Reusable custom Bootstrap Modal dialog angular component-3 ways(code snippet with eg) 

What are the best alternatives for angular js?

<img src="https://moriohcdn.b-cdn.net/193902114c.png">There are numerous frameworks and libraries used across the globe. If not angular, there are platforms like React, Vue, Aurelia and so on for app development.

There are numerous frameworks and libraries used across the globe. If not angular, there are platforms like React, Vue, Aurelia and so on for app development.

Angular 8 Node & Express JS File Upload

Angular 8 Node & Express JS File Upload

In this Angular 8 and Node.js tutorial, we are going to look at how to upload files on the Node server. To create Angular image upload component, we will be using Angular 8 front-end framework along with ng2-file-upload NPM package; It’s an easy to use Angular directives for uploading the files.

In this Angular 8 and Node.js tutorial, we are going to look at how to upload files on the Node server. To create Angular image upload component, we will be using Angular 8 front-end framework along with ng2-file-upload NPM package; It’s an easy to use Angular directives for uploading the files.

We are also going to take the help of Node.js to create the backend server for Image or File uploading demo. Initially, we’ll set up an Angular 8 web app from scratch using Angular CLI. You must have Node.js and Angular CLI installed in your system.

We’ll create the local server using Node.js and multer middleware. Multer is a node.js middleware for handling multipart/form-data, which is primarily used for uploading files. Once we are done setting up front-end and backend for our File uploading demo then, we’ll understand step by step how to configure file uploading in Angular 8 app using Node server.

Prerequisite

In order to show you Angular 8 File upload demo, you must have Node.js and Angular CLI installed in your system. If not then check out this tutorial: Set up Node JS

Run following command to install Angular CLI:

npm install @angular/cli -g

Install Angular 8 App

Run command to install Angular 8 project:

ng new angular-node-file-upload

# ? Would you like to add Angular routing? No
# ? Which stylesheet format would you like to use? CSS
cd angular-node-file-upload

Show Alert Messages When File Uploaded

We are going to install and configure ngx-toastr an NPM package which helps in showing the alert message when the file is uploaded on the node server.

npm install ngx-toastr --save

The ngx-toastr NPM module requires @angular/animations dependency:

npm install @angular/animations --save

Then, add the ngx-toastr CSS in angular.json file:

"styles": [
    "src/styles.css",
    "node_modules/ngx-toastr/toastr.css"
]

Import BrowserAnimationsModule and ToastrModule in app.module.ts file:

import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ToastrModule } from 'ngx-toastr';
 
@NgModule({
  imports: [
    CommonModule,
    BrowserAnimationsModule, // required animations module
    ToastrModule.forRoot() // ToastrModule added
  ]
})

export class AppModule { }

Install & Configure ng-file-upload Directive

In this step, we’ll Install and configure ng-file-upload library in Angular 8 app. Run command to install ng-file-upload library.

npm install ng2-file-upload

Once the ng2-file-upload directive is installed, then import the FileSelectDirective and FormsModule in app.module.ts. We need FormsModule service so that we can create the file uploading component in Angular.

import { FileSelectDirective } from 'ng2-file-upload';
import { FormsModule } from '@angular/forms';

@NgModule({
  declarations: [
    FileSelectDirective
  ],
  imports: [
    FormsModule
  ]
})

export class AppModule { }

Setting Up Node Backend for File Upload Demo

To upload the file on the server, we need to set up a separate backend. In this tutorial, we will be using Node & Express js to create server locally along with multer, express js, body-parser, and dotenv libraries.

Run command to create backend folder in Angular app’s root directory:

mkdir backend && cd backend

In the next step, create a specific package.json file.

npm init

Run command to install required dependencies:

npm install express cors body-parser multer dotenv --save

In order to get rid from starting the server again and again, install nodemon NPM package. Use –-save-dev along with the npm command to register in the devDependencies array. It will make it available for development purpose only.

npm install nodemon --save-dev

Have a look at final pacakge.json file for file upload demo backend:

{
  "name": "angular-node-file-upload",
  "version": "1.0.0",
  "description": "Angualr 8 file upload demo app",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js"
  },
  "author": "Digamber Rawat",
  "license": "ISC",
  "dependencies": {
    "body-parser": "^1.19.0",
    "cors": "^2.8.5",
    "dotenv": "^8.0.0",
    "express": "^4.17.1",
    "multer": "^1.4.1"
  },
  "devDependencies": {
    "nodemon": "^1.19.1"
  }
}

Create a file by the name of server.js inside backend folder:

Configure Server.js

To configure our backend we need to create a server.js file. In this file we’ll keep our backend server’s settings.

touch server.js

Now, paste the following code in backend > server.js file:

const express = require('express'),
  path = require('path'),
  cors = require('cors'),
  multer = require('multer'),
  bodyParser = require('body-parser');

// File upload settings  
const PATH = './uploads';

let storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, PATH);
  },
  filename: (req, file, cb) => {
    cb(null, file.fieldname + '-' + Date.now())
  }
});

let upload = multer({
  storage: storage
});

// Express settings
const app = express();
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
  extended: false
}));

app.get('/api', function (req, res) {
  res.end('File catcher');
});

// POST File
app.post('/api/upload', upload.single('image'), function (req, res) {
  if (!req.file) {
    console.log("No file is available!");
    return res.send({
      success: false
    });

  } else {
    console.log('File is available!');
    return res.send({
      success: true
    })
  }
});

// Create PORT
const PORT = process.env.PORT || 8080;
const server = app.listen(PORT, () => {
  console.log('Connected to port ' + PORT)
})

// Find 404 and hand over to error handler
app.use((req, res, next) => {
  next(createError(404));
});

// error handler
app.use(function (err, req, res, next) {
  console.error(err.message);
  if (!err.statusCode) err.statusCode = 500;
  res.status(err.statusCode).send(err.message);
});

Now, while staying in the backend folder run the below command to start the backend server:

nodemon server.js

If everything goes fine then you’ll get the following output:

[nodemon] 1.19.1
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node server.js`
Connected to port 8080

Create Angular 8 File Upload Component

In this last step, we are going to create a file upload component in Angular 8 app using Express js API.

Get into the app.component.ts file and include the following code:

import { Component, OnInit } from '@angular/core';
import { FileUploader } from 'ng2-file-upload/ng2-file-upload';
import { ToastrService } from 'ngx-toastr';

const URL = 'http://localhost:8080/api/upload';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent implements OnInit {
  public uploader: FileUploader = new FileUploader({
    url: URL,
    itemAlias: 'image'
  });

  constructor(private toastr: ToastrService) { }

  ngOnInit() {
    this.uploader.onAfterAddingFile = (file) => {
      file.withCredentials = false;
    };
    this.uploader.onCompleteItem = (item: any, status: any) => {
      console.log('Uploaded File Details:', item);
      this.toastr.success('File successfully uploaded!');
    };
  }

}

Go to app.component.html file and add the given below code:

<div class="wrapper">
  <h2>Angular Image Upload Demo</h2>

  <div class="file-upload">
    <input type="file" name="image" ng2FileSelect [uploader]="uploader" accept="image/x-png,image/gif,image/jpeg" />
    <button type="button" (click)="uploader.uploadAll()" [disabled]="!uploader.getNotUploadedItems().length">
      Upload
    </button>
  </div>

</div>

Now, It’s time to start the Angular 8 app to check out the File upload demo in the browser. Run the following command:

ng serve --open

Make sure your NODE server must be running to manage the backend.

When you upload the image from front-end you’ll see your image files are saving inside the backend > uploads folder.

Conclusion

In this Angular 8 tutorial, we barely scratched the surface related to file uploading in a Node application. There are various other methods available on the internet through which you can achieve file uploading task quickly. However, this tutorial is suitable for beginners developers. I hope this tutorial will surely help and you if you liked this tutorial, please consider sharing it with others.