Using angular 2 with lazy loaded modules, I can receive(for example) 401 HTTP code from server
bootstrap 0b40fee…:101 GET http://localhost:8082/2.chunk.js
Error: Loading chunk 2 failed.
at HTMLScriptElement.onScriptComplete (bootstrap 0b40fee…:91)
at HTMLScriptElement.wrapFn (zone.js:1032)
at ZoneDelegate.invokeTask (zone.js:414)
at Object.onInvokeTask (core.es5.js:4119)
at ZoneDelegate.invokeTask (zone.js:413)
at Zone.runTask (zone.js:181)
at HTMLScriptElement.ZoneTask.invoke (zone.js:476)
How to handle this error?
Check my answer for details
Workaround to bypass this chunk fails error => Programmatically force app to reload if chunks failed error occurs using global error handler.
import { ErrorHandler } from '#angular/core';
#Injectable()
export class GlobalErrorHandler implements ErrorHandler {
handleError(error: any): void {
const chunkFailedMessage = /Loading chunk [\d]+ failed/;
if (chunkFailedMessage.test(err.message)) {
window.location.reload();
}
}
}
Provide it in our root module to change default behavior in our app, so instead of using default ErrorHandler class we are using our custom GlobalErrorHandler class.
#NgModule({
providers: [{provide: ErrorHandler, useClass: GlobalErrorHandler}]
})
I was having the same problem so I investigated. I found the solution. This happened to me when I redeployed to another server and the chunk had a [hash].
You can catch the error either in a catch all like this:
ngOnInit() {
if (!this.previousRouterErrorHandler) {
this.previousRouterErrorHandler = this.router.errorHandler;
let that = this;
this.router.errorHandler = function (err: any) {
// Handle here. err.message == "Loading chunk chunk-name failed."
return that.previousRouterErrorHandler.apply(that.previousRouterErrorHandler, arguments);
};
}
}
Or directly at the link which navigated
click() {
this.router.navigate(['/lazy-route'])
.catch(err => {
// Handle here
});
}
Here is my solution for this. I inject this service as a singleton in my app / core module.
It waits for instances of NavigationError from the router, checks if they are ChunkLoadError's and then does a redirect to the place the user wanted to go to anyway.
// Angular
import { Injectable, OnDestroy } from '#angular/core';
import { Router, NavigationError } from '#angular/router';
// Rxjs
import { Subscription } from 'rxjs';
import { filter } from 'rxjs/operators';
#Injectable()
export class ChunkErrorHandler implements OnDestroy {
private subscription: Subscription;
constructor(router: Router) {
this.subscription = router.events
.pipe(filter(event => event instanceof NavigationError))
.subscribe(event => {
this.handleRouterErrors(event as NavigationError);
});
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
private handleRouterErrors(event: NavigationError) {
if (event.error.name === 'ChunkLoadError') {
window.location.href = `${window.location.origin}${event.url}`;
}
}
}
It happen when when deploy new code.The manifest.js which holds the files and hashes doesn't update without refreshing and when it loads a chunk it obviously uses the old hash from manifest.js.
So while catching error we can do force reload with given url :-
click() {
this.router.navigate(['/lazy-route'])
.catch(err => {
// Handle here
// reload with given route
// window.location.pathname('/lazy-route');
// OR
// reset existing route(containing query params) with given route and force reload
window.history.pushState({}, document.title, '/lazy-route' );
window.location.reload();
});
}
chunk related errors can be raised by any environment or routing related issues making them hard to debunk.
In my case, the amount of data moving in my PWA was too much to handle by the angular router. It was flooding the headers of the js chunks getters and therefore raising bad_request errors.
I suggest you to check out those network calls (getters of chunks.js like http://localhost:xxxx/158.js) for anything unusual in headers and refactor sketchy stuff in your current dev environment, since it's a real black hole time to investigate the source of the error by yourself.
Hope that'll help
check out Catch Storage, i guess service worker save some thing in catch storage
console.log(Object.entries(error));
this help me to understand what's inside the error is
rejection,
promise,
zone,
task
and below is my solution:
handleError(error) {
switch (error?.rejection?.name) {
case 'ChunkLoadError':
window.location.href = window.location.href;
break;
default:
break;
}
}
In my case, I was putting my files in an S3 bucket. I kept getting this error because it was calling the wrong filenames all together and returning an html error response.
At some point I let the IT team know what was happening. They were like, let's invalidate the cache on CloudFront... What?! Yeah! Let's do that...
Moral of the story, if you've been searching the web for answers to this error and can't find any, check with the IT team or any place that the index.html file might be getting cached.
this probably means unhandled exception. you have to handle error responses (4xx, 5xx status codes) from server in whatever way you want: show error message somewhere, redirect to some page, do anything but not leave it unhandled.
for example:
return this.http.get(requestDetails)
.map(res => res.json())
.catch(err => {
console.log('server error:', err)
Observable.throw(err);
});
Related
I'm completely stumped on how to avoid this circular dependency. I have a TS module that sends emails, and one that handles errors. The error handler writes to a DB and sends emails. And the emailer needs to be able to handle errors. Then most apps use both of them.
For example, something like:
emailer.js
import err from "error-handler.js"
function sendEmail() {
try { trySendEmail() }
catch(e) { err(e) }
}
error-handler.js
import sendEmail from "emailer.js"
function err(e) {
sendEmail("Error Occurred", e)
}
Is there a right way to handle this situation? Thanks for your help!
a) there's absolutely no reason not to use a circular dependency here - the two modules do depend on each other, and the code you've written works as-is with ES6 modules, no problems at all. It's no different from putting both function declarations in the same file.
b) break the dependency chain and use dependency injection instead. Either have
// emailer.js
function sendEmail(text, handleError) {
try { trySendEmail(text) }
catch(e) { handleError(e) }
}
// error-handler.js
import sendEmail from "emailer.js"
function err(e) {
sendEmail("Error Occurred: "+e.message, err)
}
or
// emailer.js
import err from "error-handler.js"
function sendEmail(text) {
try { trySendEmail(text) }
catch(e) { err(e, sendEmail) }
}
// error-handler.js
function err(e, sendEmail) {
sendEmail("Error Occurred: "+e.message)
}
If you still need to use both in your project, without injecting a dependency in either, you'll need a third module that depends on both and does export a function with the dependency injected.
I am new to Google Analytics. I have an Angular 4 application. I would like to know when an uncaught JavaScript exception had been thrown. I've set up a Google Analytics via Google Tag Manager. It works perfectly for history change event (navigation between Angular routes). But it doesn't fire JS errors.
And here is what I see in debug pane:
You can see an exception in console but JS Error tag is not fired. What am I doing wrong?
Ok, here is what I found. You shouldn't expect that exceptions will be fired automatically. You should implement ErrorHandler and manually throw event from there.
import {ErrorHandler, Injector} from '#angular/core';
import {Angulartics2GoogleTagManager} from 'angulartics2/gtm';
export class MyErrorHandler implements ErrorHandler {
private analytics: Angulartics2GoogleTagManager;
constructor(private injector: Injector) {
}
handleError(error: Error) {
console.error(error);
if (!this.analytics) {
this.analytics = this.injector.get(Angulartics2GoogleTagManager);
}
this.analytics.eventTrack(JSON.stringify(error.message), {
event: 'jsError',
label: JSON.stringify(error.stack).replace('\n', ' ')
});
}
}
app.module.ts:
{provide: ErrorHandler, useClass: MyErrorHandler, deps: [Injector]},
While working inside Angular (Angular 4, 5), if a component raises Error (TypeError or null or undefined error or so), whole application breaks onward.
How can we deal with this, to catch errors on component level and possibly show a fallback UI, like React16 does using Error Boundaries.
I would approach it by handling the error at Component level and have a service that listens to any errors happening at Component or Service level.
Ex:
Throw the error from the service
catch the error in component
Handle the error, process it and send the Error event with details to ErrorService.
You can have a app level component "errorBannerComponent" which takes input from ErrorService and paint your UI.
As soon as the error is received in ErrorService, The errorBannerComponent should display the error on screen.
Hope it helps.
Also By default, Angular comes with its own ErrorHandler that
intercepts all the Errors that happen in our app and logs them to the
console, preventing the app from crashing. We can modify this default behavior by creating a new class that implements the ErrorHandler:
You can find more details and example here:
As the proposed solutions are rather dull. I tried to recreate it myself. The easiest solution would be to provide a module scoped custom ErrorHandler class.
Thanks to this, you could even create a multiple different ErrorBoundaries.
My proposed solution can be seen here: https://stackblitz.com/edit/angular-ivy-brb143?file=src/app/widget/widget.module.ts
What is really important for this solution to work (atleast it didn't work otherwise for me). Was to provide the custom error handler as a part of a module rather than a component directly.
The important bits from the solutions:
module:
/**
* This is really imporant as this allows us to provide a module scoped ErrorHandler
*/
#NgModule({
imports: [CommonModule],
declarations: [WidgetComponent],
providers: [{ provide: ErrorHandler, useClass: WidgetErrorHandler }],
exports: [WidgetComponent],
})
export class WidgetModule {}
component where we can throw, and catch error
#Component({
selector: 'app-widget',
templateUrl: './widget.component.html',
styleUrls: ['./widget.component.css'],
})
export class WidgetComponent implements OnInit {
constructor(#Inject(ErrorHandler) public widgetError: WidgetErrorHandler) {}
ngOnInit() {
this.widgetError.isError$.subscribe((error) =>
console.log('component can act on error: ', error)
);
}
public handleThrowErrorClick(): void {
throw Error('Button clicked');
}
}
and the handler iself
#Injectable()
export class WidgetErrorHandler implements ErrorHandler {
public isError$: Subject<Error | any> = new Subject();
handleError(error) {
console.log('Intercepted error', error);
this.isError$.next(error);
}
}
Is there a way to handle when an Angular 2+ app is updated?
Note: NOT WHEN ANGULAR IS UPDATED for example from 4.1.0 to 4.1.2 (this not)
when i say "updated" i mean to:
When code has changed and is published to production.
when i publish an update to the system built in Angular 4, the view of clients just start to have errors because javascript of NG has changed, even have other javascript generated names.
what's the right way to handle this?
Angular has "something" official for say to the Browser when to update code/resources?
or something like that.
thanks.
I don't think there is an "official" way to force a client side reload when you deploy new code. Usually this should not be a problem, because when the client calls the app, it caches the JS and CSS files, so a deploy should not have any effects on the version of the application a client is currently running...
But if this really is a problem, you could provide your application version via an HTTP API, have your angular app check it on every interaction, and reload the page if the version has changed.
version.txt
1.0.1
src/environments/environment.prod.ts
export const environment = {
production: true,
version: '1.0.2'
};
src/app/version.service.ts
import {Injectable} from '#angular/core';
import {HttpClient} from '#angular/common/http';
import {environment} from '../environments/environment';
#Injectable()
export class VersionService {
constructor(private __httpClient: HttpClient) { }
checkVersion() {
this.__httpClient.get('/version.txt').subscribe(data => {
if (data != environment.version) {
alert('Code is outdated, website will reload');
window.reload();
}
}
}
}
Add a constructor to all your components and check the version
src/app/app.component.ts
constructor(private __versionService: VersionService) {
this.__versionService.checkVersion();
}
Note: this code is completely untested :-) You might have to tinker with it... Also, I am not sure if this actually IS the best way to do it, but I couldn't find a better answer anywhere either.
Thank you so much #masterfloda!!
I been working in your aproach, and it worked like a charm, I did some tunning to your code so I hope you don't mind if I publish the updated code to help other people facing the same problem.
version.txt
1.0
I noticed that when it was more than one point betwen numbers (0.0.0 -> 0.0) it fails comparing the values.
I didn't use the src/environments/environment.prod.ts aproach because I wanted a way to update the value of version inside src/environments/environment.prod.ts and was not sure how to do it once in production, so I stored the value in local storage.
src/app/version.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Compiler } from '#angular/core';
import { GlobalVariablesService } from '../services/global.service';
//https://stackoverflow.com/questions/47440576/how-to-handle-angular-2-code-updates
#Injectable()
export class VersionService {
constructor(private __httpClient: HttpClient,
private _compiler: Compiler,
public variablesService: GlobalVariablesService
) { }
checkVersion() {
this.__httpClient.get('https://host.com/version.txt?'+Math.round(Math.random() * 10000),{responseType: 'text'}).subscribe(
response => {
let text_version = response;
let stored_version = this.variablesService.getVersion();
//alert('text_version: '+text_version);
if(stored_version == undefined){
this.variablesService.setVersion(text_version);
} else if (+text_version != +stored_version) {
this.reloadAndStore(text_version);
}
},
error => {
console.log(<any>error);
}
);
}
reloadAndStore(text_version){
//alert('Code is outdated, website will reload');
this._compiler.clearCache();
this.variablesService.setVersion(text_version);
location.reload();
}
}
version.txt?'+Math.round(Math.random() * 10000)
Here you will see I'm using a random param, because if not I noticed when the web app is installed in ios homescreen it catches even that version.text file.
../services/global.service
...
getVersion() {
if(this.deCodeLocal('storedVersion') === null){
return undefined;
} else {
return this.deCodeLocal('storedVersion');
}
}
setVersion(val) {
this.enCodeLocal('storedVersion', val);
}
...
src/app/app.component.ts
constructor(private __versionService: VersionService) {
this.__versionService.checkVersion();
}
I hope it helps somebody, thank so much.
I've been stuck on an error that I'm not completely sure how to solve.
My application is made in Angular2 and runs completely in a webworker largely based on this tutorial http://www.syntaxsuccess.com/viewarticle/web-workers-in-angular-2.0
My first feature was an implementation of socket.io which is working perfectly(also with observables etc..) but now I want to use the Http service of Angular2 and I get the following error:
My code of the service is like this and the error arrises when I call validateAccessToken (I have to add the .js on my imports otherwise I get a 404 on the files within the webworker):
import { Injectable } from '#angular/core';
import { Http, Headers, RequestOptions, Response } from "#angular/http";
import { environment } from "../../../environments/environment.js";
import { Observable } from "rxjs/Observable.js";
import 'rxjs/add/operator/toPromise.js';
import 'rxjs/add/operator/map.js';
#Injectable()
export class AuthService {
headers: Headers;
options: RequestOptions;
url: string;
constructor(private http:Http) {
this.url = environment.authServerUrl;
}
validateAccessToken(token) {
return this.http.get(this.url)
.map(this.extractData)
.catch(this.handleError);
};
extractData(response: Response) {...}
handleError(error: any) {...}
}
I know the question is quite vague but with the information I get from the error it's not really clear what's going wrong for me.
The CookieXSRFStrategy is default enabled by Angular2 and used by http.
The webworker does not have DOM access to get the cookie to insert in the http headers. And thus throws the error Uncaught not implemented.
You should implement your own CookieXSRFStrategy strategy which at least does not throw this error ;)