How to reload a component in Angular without reloading the whole page - javascript

I want to reload a route but without reloading the entire page. This is not what I want:
window.location.reload();
I want to reload just the component not the page. This is waht I tried:
Angular: Change the route without reloading
Angular: Refetch data on same URL navigation
Some other random tricks also I tried. Tried following code from this URL:
Angular Basics: Refresh an Angular Component without reloading the same Component
mySubscription: any;
constructor() {
this.router.routeReuseStrategy.shouldReuseRoute=function() {
return false;
}
this.mySubscription= this.router.events.subscribe((event)=> {
if(event instanceof NavigationEnd) {
// Trick the Router into believing it's last link wasn't previously loaded
this.router.navigated=false;
}
})
}
But It's not working due to other reasons. I need a solution. Please suggest something else.

the skipLocationChange option works for me,try my service
import { Injectable } from '#angular/core';
import { Router } from '#angular/router';
#Injectable()
export class ReloadRouteService {
constructor(
private router: Router,
) { }
redirectTo(url: string): void {
// When skipLocationChange true, navigates without pushing a new state into history.
this.router.navigateByUrl('/', {skipLocationChange: true}).then(() => {
this.router.navigate([url]);
});
}
}

Related

Router.navigate not loading the full page content in Angular

I am trying to fix an issue users are having after impersonating as a different user in the system. Currently, a regional user could impersonate as someone else which does not load the full content of the page as they now have to press F5 to see everything on this page. I tried to reproduce this issue by pointing to the same database in local but not able to do so. When I go to the DEV url for instance, I then try impersonating as a different user which then loads the page partially and refreshing the page (F5), I see the entire content. I believe this is happening due to the route navigate, not sure if I am missing anything to pass in this function.
this.router.navigate(['/', user.Company.Abbreviation.toLowerCase()]);
This is the full function that the Impersonate button is executing.
setImpersonatation() {
this.store.dispatch(new AuditSearchClearAction());
this.store.dispatch(new CompanyChangedAction(this.impersonationForm.value.company, null));
const user = this.impersonationForm.value.user as UserLogin;
this.store.dispatch(new BaseUserSetAction(user as User));
this.store.dispatch(new AuthorizeAction(user));
this.router.navigate(['/', user.Company.Abbreviation.toLowerCase()]);
this.store.dispatch(new MyWorkSetLetterReminderUserAction(user.UserId));
}
When I switch a user, I get the screen below.
But when I refresh the page (F5), then i see the entire data.
Do I need to add any parameters to the router.navigate so it loads the page correctly? Seems like something is missing of when trying to load the page after the Impersonate button is clicked.
Thanks!
UPDATE:
setImpersonatation() {
this.store.dispatch(new AuditSearchClearAction());
this.store.dispatch(new CompanyChangedAction(this.impersonationForm.value.company, null));
const user = this.impersonationForm.value.user as UserLogin;
this.store.dispatch(new BaseUserSetAction(user as User));
this.store.dispatch(new AuthorizeAction(user));
this.store.dispatch(new MyWorkSetLetterReminderUserAction(user.UserId));
**this.changeDetector.detectChanges();
this.router.navigate(['/', user.Company.Abbreviation.toLowerCase()]);**
}
Using a resolver is always a good choice in cases that data is not ready to generate the page UI.
Resolver is preventing your app from navigate unless the data is fetched completely from server.
Steps:
create a resolver service by running this command:
ng generate resolver test-resolver
you have to put your ajax call in the resolve method of resolver:
#Injectable({ providedIn: 'root' })
export class TestResolver implements Resolve<Hero> {
constructor(private service: TestService) {}
resolve(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<any>|Promise<any>|any {
return this.service.testAjax();
}
}
you have to define your resolver in your routing module:
#NgModule({
imports: [
RouterModule.forRoot([
{
path: 'test',
component: TestComponent,
resolve: {
test: TestResolver
}
}
])
],
exports: [RouterModule]
})
export class AppRoutingModule {}
you have to catch the response in your component (in this case TestComponent):
#Component({
selector: 'app-test',
template: ``
})
export class TestComponent {
constructor(private activatedRoute: ActivatedRoute) {
this.activatedRoute.data.subscribe((data) => {
console.log(data.test);
});
}
}
In this way you will navigate to the next page ONLY if data is fully loaded and this feature makes it so much easier to implement a better user experience like a loading on navigation to those pages that require some data to be fetched.

What is a proper way to preserve parameters after navigating back?

In my Angular app, I have list and details pages and I want to lkeep the pageIndex value before navigating to details page. There is a Back button in the details page and I can return the list page by clicking on that button. However, I want to get the pageIndex value when navigating back to the list page and let the user open the page where he/she was before. For example I navigate 3rd page on the list and click details. At this stage I set the pageIndex to 3 and then navigate to details. Then by clicking the Back button I can navigate back to the list page, but I need to retrieve the pageIndex as 3.
So, what is an elegant way to fix this problem in Angular 10+?
list-componnet
details(id) {
this.router.navigate(['/details'], { state: { id: id } }); // I pass id value of the record
}
details-componnet
constructor(private router: Router) {
this.id = this.router.getCurrentNavigation().extras.state.id;
}
back() {
this._location.back();
}
Just write a simple example to make it work, I use the sessionStorage and router together, use router to show your the routing module, actually you can just use sessionStorage only, and wrapper it in a servive. Then you can retrieve the pageIndex anywhere.
And if you want to use router only, the pageIndex paramater will be place in both list and detail component, since this two component all need to use this value, in list component you will need pageIndex to set data-table, in detail component you need this value to pass to list component when redirect back triggered.
The routing module like below:
import { NgModule } from "#angular/core";
import { Routes, RouterModule } from "#angular/router";
import { ListComponent } from "./list/list.component";
import { DetailComponent } from "./detail/detail.component";
const routes: Routes = [
{ path: "", redirectTo: "list", pathMatch: "full" },
{
path: "list/:pageIndex=1",
component: ListComponent,
pathMatch: "full"
},
{
path: "detail/:id",
component: DetailComponent
}
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {}
Here you can navigate to list page from detail page use:
this.router.navigate(["/list/" + pageIndex]);
And then in list page's ngOnInit method to set current pageIndex to your data-table. Here is the demo: https://stackblitz.com/edit/angular-ivy-5vmteg?file=src/app/list/list.component.ts
Use sessionStorage, a listService or router queryParams to keep track of the current pageIndex.
I´d advocate for queryParams as it seems most logical and you can also link directly to specific page.
constructor(
private route: ActivatedRoute,
) { }
// Access the queryParam in list component
// Ie /list?pageIndex=4
this.route.queryParams.subscribe(params => {
// Do something with params.pageIndex if it exists
}
);
I´d also consider to change the way you handle routing to the details. If the route to the list is /list then route to the details should be /list/<listid> so you can link directly to the details if needed.
You can access the listId parameter as below but note it must also be specified as parameter in the router definition.
// Router definition
{ path: 'list/', component: ListComponent},
{ path: 'list/:listId', component: ListIdComponent}
// Access the listId in the details component
this.route.params.subscribe(param=> {
// Do somthing with param.listId
});

Run a service before application or components load Angular 7

I am building an application using Angular 7, I have handled the API calls, the JWT Token authentication system using C#, and also updating the LocalStorage() when necessary, when the user logs in and logs out, and all these are working perfectly.
My problem is I want it to run a login check as a middleware within the application rather than on the lifecycle method - ng.onInit(). How do I go about this?
Is there a way to execute lifecycle events as an entry component or service. That is, before any component loads it is able to check if the user is logged in or not and redirect via Router to a desired page.
Guard is based on the routes... so I think you should prefer a module/service solution.
import { APP_INITIALIZER } from '#angular/core';
then add it as a provider like this :
export function initApp(initService: YourInitService) {
return () => {
initService.Init();
}
}
{ provide: APP_INITIALIZER,useFactory: initApp, deps: [YourInitService], multi: true }
Routing Decisions Based on Token Expiration
If you’re using JSON Web Tokens (JWT) to secure your Angular app (and I recommend that you do), one way to make a decision about whether or not a route should be accessed is to check the token’s expiration time. It’s likely that you’re using the JWT to let your users access protected resources on your backend. If this is the case, the token won’t be useful if it is expired, so this is a good indication that the user should be considered “not authenticated”.
Create a method in your authentication service which checks whether or not the user is authenticated. Again, for the purposes of stateless authentication with JWT, that is simply a matter of whether the token is expired. The JwtHelperService class from angular2-jwt can be used for this.
// src/app/auth/auth.service.ts
import { Injectable } from '#angular/core';
import { JwtHelperService } from '#auth0/angular-jwt';
#Injectable()
export class AuthService {
constructor(public jwtHelper: JwtHelperService) {}
// ...
public isAuthenticated(): boolean {
const token = localStorage.getItem('token');
// Check whether the token is expired and return
// true or false
return !this.jwtHelper.isTokenExpired(token);
}
}
Note: This example assumes that you are storing the user’s JWT in local storage.
Create a new service which implements the route guard. You can call it whatever you like, but something like auth-guard.service is generally sufficient.
// src/app/auth/auth-guard.service.ts
import { Injectable } from '#angular/core';
import { Router, CanActivate } from '#angular/router';
import { AuthService } from './auth.service';
#Injectable()
export class AuthGuardService implements CanActivate {
constructor(public auth: AuthService, public router: Router) {}
canActivate(): boolean {
if (!this.auth.isAuthenticated()) {
this.router.navigate(['login']);
return false;
}
return true;
}
}
The service injects AuthService and Router and has a single method called canActivate. This method is necessary to properly implement the CanActivate interface.
The canActivate method returns a boolean indicating whether or not navigation to a route should be allowed. If the user isn’t authenticated, they are re-routed to some other place, in this case a route called /login.
Now the guard can be applied to any routes you wish to protect.
// src/app/app.routes.ts
import { Routes, CanActivate } from '#angular/router';
import { ProfileComponent } from './profile/profile.component';
import {
AuthGuardService as AuthGuard
} from './auth/auth-guard.service';
export const ROUTES: Routes = [
{ path: '', component: HomeComponent },
{
path: 'profile',
component: ProfileComponent,
canActivate: [AuthGuard]
},
{ path: '**', redirectTo: '' }
];
The /profile route has an extra config value now: canActivate. The AuthGuard that was created above is passed to an array for canActivate which means it will be run any time someone tries to access the /profile route. If the user is authenticated, they get to the route. If not, they are redirected to the /login route.
Note: The canActivate guard still allows the component for a given route to be activated (but not navigated to). If we wanted to prevent activation altogether, we could use the canLoad guard.
more info here
You should check for Guard in angular, especially canActivate Guard: https://angular.io/guide/router
A guard is created like this:
#Injectable({
providedIn: 'root'
})
export class MyGuard implements CanLoad {
constructor() {}
canLoad(route: Route, segments: UrlSegment[]): Observable<boolean> |
Promise<boolean> | boolean {
const x = true;
if (x) {
return true; // It allows access to the route;
} else {
// redirect where you want;
return false; // it doesnt allow to access to the route
}
}
}
Then in your routing Module:
{
path: "yourRoute",
canActivate: [MyGuard],
component: YourComponent
}
For authentication, you have a good library that uses guard here:
https://www.npmjs.com/package/ngx-auth
You should implement an authGuardService or something like that to use as middleware for your routing (using the canActivate section)
See: https://angular.io/api/router/CanActivate
This prevents routes from being loaded if the canActivate fails the condition (which is preferred when using a login system etc instead of checking in lifecycle hooks).

How to handle Angular 2+ code updates?

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.

Embedding tracking codes in Angular 2

I'm looking for the best way to implement tracking codes into my Angular 2 app. Not Google Analytics, but 3rd party suppliers like Marketo and others. I need the codes to fire each time I load a component(page). I've tried using the router changes with only partial success (and some unexpected results). Some version of this kind of worked but not fully.
this.router.events.subscribe(() => {
//tracking code goes here
});
Has anyone else had success with 3rd party tracking on Angular2 or other SPAs? Putting the tags in the template files doesn't work. Thanks.
Just use a guard on any route you want to track. It will be called every time the route is activated:
import {CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot} from "#angular/router";
import {Observable} from "rxjs";
export class TrackingGuard implements CanActivate
{
canActivate(route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean>|boolean
{
console.log(state.url);
console.log(state.queryParams);
return true;
}
}
And in your route definitions where you specify which component for the route just add canActivate: [TrackingGuard] under it. In the above example you will have access to the url and any query params. Would recommend making some other service which makes the request to the tracking api and just call if from the guard.

Categories