Skip to content

Instantly share code, notes, and snippets.

@andresmgsl
Last active October 22, 2021 01:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andresmgsl/bb7df8095d67e97ee4ecb23c701c55a1 to your computer and use it in GitHub Desktop.
Save andresmgsl/bb7df8095d67e97ee4ecb23c701c55a1 to your computer and use it in GitHub Desktop.
Create new monorepo

Nx Monorepo PlayBook

In this short tutorial we'll create a simple monorepo workspace using Nx, with two (2) apps, the first in angular an the other in react, also, there'll be two (2) APIs, NestJS and Django. The code of this would be later added here, in a github repo.

I strongly recommend you to use VSCode and install the NX Console Extention Its just my opinion, any other editor or extention could be used 👍

without furthermore words, lets start.

Creating the workspace

First we have to run the follow command, and create a simple workspace (not angular, react o frameworklike, just a empty workspace). For official doc, you can check this out.

npx create-nx-workspace@latest

then, its not mandatory but dont hurt at all so, we add NX globally

yarn global add nx

# or 

npm install -g nx

With this, we have the base.

Creating frontend Apps

Now, lets add the firsts apps, for this example I would add 2 apps, an Angular App, and a React App, and a ts library with a simple , we need to install some dependencies. Lets do it:

For Angular App

npm install --save-dev @nrwl/angular

For React App

npm install --save-dev @nrwl/react

With this, now is time to run some NX commands. The best way is use the Nx CLI to create the apps, because its wrapped on top of angular cli, extending its functionality and providing extra features. Lets start with Angular, there is two ways:

Using Nx console in VSCode

Use the console, is very intuitive. (TODO: Extend with images or video) Screenshot from 2021-10-03 17-45-33

Using the terminal

The most probably scenario if you pick this way, will be expend some time reading the official documentation for the Nx CLI. In any case, I'll let you here the analogous command for the Nx console way

nx generate @nrwl/angular:application --name=wallet-playground --style=scss --inlineTemplate --skipTests --no-interactive

# or if you dont have Nx CLI instaled globally

npx nx generate @nrwl/angular:application --name=wallet-playground --style=scss --inlineTemplate --skipTests --no-interactive

For the React app, the process is pretty much the same, but the Nx command I used is:

npx nx generate @nrwl/react:application --name=landing --style=styled-components --pascalCaseFiles --routing --no-interactive

That's it, we have a simple monorepo with two frontend apps ready to use. Now, is time to add a TS library both apps can use

Creating the shared library

The goal is create a simple TS framework agnostic library. As you'd imagine, creating this TS library is very similar to the last process, we could use Nx Console or Nx CLI, you'll obtain the same result. Thats why i only going to let here ther direct Nx CLI command:

npx nx generate @nrwl/workspace:library --name=counter --strict --no-interactive

Now its up to you, added some logic and connect the apps, get fun while do it. The last section will be a FAQ of all the bugs I have encounter until now, hoping it help some of you.

FAQ and Bugs

  1. Sometimes you'd see a webpack error like this

Screenshot from 2021-10-03 20-49-17

To solve it, you've to create a webpack.config.js file, and add every module for error present in the app. For the above case will be

module.exports = (config) => {
  config.resolve.fallback = {
    crypto: false
  };

  return config;
};

Now, we have to change the webpack compiler used by angular, normally its in the angular.json file, but in Nx monorepo, we'll find it in the workspace.json, inside the app, in the build and serve options. See the images below for more details

Screenshot from 2021-10-04 00-03-48

you have to install "@angular-builders/custom-webpack": "^12.1.2" in your package.json devDependencies, and then, add in your workspace.json the correct executor: For build is -> @angular-builders/custom-webpack:browser and for serve is -> @angular-builders/custom-webpack:dev-server

In the same file, after add the custom webpack, dont forget to reference the new webpack.config.js file we just created.

   "customWebpackConfig": {
      "path": "apps/dapps/angular-counter-sample-dapp/webpack.config.js",
      "mergeStrategies": {
        "externals": "replace"
      }
    }

And finally, add this line "allowSyntheticDefaultImports": true to the compilerOptions in tsconfig.js. And thats it, all things will be working now

Dark theme mode using angular material.

In this short tutorial we will learn how to implement a custom theme in material and how to add a dark theme mode too.

First, im gonna recommend to use this website because it will help to generate part of the code. The rest, i'll teach how to do it

Material theme

First, lets create the theme, right now using thease three colors #ffc407 #ffa807 #e20000 we'll obtain the following material custom theme code

// Foreground Elements

// Light Theme Text
$dark-text: #000000;
$dark-primary-text: rgba($dark-text, 0.87);
$dark-accent-text: rgba($dark-primary-text, 0.54);
$dark-disabled-text: rgba($dark-primary-text, 0.38);
$dark-dividers: rgba($dark-primary-text, 0.12);
$dark-focused: rgba($dark-primary-text, 0.12);

$mat-light-theme-foreground: (
  base:              black,
  divider:           $dark-dividers,
  dividers:          $dark-dividers,
  disabled:          $dark-disabled-text,
  disabled-button:   rgba($dark-text, 0.26),
  disabled-text:     $dark-disabled-text,
  elevation:         black,
  secondary-text:    $dark-accent-text,
  hint-text:         $dark-disabled-text,
  accent-text:       $dark-accent-text,
  icon:              $dark-accent-text,
  icons:             $dark-accent-text,
  text:              $dark-primary-text,
  slider-min:        $dark-primary-text,
  slider-off:        rgba($dark-text, 0.26),
  slider-off-active: $dark-disabled-text,
);

// Dark Theme text
$light-text: #ffffff;
$light-primary-text: $light-text;
$light-accent-text: rgba($light-primary-text, 0.7);
$light-disabled-text: rgba($light-primary-text, 0.5);
$light-dividers: rgba($light-primary-text, 0.12);
$light-focused: rgba($light-primary-text, 0.12);

$mat-dark-theme-foreground: (
  base:              $light-text,
  divider:           $light-dividers,
  dividers:          $light-dividers,
  disabled:          $light-disabled-text,
  disabled-button:   rgba($light-text, 0.3),
  disabled-text:     $light-disabled-text,
  elevation:         black,
  hint-text:         $light-disabled-text,
  secondary-text:    $light-accent-text,
  accent-text:       $light-accent-text,
  icon:              $light-text,
  icons:             $light-text,
  text:              $light-text,
  slider-min:        $light-text,
  slider-off:        rgba($light-text, 0.3),
  slider-off-active: rgba($light-text, 0.3),
);

// Background config
// Light bg
$light-background:    #fafafa;
$light-bg-darker-5:   darken($light-background, 5%);
$light-bg-darker-10:  darken($light-background, 10%);
$light-bg-darker-20:  darken($light-background, 20%);
$light-bg-darker-30:  darken($light-background, 30%);
$light-bg-lighter-5:  lighten($light-background, 5%);
$dark-bg-tooltip:     lighten(#2c2c2c, 20%);
$dark-bg-alpha-4:     rgba(#2c2c2c, 0.04);
$dark-bg-alpha-12:    rgba(#2c2c2c, 0.12);

$mat-light-theme-background: (
  background:               $light-background,
  status-bar:               $light-bg-darker-20,
  app-bar:                  $light-bg-darker-5,
  hover:                    $dark-bg-alpha-4,
  card:                     $light-bg-lighter-5,
  dialog:                   $light-bg-lighter-5,
  tooltip:                  $dark-bg-tooltip,
  disabled-button:          $dark-bg-alpha-12,
  raised-button:            $light-bg-lighter-5,
  focused-button:           $dark-focused,
  selected-button:          $light-bg-darker-20,
  selected-disabled-button: $light-bg-darker-30,
  disabled-button-toggle:   $light-bg-darker-10,
  unselected-chip:          $light-bg-darker-10,
  disabled-list-option:     $light-bg-darker-10,
);

// Dark bg
$dark-background:     #2c2c2c;
$dark-bg-lighter-5:   lighten($dark-background, 5%);
$dark-bg-lighter-10:  lighten($dark-background, 10%);
$dark-bg-lighter-20:  lighten($dark-background, 20%);
$dark-bg-lighter-30:  lighten($dark-background, 30%);
$light-bg-alpha-4:    rgba(#fafafa, 0.04);
$light-bg-alpha-12:   rgba(#fafafa, 0.12);

// Background palette for dark themes.
$mat-dark-theme-background: (
  background:               $dark-background,
  status-bar:               $dark-bg-lighter-20,
  app-bar:                  $dark-bg-lighter-5,
  hover:                    $light-bg-alpha-4,
  card:                     $dark-bg-lighter-5,
  dialog:                   $dark-bg-lighter-5,
  tooltip:                  $dark-bg-lighter-20,
  disabled-button:          $light-bg-alpha-12,
  raised-button:            $dark-bg-lighter-5,
  focused-button:           $light-focused,
  selected-button:          $dark-bg-lighter-20,
  selected-disabled-button: $dark-bg-lighter-30,
  disabled-button-toggle:   $dark-bg-lighter-10,
  unselected-chip:          $dark-bg-lighter-20,
  disabled-list-option:     $dark-bg-lighter-10,
);

// Compute font config
@include mat-core($fontConfig);

// Theme Config

body {
  --primary-color: #ffc407;
  --primary-lighter-color: #ffedb5;
  --primary-darker-color: #ffaf04;
  --text-primary-color: #{$dark-primary-text};
  --text-primary-lighter-color: #{$dark-primary-text};
  --text-primary-darker-color: #{$dark-primary-text};
}   
$mat-primary: (
  main: #ffc407,
  lighter: #ffedb5,
  darker: #ffaf04,
  200: #ffc407, // For slide toggle,
  contrast : (
    main: $dark-primary-text,
    lighter: $dark-primary-text,
    darker: $dark-primary-text,
  )
);
$theme-primary: mat-palette($mat-primary, main, lighter, darker);


body {
  --accent-color: #ffa807;
  --accent-lighter-color: #ffe5b5;
  --accent-darker-color: #ff8d04;
  --text-accent-color: #{$dark-primary-text};
  --text-accent-lighter-color: #{$dark-primary-text};
  --text-accent-darker-color: #{$dark-primary-text};
}   
$mat-accent: (
  main: #ffa807,
  lighter: #ffe5b5,
  darker: #ff8d04,
  200: #ffa807, // For slide toggle,
  contrast : (
    main: $dark-primary-text,
    lighter: $dark-primary-text,
    darker: $dark-primary-text,
  )
);
$theme-accent: mat-palette($mat-accent, main, lighter, darker);


body {
  --warn-color: #e20000;
  --warn-lighter-color: #f6b3b3;
  --warn-darker-color: #d60000;
  --text-warn-color: #{$light-primary-text};
  --text-warn-lighter-color: #{$dark-primary-text};
  --text-warn-darker-color: #{$light-primary-text};
}   
$mat-warn: (
  main: #e20000,
  lighter: #f6b3b3,
  darker: #d60000,
  200: #e20000, // For slide toggle,
  contrast : (
    main: $light-primary-text,
    lighter: $dark-primary-text,
    darker: $light-primary-text,
  )
);
$theme-warn: mat-palette($mat-warn, main, lighter, darker);

If at the time you read this, the website metion above is down, you can use the code and replace the colors, also im gonna let the following image for futher reference

paleta_colores_dark

after having thease, we gonna refact the default angular style folder. As you may know, when a new angular project is created, we only have one .scss file (called style.scss). I always change the struct a little, having something like this

Screenshot from 2021-10-06 11-30-43

now, its important to know one particular behavior of material. in order to override the foreground and background element, you need to replace the sass funcions and pass your recent created variables. This would do it:

@function set-light-theme($primary, $accent, $warn) {
  @return (
    primary: $primary,
    accent: $accent,
    warn: $warn,
    is-dark: false,
    foreground: $mat-light-theme-foreground,
    background: $mat-light-theme-background,
  );
}

@function set-dark-theme($primary, $accent, $warn) {
  @return (
    primary: $primary,
    accent: $accent,
    warn: $warn,
    is-dark: false,
    foreground: $mat-dark-theme-foreground,
    background: $mat-dark-theme-background,
  );
}

Now, lets add some functionality to be able of change between light mode and dark mode

Theme Service

At this time we have set up a custom theme light theme and dark theme, but it lack in the ability to change between the two. To solve that, we gonna create a custom angular service to change the theme base in the user selection.

Lets create an angular lib using NX console or the following command

npx nx generate @nrwl/angular:library --name=dark-theme --directory=application/application/ui --no-interactive

Then, inside the new lib, we will create a directive, to add the body a class, yes, the dark mode class.

import { Directive, Input } from '@angular/core';

@Directive({
  selector: '[demobaseLabsDarkTheme]',
})
export class DarkThemeDirective {
  @Input('demobaseLabsDarkTheme') set darkThemeValue(value: boolean | null) {
    if (value !== null) {
      this.setDarkTheme(value);
    }
  }

  setDarkTheme(value: boolean) {
    const bodyClass = document.body.className;
    if (value) {
      document.body.className += ' darkMode';
    } else {
      document.body.className = bodyClass.replace('darkMode', '');
    }
  }
}

you can use another implementation or class name, Im only showing a way to do it. Next we nee a service, to handle the logic of when add or remove the dark mode class

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

@Injectable()
export class DarkThemeService {
  private _darkTheme = new BehaviorSubject<boolean>(this.defaultValue);
  isDarkThemeEnabled$ = this._darkTheme.asObservable();

  get defaultValue() {
    return localStorage.getItem('darkTheme') === 'true';
  }

  setDarkTheme(isDarkThemeOn: boolean) {
    this._darkTheme.next(isDarkThemeOn);
    localStorage.setItem('darkTheme', isDarkThemeOn.toString());
  }
}

Thats it, now, we need to use this lib in our previus custom theme app. 😄

Im not showing here how to use it in a app, but it'll like this:

  <div class="w-full mb-6 flex justify-center items-center">
    <mat-icon class="mr-1">bedtime</mat-icon>
    <mat-slide-toggle
      class="mr-1"
      (change)="toggleDarkMode(!$event.checked)"
      [demobaseLabsDarkTheme]="isDarkThemeEnabled$ | async"
      [checked]="(isDarkThemeEnabled$ | async) === false"
    >
    </mat-slide-toggle>
    <mat-icon>brightness_5</mat-icon>
  </div>

  <!-- angular component emulation -->
  <script> 
  ...
  readonly isDarkThemeEnabled$ = this._themeService.isDarkThemeEnabled$;

  constructor(
    private readonly _themeService: DarkThemeService
  ) {}


  toggleDarkMode(isDarkThemeEnabled: boolean) {
    this._themeService.setDarkTheme(isDarkThemeEnabled);
  }
  ...
  </script>

Current theme and Localstorage

I always love when a website remember my ui selections, its feel like a im dealing with a not so stupid app. Thats why we gonna saved the last user selection to display when the user come back.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment