Whereas in Python you would use
if name == "__main__": # TODO Your code goes here
in NodeJS you can simply use
if (require.main === module) { // TODO Your code goes here }
Whereas in Python you would use
if name == "__main__": # TODO Your code goes here
in NodeJS you can simply use
if (require.main === module) { // TODO Your code goes here }
In order to set the HTTP response code using Koa, use ctx.status = ...
ctx.status = 403;
const Koa = require("koa"); const app = new Koa(); app.use(async (ctx, next) => { ctx.status = 403; ctx.body = "Forbidden"; }); app.listen(3000);
Your Angular template looks like this:
<div *ngIf="files.length > 0 ; else noFiles"> <!-- ... --> </div> <div #noFiles> <!-- ... --> </div>
but when you open it in the browser, you see the following stacktrace:
c_my_module.js:2 ERROR Error: ngIfElse must be a TemplateRef, but received '[object HTMLDivElement]'. at assertTemplate (common.mjs:3392:15) at set ngIfElse [as ngIfElse] (common.mjs:3328:9) at setInputsForProperty (core.mjs:11741:34) at elementPropertyInternal (core.mjs:10874:9) at ɵɵproperty (core.mjs:13637:9) at PlanungsunterlagenDashboardComponent_Template (planungsunterlagen-dashboard.component.html:8:55) at executeTemplate (core.mjs:10534:9) at refreshView (core.mjs:10419:13) at refreshComponent (core.mjs:11480:13) at refreshChildComponents (core.mjs:10210:9)
The error message is telling you that #noFiles
is a <div>
but it must be a <ng-template>
:
In order to fix it, change it to a ng-template
:
<ng-template #noFiles> <!-- ... --> </ng-template>
If you need a <div>
specifically, you need to place it inside the ng-template
:
<ng-template #noFiles> <div> <!-- ... --> </div> </ng-template>
When you are running a NodeJS server inside a docker container, you will often encouter the issue that the container does not shutdown properly but taking a long time (several minutes) to shutdown.
In order to fix this, add the following code to your main NodeJS file (typically you should add it at the top of the file to make sure nothing prevents it from getting executed)
process.on('SIGTERM', function() { console.log('SIGTERM received, shutting down...'); process.exit(0); }); process.on('SIGINT', function() { console.log('SIGINT received, shutting down...'); process.exit(0); });
This will force NodeJS to exit (more or less immediately) once either SIGINT
or SIGTERM
is received. Typically, Docker sends SIGTERM
on container shutdown.
See this GitHub guide
Unless you have errors in your build (which is clearly visible from looking at the ng build
output), the reason why ng build
doesn’t produce an index.html
is that the resulting bundle exceeds the maximum allowed size.
This is evident from the output such as
Error: bundle initial exceeded maximum budget. Budget 1.00 MB was not met by 3.73 kB with a total of 1.00 MB.
In order to fix it, either shrink your application by removing unneeded features or libraries or splitting off certain features into extra modules or, at least temporarily, increase the allowable budget.
In order to do that, open angular.json
and look for the "budgets": {...
section:
"budgets": [ { "type": "initial", "maximumWarning": "500kb", "maximumError": "1mb" }, { "type": "anyComponentStyle", "maximumWarning": "2kb", "maximumError": "4kb" } ],
Increase the maximumError
value for "type": "initial"
(the first entry in the list shown above).
For example, you could increase it from 1mb
to 4mb
to fix your build.
After that, run your ng build
command again and index.html
should be generated.
Be aware, however, that having huge JS bundles can really slow down your web application especially for mobile users.
The following example requests /api/data
every second, creating an Observable
where you can subscribe to the continous value stream.
import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { interval, mergeMap, Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class DataService { constructor(private http: HttpClient) { } continousDataStream(_interval=1000): Observable<any> { return interval(_interval).pipe(mergeMap(_ => { return this.http.get<any>(`/api/data`); })); } }
While trying to rename a file using fs.move()
in NodeJS, you see an error message such as
Error: EXDEV: cross-device link not permitted, rename '/tmp/upload_8a8c71784abf9942b40c1359935b1997' -> 'myfile.pdf'
This error occurs because you are moving a file from one drive (/tmp
in our example above) to another drive (the current directory in our example above). On most platforms, this can’t be done using a simple rename operation.
Instead, fs.copyFile()
the file.
For example, instead of
await fs.move('/tmp/upload_8a8c71784abf9942b40c1359935b1997', 'myfile.pdf')
first, copy the file:
await fs.copyFile('/tmp/upload_8a8c71784abf9942b40c1359935b1997', 'myfile.pdf')
and then – if desired – remove the source file to mirror the behaviour of os.move()
as closely as possible.
await fs.remove('/tmp/upload_8a8c71784abf9942b40c1359935b1997', 'myfile.pdf') await fs.unlink('/tmp/upload_8a8c71784abf9942b40c1359935b1997')
First, import stat
from the NodeJS standard library:
import { stat } from "node:fs/promises";
// Async-await style: const statResult = await stat("myfile.pdf"); const fileSizeInBytes = statResult.size;
stat("myfile.pdf").then(statResult => { const fileSizeInBytes = statResult.size; // TODO your code goes here });
One one of my WordPress sites, the title of category pages was literally
Category <span>My category</span>
Searching through the theme source code didn’t yield a solution quickly, so I added this custom JS using a plugin:
jQuery(document).ready(function( $ ){ if(jQuery("h1.page-title").text().includes("Category: <span>")) { var input = jQuery("h1.page-title").text(); var output = /<span>(.*)<\/span>/g.exec(input)[1]; jQuery("h1.page-title").text(output); } });
This will just replace the title by the content of the span tag. You might need to adjust the CSS classes for your theme.
In case you see the following error for your Angular application in your JS console:
main.ts:6 ERROR NullInjectorError: R3InjectorError(AppModule)[MyService -> HttpClient -> HttpClient -> HttpClient]: NullInjectorError: No provider for HttpClient! at NullInjector.get (core.mjs:7599:27) at R3Injector.get (core.mjs:8020:33) at R3Injector.get (core.mjs:8020:33) at R3Injector.get (core.mjs:8020:33) at injectInjectorOnly (core.mjs:634:33) at Module.ɵɵinject (core.mjs:638:60) at Object.MyService_Factory [as factory] (my.service.ts:8:38) at R3Injector.hydrate (core.mjs:8121:35) at R3Injector.get (core.mjs:8009:33) at ChainedInjector.get (core.mjs:12179:36)
you need to add HttpClientModule
to your app.module.ts
.
First, import HttpClientModule
at the top of app.module.ts
:
import { HttpClientModule } from '@angular/common/http';
After that, add
HttpClientModule,
to the imports: [...]
section of your @NgModule
, for example:
@NgModule({ declarations: [ AppComponent, ], imports: [ HttpClientModule, BrowserModule, ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
After that, your error should have disappeared
Using Angular’sHttpClient
in a service to fetch a JSON is pretty straightforward. In this example, we’ll fetch a JSON array.
Note that it’s recommended to create your own TypeScript interface for improved static typing, but nevertheless starting with any[]
is often a good idea since it allows you to get started quickly.
public loadMyJSONArray(): Observable<any[]> { return this.http.get<any[]>(`/api/my-api`); }
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class MyService { constructor(private http: HttpClient) { } public loadMyJSONArray(): Observable<any[]> { return this.http.get<any[]>(`/api/my-api`); } }
Note: This solution does not deal with double quotes which are escaped already correctly, plus it does not correctly escape backslashes. However it can still serve as a starting point for doing customized escaping.
'foo"bar"gas'.replace(/"/g, "\\\"") # Results in foo\"bar\"gas
"foo/bar/gas".replace(/\//g, "") # foobargas
Use this tsconfig.json
as a starting point to setup your NodeJS TypeScript project:
{ "compilerOptions": { "strict": false, "target": "es2020", "module": "commonjs", "moduleResolution": "node", "esModuleInterop": true, "sourceMap": true, "resolveJsonModule": true, "isolatedModules": true, "skipLibCheck": true, "outDir": "./build", "rootDir": "./", "lib": [ "esnext" ], "forceConsistentCasingInFileNames": true }, "include": [ "**/*.ts", "types/*.ts" ], "exclude": [ "node_modules" ] }
import Router from '@koa/router'; import Koa from 'koa'; const app = new Koa(); const router = new Router();
import Koa from 'koa'; const app = new Koa(); /* TODO your code goes here */
This example showcases how to make a simple Hello World server using Koa in Typescript.
import Router from '@koa/router'; import koaBody from 'koa-body'; import Koa from 'koa'; const router = new Router(); const app = new Koa(); app.use(koaBody()); router.get('/', (ctx) => { ctx.body = "Hello world"; }); app .use(router.routes()) .use(router.allowedMethods()); if (!module.parent) app.listen(3000);
{ "compilerOptions": { "strict": true, "target": "es2020", "module": "commonjs", "moduleResolution": "node", "esModuleInterop": true, "sourceMap": true, "resolveJsonModule": true, "isolatedModules": true, "skipLibCheck": true, "outDir": "./build", "rootDir": "./", "lib": [ "esnext" ], "forceConsistentCasingInFileNames": true }, "include": [ "**/*.ts", "types/*.ts" ], "exclude": [ "node_modules" ] }
{ "name": "KoaTypescriptTest", "version": "1.0.0", "description": "", "scripts": { "start": "./node_modules/.bin/tsnd --respawn server.ts", "build": "./node_modules/.bin/tsc -b tsconfig.json" }, "dependencies": { "@koa/router": "^12.0.0", "koa": "^2.14.1", "koa-body": "^6.0.1" }, "devDependencies": { "@types/koa": "^2.13.5", "@types/koa__router": "^12.0.0", "@types/node": "^18.11.17", "ts-node-dev": "^1.1.8", "typescript": "^4.9.4" } }
When using Google Search Console, you might want to submit your sitemap manually from time to time to accelerate indexing of new content on your website.
The following Tapermonkey script will automatically fill in sitemap.xml
and click the Send
button. Depending on your language setting, you will need to adjust the document.querySelector()
calls and modify the script header according to your domain (mydomain.com
in this example):
// ==UserScript== // @name SearchConsoleAutoFillSitemap // @namespace http://tampermonkey.net/ // @version 0.1 // @description ? // @author You // @match https://search.google.com/search-console/sitemaps?resource_id=https%3A%2F%2Fmydomain.com%2F&hl=de // @icon https://www.google.com/s2/favicons?sz=64&domain=tampermonkey.net // @grant none // ==/UserScript== (function() { 'use strict'; // Simulate input let elem = document.querySelector("input[aria-label='Sitemap-URL eingeben']"); elem.value = "sitemap.xml"; elem.dispatchEvent(new Event('input', { bubbles: true, cancelable: true, })); // Click "Send" button setTimeout(() => { console.log("Clicking Send button"); document.querySelector("c-wiz[data-node-index='2;0'] div[tabindex='0']").click(); }, 500); })();
This snippet will change an input programmatically and then dispatch an input
event which will tell the javascript code running on the page that the input has changed.
let myinput = document.querySelector("input#myinput"); // Set value myinput.value = "newvalue"; // Simulate input event myinput.dispatchEvent(new Event('input', { bubbles: true, cancelable: true, }));
This Tapermonkey script will find <input id="myinput">
and set its value to newvalue
.
(function() { 'use strict'; document.querySelector("input#myinput").value = "newvalue"; })();
You’ll need to add a suitable header – it’s often best just to use the default template and modify only the URL.