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.
Related
I use WebStorm (great IDE) and I'm working on a custom pet project of mine which will become a game eventually. But I'm developing a bare bones Alpha version to show off to potential employers because I'm looking for work and want to add this project to my resume. Anyway, I'm using the Builder design pattern to create very complex objects. Some of these objects are spread across several different services because it makes more sense to do it that way. The game I'm making is going to be a text-based RPG where players can create characters, go to different locations, gather items, etc. So IDK how to get around needing multiple services which multiple Builder objects, but when I combined them all into a "super object"...I get a circular dependency error.
I tried installing NestJS because Nest has a way around Circular Dependency errors and I believe I did everything right, but I'm still getting the same error.
https://docs.nestjs.com/fundamentals/circular-dependency
As you can see below, it does build a localhost, but of course it doesn't do anything.
Terminal Error Image
Here's a small example from two Service files. The Nest docs say I need the ForwardRef in both Services files, not one which I have here. I've also of course installed the packages #nestjs/common and #nestjs/core. I've also tested a couple other Builder objects that don't depend on another service and they display to the console just fine. So I know the source of my problem are these Circular Dependencies.
decisions.service.ts
import { DecisionBuilder } from '../../../../Shared/builder';
import { DecisionType } from '../../../Gameplay/structs';
import { ChapterOneService } from './chapter-one.service';
import { forwardRef, Inject } from '#nestjs/common';
#Injectable({
providedIn: 'root'
})
export class DecisionsService
{
commonOne = DecisionBuilder.create({
decisionType: DecisionType.Common,
agendaScore: DecisionType.Common.valueOf(),
agendaImpact: 'Moderate',
currentPage: this.chapOne.PageOne,
nextPage: this.chapOne.PageTwo
});
commonTwo = DecisionBuilder.create({
decisionType: DecisionType.Common,
agendaScore: DecisionType.Common.valueOf(),
agendaImpact: 'Idealist',
currentPage: this.chapOne.PageOne,
nextPage: this.chapOne.PageTwo
});
commonThree = DecisionBuilder.create({
decisionType: DecisionType.Common,
agendaScore: DecisionType.Common.valueOf(),
agendaImpact: 'Extremist',
currentPage: this.chapOne.PageOne,
nextPage: this.chapOne.PageTwo
});
constructor(
#Inject( forwardRef(() => ChapterOneService) )
private chapOne: ChapterOneService )
{
}
}
The above Decision Service only depends on one other service and that's the one before. But I use the product of the service as a value for currentPage and nextPage
chapter-one.service.ts
import { AdventurePageBuilder } from '../../../../Shared/builder';
import { LocationsService } from './locations-service';
import { CharacterService } from '../../character.service';
import { DecisionsService } from './decisions.service';
import {forwardRef, Inject} from '#nestjs/common';
#Injectable({
providedIn: 'root'
})
/**
* CHAPTER ONE
* - Chapter Service that contains all Page objects
* - Each Chapter component accesses this one service for all content
*/
export class ChapterOneService
{
PageOne = AdventurePageBuilder.create({
name: this.location.getShip().area,
location: this.location.getShip(),
character: this.character.Krellen,
storyText: this.pageOneStory(),
descriptors: this.pageOneDesc(),
decisionEvents: this.decisions.commonOne
});
PageTwo = AdventurePageBuilder.create({
name: this.location.getShipTwo().area,
location: this.location.getShipTwo(),
character: this.character.Krellen,
storyText: this.pageTwoStory(),
descriptors: this.pageTwoDesc(),
decisionEvents: this.decisions.commonOne
});
constructor(
#Inject( forwardRef(() => LocationsService))
#Inject( forwardRef(() => CharacterService))
#Inject( forwardRef(() => DecisionsService))
private location: LocationsService,
private character: CharacterService,
private decisions: DecisionsService)
{
}
/***************************************/
/****************PAGE ONE**************/
/***************************************/
getPageOne(): any
{
return this.PageOne;
}
pageOneStory(): string
{
return `${this.PageOne.name} was dark was dreary. Much to ${this.PageOne.character.name}'s dismay`;
}
pageOneDesc(): any
{
// See if character carries any items with descriptions. Guns, armor, ect.
}
/***************************************/
/****************PAGE TWO***************/
/***************************************/
getPageTwo(): any
{
return this.PageTwo;
}
pageTwoStory(): string
{
return `${this.PageTwo.name} was dark was dreary. Much to ${this.PageTwo.character.name}'s dismay`;
}
pageTwoDesc(): any
{
// See if character carries any items with descriptions. Guns, armor, ect.
}
displayHolodeckPage()
{
return this.PageOne;
}
}
Some of the code from above can be ignored as I don't think they're directly the problem...I just wanted to show that I use the ForwardRef in this file too as well as the other Services that are used in chapter-one.service.ts
Click on the error image link above to see the error code I get, but any help is welcomed. Whether it's a fix to the circular error problem or a way to refactor the code so I can essentially get the same result by doing something differently.
NestJS has project structure and Dependency Injection system that is somewhat inspired by Angular but they are completely different frameworks. You cannot use NestJS decorators inside of an Angular app and expect it to do anything. You need to fix the problem in your Angular app. Don't install backend framework dependencies into a frontend app
My question sounds similar to Cannot find the '#angular/common/http' module and Error loading #angular/common/http - angular 2 but the problem is a bit different:
I am using Angular 4.3.5 and I am trying to read data from a Web API. (This API puts out JSON data and is using SignalR and .net Core).
I have followed several tutorials and came up with this code for the class that will actually contact the service:
import 'rxjs/add/operator/map';
import { HttpClient, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '#angular/common/http';
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { Configuration } from './Configuration.js';
#Injectable()
export class DataService {
private actionUrl: string;
constructor(private http: HttpClient, private configuration: Configuration) {
this.actionUrl = configuration.serviceUrl;
}
//public getAll<T>(): Observable<T> {
// return this.http.get<T>(this.actionUrl);
//}
public getSingle<T>(id: number): Observable<T> {
return this.http.get<T>(this.actionUrl + id);
}
}
#Injectable()
export class CustomInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (!req.headers.has('Content-Type')) {
req = req.clone({ headers: req.headers.set('Content-Type', 'application/json') });
}
req = req.clone({ headers: req.headers.set('Accept', 'application/json') });
console.log(JSON.stringify(req.headers));
return next.handle(req);
}
}
Now, building this project (I am using Visual Studio 2017 Enterprise) and running a gulp task to transpile the .ts to .js works just fine, so do all the intellisense-tooltips - the IDE does recognize the existance of those things.
But if I open it up in a browser (doesnt matter if firefox, edge or chrome) I get the following error:
zone.js:958 GET http://localhost:3966/libs/#angular/common/bundles/common.umd.js/http 404 (Not Found)
If I edit the transpiled javascript file by hand and write common-http.umd.js there, the file is found. (This is the reason why at the top I import Configuration.js instead of Configuration - it doesnt seem to want to automatically resolve the suffix, like in some tutorials).
I hope I am not too vague, since this is my first Time asking something publically. Also I was not able to find an answer in the given questions.
Well, I found a solution, for anybody who is curious why this and similar problems exist:
I had to edit my systemjs file and add this line:
'#angular/common/http': 'npm:#angular/common/bundles/common-http.umd.js',
and it works!
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);
});
I am working on the front end of a file upload service. I am currently ignoring the service path with respect to the backend. I have run into a strange problem. I have a few generated components that sit within the app component. When I end the serve from console and do ng serve again, it errors out. It says:
The only way I have found to get rid of this is to erase my uploader service injection, save the file, then re-insert the injection. This is how it is supposed to look:
The only way to get ng serve to work is to by erasing the line private service: UploaderService
Any idea why this is happening? Am I missing something with my injection? My UploaderService is marked as Injectable() and the components that use it are under Directives.
Update:
What I found out is that it is unrelated to the UploaderService. I have a component that does not inject the UploaderService. I fix it the same way I fix the other components that inject the UploaderService. By deleting the parameters of the constructor, saving, and then putting the parameters back. Then it will serve
Update2:
The generated componenet, upload.component.t, has a spec file that is generated with it, upload.component.spec.ts
It has a error that asks for parameters like so:
My UploadComponent constructor has a parameter in it, where i inject the UploaderService. In the spec.ts file, a new UploadCompent is created, but does not contain any arguments. I am guessing this is where I am going wrong. How do I work around this?
Here is my UploaderService:
import { Injectable } from '#angular/core';
import {Http, Response, HTTP_PROVIDERS, Headers, HTTP_BINDINGS, RequestOptions} from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/Rx';
import { ItemEntryComponent } from './item-entry';
import { Query } from './Query';
#Injectable()
export class UploaderService {
public URL: string;
private query: Query;
public filesSelected: Array<ItemEntryComponent> = [];
progress$: any;
progress: any;
progressObserver: any;
//CONSTRUCTOR
constructor(private http: Http) {
//***SET URL***
this.URL = 'http://localhost:7547/api/picker';
//Create Query for url
this.query = new Query(this.URL);
//Create progress attribute
this.progress$ = Observable.create(observer => {
this.progressObserver = observer
}).share();
}
}
Problem solved!
I had not realized the generated files included a spec testing file, in my example it was upload.component.spec.ts. Getting rid of those files gets rid of the errors that ask for parameters to be filled in inside the test files and now ng serve works.
I've yet to find decent documentation detailing how to migrate from Angular 1.x to Aurelia. So far, I've only seen folks detailing how the concept of an Angular directive can be remade in Aurelia using #customElement. Okay, simple enough. But these examples always, always just mock data.
That said, Angular Services are singletons that can be injected into any controller/directive/service, and typically allows for the fetching of data from a server (i.e. PersonService, OrdersService).
But how are these data services modeled in Aurelia? Is everything just a class? It seems like it.
Essentially, I'd to see some code samples, a hello-world, that effectively fetches data from a service, and provides it to a #customElement. Where do the HTTP calls go? How do we even make HTTP calls? Angular uses $http, what about Aurelia?
EDIT:
Here's a simple angular service. How would one attack this in Aurelia?
app.service('SomeDataService', function () {
return {
getMyData: function (options) {
return $.ajax(options);
}
}
});
Yep- plain ES6/ES7 classes. There's no framework intrusion in your data services.
my-data-service.js
import {HttpClient} from 'aurelia-http-client'; // or 'aurelia-fetch-client' if you want to use fetch
import {inject} from 'aurelia-framework';
#inject(HttpClient)
export class MyDataService {
constructor(http) {
this.http = http;
}
getMyData() {
return this.http.get(someUrl);
}
}
fancy-custom-element.js
import {MyDataService} from './my-data-service';
import {inject} from 'aurelia-framework';
#inject(MyDataService) // aurelia's dependency injection container will inject the same MyDataService instance into each instance of FancyCustomElement
export class FancyCustomElement {
data = null;
constructor(dataService) {
this.dataService = dataService;
}
// perhaps a button click is bound to this method:
loadTheData() {
this.dataService.getMyData()
.then(data => this.data = data);
}
}