Drag-n-drop in Angular as Observable

Observables are really fantastic and I learned to love them. The concept may be intimidating at first, especially the whole RxJS landscape. But it definitely pays off.

Implementing drag and drop using Observables is actually quite simple and super handy. Here is my implementation. I also write for my future self, to save myself time. And I know, it will be useful for you as well.

import { fromEvent, mergeMap, Observable, takeUntil } from "rxjs";

export function getDragAndDropObservable(element: HTMLElement): Observable<Event> {
  const down = fromEvent(element, 'mousedown');
  const move = fromEvent(element, 'mousemove');
  const up = fromEvent(element, 'mouseup');

  const dragAndDrop = down.pipe(mergeMap(() => move.pipe(takeUntil(up))));
  return dragAndDrop;
}

Example of usage

// Somewhere in your component
// throttleTime is optional, but recommended for performance reasons
ngAfterViewInit(): void {
  getDragAndDropObservable(this.myElementRef.nativeElement)
  .pipe(throttleTime(20))
  .subscribe(event => {
    doSomething(event as MouseEvent);
  });
}

Happy coding!

Jest vs “import statement outside a module”

Jest can be very helpful framework when testing your application. Until it isn’t. Sometimes it gets in the way, and to configure it properly can be quite tricky.

Problematic use case

So, you have just learned that it is better to use specific imports instead of the { destruct } syntatx. Why? Because it makes smaller size of the bundle. So, for example when you are using lodash, you want to use import cloneDeep from 'lodash-es/cloneDeep'; instead of import { cloneDeep } from 'lodash-es';.

// This references the whole lodash-es npm package, and then pickups the cloneDeep function from it.
// Because of referencing the whole package, it increases the bundle size.
import { cloneDeep } from 'lodash-es';

// This only references the specific lodash function, and its dependencies.
// So the resulting bundle is smaller.
import cloneDeep from 'lodash-es/cloneDeep';

It is even recommended by Lodash authors.

Jest vs ES modules

As the name suggests, lodash-es uses ES6 modules syntax. Jest does not like it, and the unit tests will fail with syntax error. The error message will be something like this SyntaxError: Cannot use import statement outside a module. Not very helpful message, but try to search it, DuckDuckGo will yield ton of results.

How to fix it

Update your jest.config.js file like this:

// jest.config.js
module.exports = {
// ... rest of Jest configuration is not shown
    transform: {},
    transformIgnorePatterns: ['/!node_modules\\/lodash-es/*'],
	moduleNameMapper: {
		'^lodash-es$': 'lodash',
		'^lodash-es/cloneDeep$': 'lodash/cloneDeep',
		'^lodash-es/anyOtherLodashModule$': 'lodash/anyOtherLodashModule',
	},
}

Happy coding!

Do not throw Error in constructors

Suppose that you need to instantiate a class, which takes some dependencies as parameters in constructor. I am mostly using Angular, so those dependencies are some injectable services in my case. Something like this:

@Injectable({ providedIn: 'root' })
export class PriceManagementClient {
	private readonly allowedOptions: SomeOption[] = [];

	constructor(private readonly configurationService: ConfigurationService) {
		this.allowedOptions = this.configurationService.getValue('allowedOptions');

		if (this.allowedOptions === undefined) {
			throw new Error('Missing allowedOptions');
		}
	}
	
	doSomethingUseful() {
    // ...
	}
}

What do you think will happen if you try to instantiate this class? What will happen with that Error? How will it be handled?

In normal situations, Javascript engine is able to print stacktrace when the Error is thrown, so you can track where the problem is. But in this case, the Angular application will just crash, and no error will be printed in dev console. Which makes it extremely dificult to track it down, when you have no idea what could have gone wrong. Most likely you will only see blank screen, and the app will auto redirect to root route. Not very helpful.

Root cause is that because you have thrown Error during class instantiation, you have effectly prevented that instantion. So no instance has been created. So slightly better approach is to make sure, that the problem is logged in dev console before you throw it.

constructor() {
	// Nested somewhere ...
	const errMessage = 'We have some critical problem';
	console.error(errMessage);
	throw new Error(errMessage);
}

But that still results in whitescreen situation. So, if you really need to throw Error inside a constructor, do that it in such a way, that you do not prevent instantiation. You can use setTimeout(), so that the instance is first created, and then the Error is thrown, like this:

constructor() {
	// Nested somewhere ...
	const errMessage = 'We have some critical problem';
	console.error(errMessage);
	setTimeout(() => {
		throw new Error(errMessage);
	})
}

It still looks like a code smell though 🙂

ESLint sort-imports versus VS Code

Don’t get me wrong, both VS Code and ESLint are great tools, but sometimes they do not play nice together. Both have plugins, which are extending their functionality, and sometimes those plugins goes againts each other.

One of such cases is ESLint and automatic sorting of imports in Typescript files.

You can instruct VS Code to sort imports for you on file save, but this option conflicts with similar option in ESLint. It seems to me, that there is simply too many options, and nobody is able to test all combinations. So some combinations result in unexpected behaviour, which may be hard to reproduce, because developers are using different IDEs with different plugins and settings.

Technical details

This VSCode’s settings.json may go against ESLint. See the rule editor.codeActionsOnSave / source.organizeImports. When you are using ESLint, then you may need to turn it OFF.

Beware of source.organizeImports

My ESLint rules for import/order

The main point is this. I’m explicitly setting the groups property, even when it has the default values, because then I can be sure, what the value actually is. Notice also the the alphabetize settings.

"plugins": ["@nrwl/nx", "import"],
//
// and then later:
// 
"import/order": ["error", {
					"groups": ["index", "sibling", "parent", "internal", "external", "builtin", "object", "type"],
					"alphabetize": {
						"order": "asc", /* sort in ascending order. Options: ['ignore', 'asc', 'desc'] */
						"caseInsensitive": true /* ignore case. Options: [true, false] */
					}
				}],

Also I have a Husky pre-commit hook together with lint-staged, which is configured to autofix all eslint problems it founds. Because of lint-staged it checks only the files, which were actually modified, and would be included in the next commit. Here is a nice article explaing why you would like to have such a setup.

For the full example of how to configure .eslintrc file please refer the official documentation.

Problem with NGCC

NGCC stands for Angular Compatibility Compiler, which recompiles various npm packages to Ivy format. Most of the time it “just works”, expect when it does not.

There are configuration options for ngcc, which you can specify in ngcc.config.js file in root folder of your project. But sometimes this seems not enough. Sometimes the same code may trigger the error, while when used in different context it works ok.

Consider this example:

{ provide: MAT_SNACK_BAR_DEFAULT_OPTIONS, useValue: { verticalPosition: 'top' } }

When used in the main app.module.ts file, it will trigger error similar to the one below. But if used in a sub module file, which is loaded later, it compiles without difficulties.

So remember, when you see error related to ngcc, check your main module and submodule files. You may need to declare some providers repeatedly in the submodules.

Problem with providedIn: root

Problem with dependency on Angular Material may also happen when some class in lazy-loaded module is annotated with @Injectable({providedIn: 'root'}). Then those Material dependencies have to be declared in root app.module.ts. But I could not use that, as it caused other problems in our solution.

Using providedIn: 'any' helped. It may not be the ideal solution, because it may lead to having multiple instances of the same module, instead of singletons. So there are definitely cases when you absolute do not want to use it, for examples for Stores.

Unused imports

Interestingly, when there is a forgotten import it may affect the build in CI/CD pipeline. The import should have been tree-shaken away. But for some not-obvious reason, the pipeline sometimes does not remove the import, which results in a build crashing, while it passes ok locally.

And yes, I am going to fix this with Husky pre-commit hooks, but that is not yet ready at the time of writing.

Happy coding.

———————————————————-

Error: Tried to overwrite /opt/rb3/node_modules/.pnpm/@angular+material@11.2.13_83d90f83a63ec4f36f90a5e25a1aad3a/node_modules/@angular/animations/animations.d.ts.__ivy_ngcc_bak with an ngcc back up file, which is disallowed.
at NewEntryPointFileWriter.InPlaceFileWriter.writeFileAndBackup (/opt/rb3/node_modules/.pnpm/@angular+compiler-cli@11.2.14_d7da94ea75fca089b21302a3a7dac349/node_modules/@angular/compiler-cli/ngcc/src/writing/in_place_file_writer.js:58:27)

headscratcher

Load Image programatically with Promise

Image can be loading either in a standard way via setting the src property on the Image tag, or you can do it programatically. If you do it programatically, you can enhance the image loading with some magic, eg show a animated CSS placeholder while the image is being loaded. Or you can try several URLs.

It may come handy. At least for my future self 🙂

export function loadImage(url: string): Promise<HTMLImageElement> {
	const promise: Promise<HTMLImageElement> = new Promise((resolve, reject) => {
		const image = new Image();
		
		image.addEventListener('load', () => {
			resolve(image);
		});

		image.addEventListener('error', (err) => reject(err));

		image.src = url;
	});

	return promise;
}

And yes, you can even load image from multiple URLs. It was one exotic use case when I needed that. Long story about unreliable legacy software …

/** 
* Try to load image from array imageUrls
* The function returns the first succesfull attempt
*/
export async function loadImageWithFallbackUrl(imageUrls: string[]): Promise<HTMLImageElement> {
	let imageEl: HTMLImageElement = undefined;

	// Cant use forEach loop here
	for (let i = 0; i < imageUrls.length; i++) {
		const imageUrl = imageUrls[i];

		if (imageEl) {
			// Image has already loaded
			continue;
		}

		try {
			imageEl = await loadImage(imageUrl);
		} catch {
			imageEl = undefined;
			continue;
		}
	}

	const message = imageEl ? 'Image has loaded' : 'Image was not found on any URL: ' + imageUrls;
	console.log(message);

	return imageEl;
}

No, this image is not relevant. It is a bonus for your eyes.

Mock window.location in unit tests

When you run unit tests (I am using Jestjs) on functions which read window.location.href or window.location.search, you need to somehow mock the value.

Problem is that when you try to edit the value of window.location.search it will not work. Neither will work overriding it by Object.defineProperty(). But there is a simple workaround solution, and that is using history.pushState(). By that you can change the current URL to whatever value you need, and then read it by tested function.

Example

beforeEach(() => {
 const search = `a=aaaa&b=bbb`;
 window.history.pushState({}, 'Test Page Title', `/any/url/you/like?${search}`);
});

Coding drunk make programming fun again book | StareCat.com
Well… I just liked the image 🙂

Debugging NestJS app in NRWL NX workspace

Debugging can save you ton of time. But sometimes to set up debugging configuration correctly can be a nightmare. There are several common gotchas which may trick you.

  • Code is in Typescript, but NodeJS in debugger does not understand its syntax.
  • You use combination of .ts and .js files with import or export statement
  • Debugger does not attach, and breakpoints are ignored
  • False positive detection of debug mode (flag --inspect or --inspect-brk did not work for my use case)

NRWL NX is a great tool, but somewhat it complicated debugging of NestJS app. At least for me 🙂 So after a couple of hours of trial and error, this is what finally works for me.

launch.json

Notice that --experimental-modules is turned on. Also notice usage of pwa-node, and specification of env variables.

{
	"version": "0.2.0",
	"configurations": [
		{
			"name": "Debug my NESTJS app",
			"type": "pwa-node",
			"request": "launch",
			"args": ["src/main.ts"], // Path to main entry file
			"runtimeArgs": ["--require", "ts-node/register", "--require", "tsconfig-paths/register", "--experimental-modules"],
			"cwd": "${workspaceRoot}",
			"internalConsoleOptions": "openOnSessionStart",
			"env": {
				"NODE_ENV": "local",
				"NODE_PORT": "8000",
				"TS_NODE_PROJECT": "tsconfig.debug.json", // Specify the tsconfig to use. See content of it below.
				"IS_DEBUG_MODE": "true" // Custom env variable to detect debug mode
			},
			"sourceMaps": true,
			"console": "internalConsole",
			"outputCapture": "std",
			"resolveSourceMapLocations": [
				"${workspaceFolder}/**",
				"!**/node_modules/**" // Disable the "could not read source map" error for node_modules
			]
		}
	]
}

tsconfig.debug.json

{
	"extends": "./tsconfig.json",
	"compilerOptions": {
		"outDir": "../../dist/out-tsc",
		"types": ["node"],
		"emitDecoratorMetadata": true,
		"target": "es6",
		"module": "commonjs",
		"importHelpers": false,
		"allowJs": true // Fixes combination of .js and .ts files
	},
	"exclude": ["**/*.spec.ts"],
	"include": ["**/*.ts"]
}

Detection of debug mode inside running application

export function isInDebugMode(): boolean {
	// can not rely on "--inspect" flag, because that is used when launched via NX. Even in standard launching.
	// "--inspect-brk" flag did not work either
	// So in normal situation you would use this:
	// return /--inspect/.test(process.execArgv.join(' '));

	// Workaround using ENV variable
	return process.env['IS_DEBUG_MODE'] === 'true';
}

Fix of path to files in debug mode

Normally the asset files would be in workspace/dist/appname/assets folder. That applies when application is launched normally via nx serve appname command. But when you launch app in debug mode, then the __dirname variable may resolve differently, and then the app crashes.

So this is my workaround.

export class SomeFileService {
	private readonly encoding = 'utf8';

	// Different path in debug mode and standard mode
	private readonly pathToFile = isInDebugMode()
		? path.resolve('./', 'src/assets/configs/predefined-programs.json')
		: path.resolve(__dirname, 'assets/configs/predefined-programs.json');

        async readFile(): Promise<T> {
		return await readFile(this.pathToFile, this.encoding)
			.then((data) => {
				const parsedData = JSON.parse(data);
				return parsedData;
			})
			.catch((err) => {
				console.error('SomeFileService: Error reading data from ' + this.pathToFile, err);
			});
	}
}

Happy coding.

Rainbow text color in CSS

No images, this is pure CSS.

This is really funny. You can have rainbow text on gradient background purely in CSS. No images needed.
And yes, you can have radial background too. On a long text in looks funny and cool. Look, how awesome (and weird) it is. Mystical colors.

And here is the trick how to do it:

Combine background-image property with background-clip. Not much code required. Small caveat – don’t forget to specify ::selection too, otherwise selected text may not be visible.

/** rainbow letters */
 .rainbow-demo {
     background-image: linear-gradient(to left, violet, indigo, blue, green, #CCCC00, orange, red);
     -webkit-background-clip: text;
     -webkit-text-fill-color: transparent;
 }

 .rainbow-demo::selection {
     color: #b400ff;
     -webkit-text-fill-color: #b400ff;
 }
Taste the rainbow and be happy

Working with Angular i18n inside NRWL NX workspace

Angular v10 has great support for localization using the i18n attributes. I will not describe it, because that is already described in detail on official Angual site. What I found missing was how to integrate it with NRWL NX approach.

Problems encountered

For starters, the workspace.json has different structure than angular.json, so some parts are not 1:1. Workspace.json extends angular.json schema and adds its own properties to it. In its most basic form it is ok to just rename angular.json to workspace.json and update the version property. But the documentation about how to implement localization in NRWL NX workspace is very scarse (written in March 2021).

Nx serve cant serve multiple localizations

Angular i18n approach enables you to define multiple localization files, and then compile multiple instances of the app for each localization. Each localized version will live in different folder and will use different baseHref attribute.

Example

http://myapp.com/fr for French version of the app, 
and http://myapp.com/en for English version of the app 

Define multiple locales in default build target

You can define mutliple localizations in your workspace.json, and then build them all at one step.

"targets": {
  "build": {
    "executor": "@angular-devkit/build-angular:browser",
    "options": {
        // … options not relevant to i18n removed
        // 3) LOG WARNING ABOUT MISSING TRANSLATIONS
        "i18nMissingTranslation": "warning",
        // 4) LIST OF SUPPORTED LOCALIZATIONS
        // THIS WILL COMPILE DIFFERENT INSTANCES INTO "fr" AND "en" FOLDERS
        // BUT WILL PRODUCE PROBLEM IN "nx serve myapp" command
        "localize": ["fr", "en"]
    },

nx build appname will then produce those different folders.

But nx serve can not work multiple locales. For obvious reason, each language version is a different instance of the application. So you need to instruct nx serve which language to use.

Define named build targets for each locale

Named build target can be invoked when you build/serve your application, so that different configuration is used. The most used example is the --prod flag, which uses build target named “production“. We define named build targets for each locale, like this

"configurations": {
   "production": {
     // ... some prod settings
   },
   "en": {
     "localize": ["en"]
   },
   "fr": {
     "localize": ["fr"]
   }
  },

Update serve configuration to use specific build target

The trick is in the browserTarget option says which named build target to use. If you don’t specify it, then the default is used, but that contains multiple locales, and thus breaks the serve command.

"serve": {
 "executor": "@angular-devkit/build-angular:dev-server",
 "options": {
   "browserTarget": "appname:build:en"
 }
}

And thats it. It looks simple in retrospect, but it took me considerable time to figure it out. I hope this will save someones time. Or maybe time of my future self.

All relevant code together

// 1) NAME OF THE APPLICATION
 "appname": { 
    "projectType": "application",
    "root": "apps/appname",
    "sourceRoot": "apps/appname/src",
    "prefix": "appname",
    // 2) PATH TO LOCALIZATION FILES
    "i18n": {
      "locales": {
          "fr": "apps/appname/src/locale/messages.fr.xlf",
          "en": "apps/appname/src/locale/messages.en.xlf"
      }
    },
    "targets": {
      "build": {
          "executor": "@angular-devkit/build-angular:browser",
          "options": {
            // … options not relevant to i18n removed
            // 3) LOG WARNING ABOUT MISSING TRANSLATIONS
            "i18nMissingTranslation": "warning",
            // 4) LIST OF SUPPORTED LOCALIZATIONS
            // THIS WILL COMPILE DIFFERENT INSTANCES INTO "fr" AND "en" FOLDERS
            // BUT WILL PRODUCE PROBLEM IN "nx serve myapp" command
            "localize": ["fr", "en"]
          },
    "configurations": {
     "production": {
       // 5) HERE GOES PRODUCTION SETTINGS - IGNORED FOR BREWITY
     },
     // 6) DEFINITION OF LOCALE FOR NAMED BUILD TARGETS
     // USE "en" FOR BUILD TARGET "en"
     // AND USE "fr" FOR BUILD TARGET "fr"
     "en": {
       "localize": ["en"]
     },
     "fr": {
       "localize": ["fr"]
     }
    },
   "serve": {
        "executor": "@angular-devkit/build-angular:dev-server",
        "options": {
                "browserTarget": "appname:build:en"
        }
  }
On pull request to cripple them all. We have all been there …

Further reading

Tagged function $localize. This is really cool, you can translate messages directly in your code like this:

$localize`this message will be translated like in the template`