Angular Document is not defined - javascript

I want to build my angular app for production using
npm run build:ssr
SSR is for server-side rendering.
But after build when I try to run my project it gives an error in my header components
Document is not defined
header.ts
mobileMenu() {
const mobileMenu = document.querySelector(".mobileHeader");
mobileMenu.classList.toggle("stickymobile");
const hambar = document.querySelector(".icon>i");
mobileMenu.classList.toggle("move");
const icon = document.querySelector(".icon");
icon.classList.toggle("open");
}
head() {
const image = document.querySelector(".image>img");
window.addEventListener("scroll", (e) => {
const header = document.querySelector(".desktopHeader");
if (window.pageYOffset > 25) {
header.classList.add("sticky");
//#ts-ignore
image.src = "../../../assets/Logo/Dark logo.svg";
} else {
header.classList.remove("sticky");
//#ts-ignore
image.src = "../../../assets/Logo/New logo.svg";
}
});
}
ngOnInit(): void {}
ngAfterViewInit(): void {
this.head();
}
How to resolve this error, please help

When you use Server side rendering you need to code being carefully with your code. Because some things changes when you code run in the server. Some of those things are the realted browser objects such as Document, window, etc and some functions such as SetTimeout and SetInterval. Those objects and functions does not exist in the server. So you need to avoid executon of some code when you are in the server and this is an example
import { Inject, PLATFORM_ID } from '#angular/core';
import { isPlatformBrowser } from '#angular/common';
constructor(#Inject(PLATFORM_ID) platformId: Object) {
this.isPlatFormBrowser = isPlatformBrowser(platformId);
}
mobileMenu() {
if(!this.isPlatFormBrowser) return;
// now when you are in the server this code does not be executed although in the browser it does
const mobileMenu = document.querySelector(".mobileHeader");
//rest of your code here...
}
head(){
if(!this.isPlatFormBrowser) return;
// now when you are in the server this code does not be executed although in the browser it does
window.addEventListener("scroll", (e) => {
//rest of your code here...
}

Related

Single HTTP request for HTML template file for multiple instances of the same Web Component

Say I have a Web Component defined like this:
// web-component.js
export class WebComponent extends HTMLElement {
template = '/path/to/template.html';
tmpl = {};
constructor() {
super();
}
async connectedCallback() {
const html = fetch(this.template).then(response => response.text());
this.doSomething(await html);
}
doSomething(html) {
console.log(html);
}
}
document.addEventListener('DOMContentLoaded', customElements.define('web-component', WebComponent));
A template file like this:
//template.html
<template id="web-component">
<h1>Text Goes Here</h1>
</template>
And a web page like this:
//index.html
....
<head>
<script type="module" src="/path/to/web-component.js"></script>
</head>
<body>
<web-component>Foo</web-component>
<web-component>Bar</web-component>
<web-component>Baz</web-component>
</body>
....
The web browser is making three http requests to fetch the same template file. I want to store the html from the template file somewhere on the client so I'm only making a single http request.
I tried this:
async connectedCallback() {
const existing_template = document.body.querySelector('#web-component');
console.log(existing_template);
if (existing_template) {
this.doSomething(existing_template);
} else {
const html = fetch(this.template).then(response => response.text());
this.addTemplateToWebPage(await html);
this.doSomething(await html);
}
addTemplateToWebPage(html) {
const tag = document.createElement('body');
tag.innerHTML = html;
document.body.appendChild(tag.querySelector('template'));
}
But existing_template is always null, so I'm still making unnecessary http requests. I also tried this, with the same result:
connectedCallback() {
this.doSomething();
}
async doSomething() {
const existing_template = document.body.querySelector('#web-component');
console.log(existing_template);
if (existing_template) {
this.doSomethingElse(existing_template);
} else {
const html = fetch(this.template).then(response => response.text());
this.addTemplateToWebPage(await html);
this.doSomethingElse(await html);
}
}
doSomethingElse(html) {
console.log('doing something else');
}
How can I do this so I only have a single http request when calling the same template?
What you're doing to the HTML isn't clear, but an easy way is to create a custom fetch wrapper for your HTML template. Something like this:
const fetchTemplate = (() => {
const cache = new Map();
return async (path, options) => {
if(cache.has(path)) return cache.get(path);
const res = await fetch(path, options);
const data = await res.text();
cache.set(path, data);
return data;
};
})();
This presents a much more streamlined approach and can be easily adapted for other templates.
Usage:
async connectedCallback() {
const html = await fetchTemplate(this.template);
this.doSomething(html);
}
Since the original question refers to an "HTML" template, I'm not sure if, technically, this really answers it, but it has been a few days so I will relate how I solved my problem. Basically I just changed template.html to template.js, turned the template html code into a javascript literal, and export/imported it into my class. I guess the browser automatically caches the http request because it's only making a single http call. I didn't transpile or use any build tools ... it just worked in the most recent versions of Firefox, Chrome and Edge (which is all I'm concerned about). It has been awhile since I have played with this stuff and I'm kinda blown away at the current browser support for vanilla javasript modules. When we finally get HTML imports that play nice with javascript modules, I'm hoping this approach makes it easy to refactor the code to accommodate the new technology.
My file structure is:
/www
- index.html
- /components
-- /my-component
--- component.js
--- template.js
index.html
<html>
<head>
<script type="module" src="/components/my-component/component.js"> </script>
</head>
<body>
<my-component>One</my-component>
<my-component>Two</my-component>
<my-component>Three</my-component>
</body>
</html>
template.js
export const template = `
<template id="my-template">
<style>
h1 { color: dimgray }
</style>
<div id="wrapper">
<h1>Need Text</h1>
</div>
</template>
`;
component.js
import { template } from './template.js';
export class MyComponent extends HTMLElement {
shadow = {};
tmpl = {};
constructor() {
super();
this.shadow = this.attachShadow({mode:'open'});
}
connectedCallback() {
this.setVars();
this.build();
this.render();
}
setVars() {
const tmpl = this.setTemplate(template);
this.tmpl = tmpl.querySelector('template').content;
}
setTemplate(html) {
const range = document.createRange();
return range.createContextualFragment(html);
}
build() {
// do something
}
render() {
this.innerHTML = "";
this.shadow.append(this.tmpl);
}
}
document.addEventListener('DOMContentLoaded', () => customElements.define('my-component', MyComponent));
Add a static class property, initialized as FALSE
In the class constructor, check the value of the static property and, if needed, fetch the template file.
In the constructor, set the value of the static property with the result of the fetch.
Flag the connectedCallback method as async and use AWAIT to retrieve the
static class property.
Clone the template to use in the instance(s).
my-component.js
class MyComponent extends HTMLElement {
static file = '/path/to/template.html';
static template = false;
constructor() {
super();
if (!MyComponent.template) {
MyComponent.template = MyComponent.fetchTemplate();
}
}
async connectedCallback() {
const tmpl = await MyComponent.template;
this.tmpl = tmpl.cloneNode(true);
console.log(this.tmpl);
}
static async fetchTemplate() {
return await fetch (MyComponent.file)
.then (response => response.text())
.then (txt => {
const frag = document.createRange().createContextualFragment(txt);
return frag.querySelector('template').content;
});
}
}
template.html
<template id="my-component">
<h1>Text Goes Here</h1>
</template>

SvelteKit Maintenance Mode

Is there a good way to do display a maintenance page when visiting any route of my SvelteKit website?
My app is hosted on Vercel, for those who want to know.
What I've tried so far:
Set an environment variable called MAINTENANCE_MODE with a value 1 in Vercel.
For development purposes I've set this in my .env file to VITE_MAINTENANCE_MODE and called with import.meta.env.VITE_MAINTENANCE_MODE.
Then inside +layout.server.js I have the following code to redirect to /maintenance route
import { redirect } from "#sveltejs/kit";
export async function load({ url }) {
const { pathname } = url;
// Replace import.meta.env.VITE_MAINTENANCE_MODE with process.env.MAINTENANCE_MODE in Production
if (import.meta.env.VITE_MAINTENANCE_MODE == 1) {
if (pathname == "/maintenance") return;
throw redirect(307, "/maintenance");
  } else {
if (pathname == "/maintenance") {
throw redirect(307, "/");
    };
  };
};
What I've also tried is just throwing an error in +layout.server.js with the following:
import { error } from "#sveltejs/kit";
export async function load() {
if (import.meta.env.VITE_MAINTENANCE_MODE == 1) {
throw error(503, "Scheduled for maintenance");
  };
};
However this just uses SvelteKit's static fallback error page and not +error.svelte. I've tried creating src/error.html in the hope to create a custom error page for +layout.svelte but couldn't get it to work.
I would like to use a custom page to display "Down for maintenance", but I don't want to create an endpoint for every route in my app to check if the MAINTENANCE_MODE is set to 1.
Any help is appreciated
You could use a handle server hook, e.g. src/hooks.server.ts:
import { env } from '$env/dynamic/private';
import type { Handle } from '#sveltejs/kit';
export const handle: Handle = async ({ event, resolve }) => {
if (env.MAINTENANCE_MODE == '1' && event.routeId != '/maintenance')
return new Response(undefined, { status: 302, headers: { location: '/maintenance' } });
// <other logic>
// Default response
return await resolve(event);
}
And on the maintenance page you can prevent all further navigation:
import { beforeNavigate } from '$app/navigation';
beforeNavigate(async ({ cancel }) => {
cancel();
});
(Possibly add some periodic checks via fetch calls to navigate elsewhere once the site is back online.)
You can also use +layout.ts to hook up for the maintenance mode. You can even make this conditional for some parts of the site (have frontpage still up and running).
Here is the trick we use:
import type { LayoutLoad } from './$types';
import { chainsUnderMaintenance } from '$lib/config';
import { error } from '#sveltejs/kit';
export const load: LayoutLoad = ({ params }) => {
// Check chain maintenance status; if under maintenance, trigger error (see +error.svelte)
const chainName = chainsUnderMaintenance[<string>params.chain];
if (chainName) {
throw error(503, `Chain under maintenance: ${chainName}`);
}
};

How to properly add a class inside Cypress code

I am learning Cypress along with JavaScript. I am running into a problem that I am not certain how to search it into documentation. The site I started testing has the typical wait issues so I encountered a very good solution here.
Now my test is looking in this way
/// <reference types="Cypress" />
let appHasStarted
function spyOnAddEventListener (win) {
// win = window object in our application
const addListener = win.EventTarget.prototype.addEventListener
win.EventTarget.prototype.addEventListener = function (name) {
if (name === 'change') {
// web app added an event listener to the input box -
// that means the web application has started
appHasStarted = true
// restore the original event listener
win.EventTarget.prototype.addEventListener = addListener
}
return addListener.apply(this, arguments)
}
}
function waitForAppStart() {
// keeps rechecking "appHasStarted" variable
return new Cypress.Promise((resolve, reject) => {
const isReady = () => {
if (appHasStarted) {
return resolve()
}
setTimeout(isReady, 0)
}
isReady()
})
}
describe('Main test suite', () => {
beforeEach(() => {
cy.visit('http://mercadolibre.com.ar',{
onBeforeLoad: spyOnAddEventListener
}).then({ timeout: 10000 }, waitForAppStart)
})
it('search first scanner', () => {
cy.contains('nav-search-input').type("scanner bluetooth para auto")
})
})
The problem with this is, I should replicate spyOnAddEventListener, waitForAppStart and variable appHasStarted at the beginning of every source file but I want to avoid this. How could properly extend this functions as a part of the internal source project without replicating in every test source? I have tried to make a simple source JavaScript file at the root of the project but when I import it, Cypress clients give an unrelated plug error like this one:
It looks like you've added the code to /cypress/plugins/index.js, but that is for task extensions (code that requires NodeJS access).
The two functions can be added to a file, ideally in the /cypress/support folder
wait-for-app-utils.js
let appHasStarted
function spyOnAddEventListener (win) {
...
}
function waitForAppStart() {
...
}
module.exports = {
spyOnAddEventListener,
waitForAppStart
}
test
import {spyOnAddEventListener, waitForAppStart} from '../support/wait-for-app-utils.js'
describe('Main test suite', () => {
beforeEach(() => {
cy.visit('http://mercadolibre.com.ar', {
onBeforeLoad: spyOnAddEventListener
}).then({ timeout: 10000 }, waitForAppStart)
})
Another approach is to wrap it all up (including the visit) into a custom command. Now there's no need to export and import, the command will be available globally.
/cypress/support/commands.js
let appHasStarted
function spyOnAddEventListener (win) {
...
}
function waitForAppStart() {
...
}
Cypress.Commands.add('visitAndWait', (url) =>
cy.visit(url, { onBeforeLoad: spyOnAddEventListener })
.then({ timeout: 10000 }, waitForAppStart)
)
test
describe('Main test suite', () => {
beforeEach(() => {
cy.visitAndWait('http://mercadolibre.com.ar')
})

Angular 4 - routerLinks without starting slash get rewritten after uncaught error

This is a follow up to Angular 4 + zonejs: routing stops working after uncaught error
since we had a hard time integration the suggested changes into our project.
The reason for this can be seen in this adapted plunker https://embed.plnkr.co/oUE71KJEk0f1emUuMBp8/ in app.html:
The routerLinks in our project were not prefixed with a slash "/".
This breaks the whole navigation once you navigate to another section after visiting the "Error Component".
All links are being rewritten with the current path, e.g. home.
Adding the slash in the routerLink attributes fixes this behaviour.
Why is that?
And is there some documentation/spec concerning this?
We only found this angular ticket and the api for RouterLink-directive which says
or doesn't begin with a slash, the router will instead look in the children of the current activated route.
But how is this related to what is happening with an uncaught error respectively with the suggested workaround from the previous question?
After an navigation error happens the routing state is restored
this.currentRouterState = storedState;
this.currentUrlTree = storedUrl;
https://github.com/angular/angular/blob/4.1.2/packages/router/src/router.ts#L750-L751
After that within executing createUrlTree the startPosition is obtained:
function findStartingPosition(nav, tree, route) {
if (nav.isAbsolute) { // it will be executed when you use `/` in routeLink
return new Position(tree.root, true, 0);
}
if (route.snapshot._lastPathIndex === -1) { // without '/'
return new Position(route.snapshot._urlSegment, true, 0);
}
...
}
As we can see in code above when you use slash in routeLinks then router will create position based on tree.root which has not changed.
then it is used for creating UrlTree (oldSegmentGroup in code below)
function tree(oldSegmentGroup, newSegmentGroup, urlTree, queryParams, fragment) {
...
if (urlTree.root === oldSegmentGroup) { // will be false after the error
return new UrlTree(newSegmentGroup, qp, fragment);
}
return new UrlTree(replaceSegment(urlTree.root, oldSegmentGroup, newSegmentGroup), qp, fragment);
}
So workaround might be as follows:
We no longer need RouteReuseStrategy.
We store errored state
let erroredUrlTree;
let erroredState;
export class AppModule {
constructor(private router: Router) {
router.events.subscribe(function (e) {
if(e instanceof NavigationError ) {
erroredState = (router as any).currentRouterState;
erroredUrlTree = (router as any).currentUrlTree;
}
});
}
}
and recovery it after error occurs:
#Injectable()
export class MyErrorHandler implements ErrorHandler {
constructor(private inj: Injector) {}
handleError(error: any): void {
console.log('MyErrorHandler: ' + error);
if(erroredUrlTree) {
let router: any = this.inj.get(Router);
router.currentRouterState = erroredState;
router.currentUrlTree = erroredUrlTree;
erroredState = null;
erroredUrlTree = null;
}
}
}
Modified Plunker
It looks terrible but maybe it will help to understand what the problem is

Javascript ServiceStack Client serialization error

So I have a master/detail scenario between two views. The master page shows a list and after clicking on one of the items, I send a message via the EventAggregator in Aurelia to the child view with a deserialized dto (coming from the selected item of the master) as a payload of the message.
However when I then try to pass this item as a parameter of a subsequent request in the child (to get additional info) the payload object fails to serialize.
Master.ts:
import { JsonServiceClient } from "servicestack-client";
import {
ListPendingHoldingsFiles,
ListPendingHoldingsFilesResponse,
SendHoldings,
PositionFileInfo
} from "../holdingsManager.dtos";
import { inject, singleton } from "aurelia-framework";
import { Router } from "aurelia-router";
import { EventAggregator } from "aurelia-event-aggregator";
import { GetPendingPositionMessage } from "../common/GetPendingPositionMessage";
#singleton()
#inject(Router, EventAggregator)
export class Pending {
router: Router;
positions: PositionFileInfo[];
client: JsonServiceClient;
eventAgg: EventAggregator;
constructor(router, eventAggregator) {
this.router = router;
this.eventAgg = eventAggregator;
this.client = new JsonServiceClient('/');
var req = new ListPendingHoldingsFiles();
this.client.get(req).then((getHoldingsResponse) => {
this.positions = getHoldingsResponse.PositionFiles;
}).catch(e => {
console.log(e); // "oh, no!"
});
}
openHoldings(positionInfo) {
this.eventAgg.publish(new GetPendingPositionMessage(positionInfo));
this.router.navigate('#/holdings');
}
}
Child.ts:
import { JsonServiceClient } from "servicestack-client";
import { inject, singleton } from "aurelia-framework";
import { Router } from 'aurelia-router';
import { EventAggregator } from "aurelia-event-aggregator";
import { GetPendingPositionMessage } from "../common/GetPendingPositionMessage";
import {
GetPendingHoldingsFile,
GetPendingHoldingsFileResponse,
Position,
PositionFileInfo
} from "../holdingsManager.dtos";
#singleton()
#inject(Router, EventAggregator)
export class Holdings {
router: Router;
pendingPositionFileInfo: PositionFileInfo;
position: Position;
client: JsonServiceClient;
eventAgg: EventAggregator;
constructor(router, eventAggregator) {
this.router = router;
this.eventAgg = eventAggregator;
this.eventAgg.subscribe(GetPendingPositionMessage,
message => {
this.pendingPositionFileInfo = message.fileInfo;
});
}
activate(params, routeData) {
this.client = new JsonServiceClient('/');
var req = new GetPendingHoldingsFile();
req.PositionToRetrieve = this.pendingPositionFileInfo;
this.client.get(req).then((getHoldingsResponse) => {
this.position = getHoldingsResponse.PendingPosition;
}).catch(e => {
console.log(e); // "oh, no!"
});
}
}
So the error happens when the child activates and attempts to send the request 'GetPendingHoldingsFile'.
Failed to load resource: the server responded with a status of 500 (NullReferenceException)
I have verified that this.pendingPositionFileInfo in the child is not null or empty and that on the server side, the object is not being received (it is null). I am new to Aurelia and not very experienced with Javascript so I must be missing something, any advice would be appreciated.
Edit 1
This seems to be something wrong with how I'm interacting with ServiceStack. I'm using version 4.5.6 of serviceStack with servicestack-client#^0.0.17. I tried newing up a fresh copy of the dto (PositionFileInfo) and copying over all the values from the parent view just to be sure there wasn't some javascript type conversion weirdness happening that I'm not aware of, but even with a fresh dto the webservice still receives a null request.
Switching from 'client.get(...)' to 'client.post(...)' fixed the problem. Apparently trying to serialize the object over in the URL was not a good plan.

Categories

Resources