During the “work” (read HAVING FUN) on my side project I often need to reuse some of my older code. As a side effect I am building my own library of utility functions, some of which I occasionaly publish. Such as this one. I am sure it will be handy for my future self, and quite likely for you as well. Feel free to use it as you wish, live long and prosper!
From time to time I get possesed by an idea, which I want to implement. It is almost like if that idea was forcing me to make it happen. When this happens I can barely wait till the morning, so I can start coding and at least test, if that idea is doable.
I had an idea to implement digital caleidoscope few years back, but back then I lacked the necessary tech skills to make it happen. One night I was lying in my bed, when the idea of caleidoscope came back to me. Oh, this was something I dreamed about, I remembered. Hmmmm, let’s see how I would approach it today having the tech skills I have now. My dreamy, half sleeping mind started processing. After a few minutes I realized that yes, it is indeed doable. My heart started racing like a galloping horse. I was so excited I could not sleep anymore, I could not wait till the morning. But I waited anyway.
The next day I started prototyping. It was the first think in the morning, before breakfast or any other usual morning routine. I just had to test if my idea was indeed doable. After couple of evenings I had my first working prototype.
I do not want to disclose too much yet, the app is not yet production ready. But I have already learned a ton by making it.
What have I learned?
The core of the app is canvas animation and mirroring. So first I had to learn how to manipulate the raw pixels in the canvas. I have learned how to scale and rotate images in canvas, how to cut them to pieces and flip them. I have learned how to capture stream from device cameras, and how to produce a video. Also how to debug application on Android phone, how to host prototype application on render.com and let it automatically deploy new version when I push to repository. The world has changed significantly since I tried the first version back then. The web development technologies have matured.
In the peak of my excitement, I even skipped social activies, so I had time to study canvas APIs and related technologies. I was totally consumed by it. Now the excitement has already passed (because falling in love can be consuming as well…) and I have to actually push myself to work on my side project. It is not that I would really have to, but I have decided that I will do my best to finish it. So that’s what I am doing.
Why to have a side project?
Why to have a side project when I already work full time? Why to invest my time and energy into something what may turn out to be “waste of time”? Why to learn new technologies which I will not use in my day job?
Well, I am creative and driven by curiosity. Also I am ambitious, and I really want this project to fly. Also it is great learning opportunity. Some of the ideas and technologies are reusable in my day job, some not. But it keeps my mind fresh and programming is still an exiting thing to do. When day job becomes too grinding or boring, I still have something what is fresh, juicy and fun to do.
I have some ideas how I could monetize this idea, but it needs a lot of polishing. If you have an idea or want to cooperate on it, let me know in the comments.
If you are developing for evergreen browsers, you are a happy developer. You will always have recent browser to work with, latest APIs and more or less stable and predictable behaviour. But when are developing software for some legacy browser, you may run into some weird surprises.
In Merim where I work, we are developing both software and hardware for restaurants. Mostly for the big brands like BurgerKing, McDonalds, Steak-n-shake and others. And for historical reasons some of those devices are running embedded Chromium wrapped in a Python package. It would be a long story if I wanted to explaing why it works this way, but to make it short: we inherited that.
Because there is embedded Chromium tied to a specific Python package, it is no longer evergreen browser. We are stuck at version 49, that version comes from year 2016, so some of the internal APIs and behaviour are not what you would expect in 2022. Like for instance the behaviour of Array.prototype.sort().
Surprise
One would naturally expect that calling myArray.sort(customSortingFunction) will return the exact same results everywhere. But it does not. At least now it does, because the behaviour stabilized in Ecmascript 2019, stating that in case of “item equality” the original sorting order is preserved. Our Chromium is from 2016, so it does not preserve the original sorting order, and we ended with really weird situations, when the same code with same input data worked on developer’s machines, but did worked differently in production.
Solution?
The obvious advice would be to upgrade to recent version of Chromium. Can’t do that, been there, tried that, did not work. So? What to do instead? Well, you have to use some independent algorithm quaranteed to work the same everywhere no matter what. So not relying on browser APIs, but reverting to good old bubbleSort. And honestly, this is the first time I have ever used that in production code.
Algoritms like bubbleSort, quickSort or whatever else esoteric stuff meant to scare newbees can be actually used sometimes 🙂
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);
});
}
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',
},
}
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:
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);
})
}
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.
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.
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.
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)
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 🙂
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.
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.