Ask user to save data before leave the component - javascript

I have a component parent app-sidebar that can have two different child depends on variable:
<app-content-right *ngIf="!componentService.getEditor.inView"></app-content-right>
<!-- alternative editors -->
<app-labtech-home *ngIf="componentService.getComponentEditor && componentService.getEditor.inView"></app-labtech-home>
I need to inform user with a modal when leave app-labtech-home to ask him to leave or not the page without saving. I catch the event with
#HostListener('document:click', ['$event'])
clickout(event) {
if(this.eRef.nativeElement.contains(event.target)) {
console.log("clicked inside")
} else {
if(window.confirm("Are you sure?"))
alert('Your action here');
}
}
inside app-labtech-home component but it exit without the modal, i need to intercept the exit and do it only if user accept. How can I achieve that? Thanks
PS: the route doesn't change, it is a different component in the same page (really a different project)
My routing module:
export const routes: Routes = [
{ path: 'login', component: LoginComponent},
{ path: '', component: HomeComponent, canActivate: [AuthGuard],canDeactivate:[BackButtonDeactiveGuard]}
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

I think you looking for this:
link

Try like this:
Create DeactivateGuardService
.service
import { Injectable } from '#angular/core';
import { CanDeactivate } from '#angular/router';
import { Observable } from 'rxjs/Observable';
export interface CanComponentDeactivate {
canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean;
}
#Injectable()
export class DeactivateGuardService implements CanDeactivate<CanComponentDeactivate>{
canDeactivate(component: CanComponentDeactivate) {
return component.canDeactivate ? component.canDeactivate() : true;
}
}
Add canDeactivate property in each component route
routing.module
#NgModule({
imports: [
RouterModule.forRoot([
{
path: 'example',
canDeactivate: [DeactivateGuardService],
component: ExampleComponent
}
])
]
Call the service method when ever user tries to leave the page having form dirty
.component
export class ExampleComponent {
loading: boolean = false;
#ViewChild('exampleForm') exampleForm: NgForm;
canDeactivate(): Observable<boolean> | boolean {
if (this.exampleForm.dirty) {
alert('Discard Unsaved Changes?');
}
return true;
}
}

Related

How can I make this Angular navigation forcefully reload a certain component?

I am working on a navigation system for an Angular 14 app.
In app-routing.module.ts I have:
import { NgModule } from '#angular/core';
import { RouterModule, Routes } from '#angular/router';
import { AboutComponent } from './components/pages/about/about.component';
import { TermsComponent } from './components/pages/terms/terms.component';
const routes: Routes = [
{ path: '', component: AboutComponent },
{ path: 'about', component: AboutComponent },
{ path: 'terms', component: TermsComponent },
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
In navbar.component.ts I have:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-navbar',
templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.css'],
})
export class NavbarComponent implements OnInit {
constructor() {}
public menuItems: any[] = [
{
route: '',
text: 'Home',
},
{
route: '/about',
text: 'About',
},
{
route: '/terms',
text: 'Terms and Conditions',
},
];
ngOnInit() {}
}
In navbar.component.html I have:
<nav>
Logo
<ul class="navigation">
<li *ngFor="let item of menuItems">
<a [routerLink]="[item.route]" [routerLinkActive]="'active'">
{{ item.text }}
</a>
</li>
</ul>
</nav>
There is a Stackblitz HERE with all the code.
The goal
The goal is to be able to choose which of the pages (components) should reload upon clicking its corresponding router link, even if the component is already loaded.
For example, if I want (only) the "Terms and Conditions" to always reload, I would have:
public menuItems: any[] = [
{
route: '',
text: 'Home',
reload: false
},
{
route: '/about',
text: 'About',
reload: false
},
{
route: '/terms',
text: 'Terms and Conditions',
reload: true
},
];
EDIT
Here is a solution that works but is redundant and it lacks flexibility:
In navbar.component.html I have added a reloadComponent()method:
public menuItems: any[] = [
{
route: '',
text: 'Home',
reload: false
},
{
route: '/about',
text: 'About',
reload: false
},
{
route: '/terms',
text: 'Terms and Conditions',
reload: true
},
];
public reloadComponent(item: any){
if (item.reload && window.location.pathname == '/terms' ) {
window.location.reload();
}
}
In navbar.component.html I have added:
<li *ngFor="let item of menuItems">
<a [routerLink]="[item.route]" [routerLinkActive]="'active'" (click)="reloadComponent(item)">
{{ item.text }}
</a>
</li>
I consider this solution, to be honest, dumb. It's only quality its that it works, for the particular case it is needed for.
I wish there was a way that I could reload the component that corresponds to the clicked menu item and not the current page.
Questions
How can I achieve this goal?
Is there a flexible and "elegant" solution to this issue?
Add this in your router config
imports: [RouterModule.forRoot(routes, {
onSameUrlNavigation: 'reload'
})],
Along with this you will have to disable click with condition reload true/false.
Docs link: https://angular.io/api/router/OnSameUrlNavigation
You should implement RouteReuseStrategy, which allows you to control the reusability of the components, and you can choose which components should be cached and reused when the user navigates to a route they have already visited.
import { RouteReuseStrategy, ActivatedRouteSnapshot, DetachedRouteHandle } from '#angular/router';
export class CustomRouteReuseStrategy implements RouteReuseStrategy {
private storedRouteHandles = new Map<string, DetachedRouteHandle>();
shouldReuseRoute(current: ActivatedRouteSnapshot, next: ActivatedRouteSnapshot): boolean {
return true;
}
store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
this.storedRouteHandles.set(route.routeConfig.path, handle);
}
retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null {
return this.storedRouteHandles.get(route.routeConfig.path);
}
shouldAttach(route: ActivatedRouteSnapshot): boolean {
return this.storedRouteHandles.has(route.routeConfig.path);
}
shouldDetach(route: ActivatedRouteSnapshot): boolean {
return true;
}
deleteRouteSnapshot(path: string): void {
this.storedRouteHandles.delete(path);
}
}
You need to register it into app module:
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { RouterModule, RouteReuseStrategy } from '#angular/router';
import { AppComponent } from './app.component';
import { CustomRouteReuseStrategy } from './custom-route-reuse-strategy';
#NgModule({
imports: [
BrowserModule,
RouterModule.forRoot([
// ...
])
],
declarations: [AppComponent],
providers: [
{ provide: RouteReuseStrategy, useClass: CustomRouteReuseStrategy }
],
bootstrap: [AppComponent]
})
export class AppModule { }
In the ActivatedRouteSnapshot you should have access to that custom route information:"reload: true":
shouldReuseRoute(current: ActivatedRouteSnapshot, next: ActivatedRouteSnapshot): boolean {
// Check if the custom parameter "myParam" exists in the route's data.
const myParam = current.data && current.data['myParam'];
// If "myParam" is set to "reuse", reuse the current route.
if (myParam === 'reuse') {
return true;
}
// Otherwise, create a new component.
return false;
}
Maybe you could set state data to your route, and in it's components you could validate if the route state snapshot has the flag you refresh the page.
Example: When you render your menu, you could do something like this:
navbar.component.html
<nav>
Logo
<ul class="navigation">
<li *ngFor="let item of menuItems">
<a [routerLink]="[item.route]" [routerLinkActive]="'active'" [state]="{ reload:item.reload}">
{{ item.text }}
</a>
</li>
</ul>
</nav>
And on each component belonging to your route, do something like this.
constructor(private router: Router) {}
ngOnInit() {
boolean shouldPageReload = this.router.getCurrentNavigation()?.extras?.state["reload"]
if (shouldPageReload) {
// reload your component
}
}
For more information please check this article about passing data to your routes
https://www.tektutorialshub.com/angular/angular-pass-data-to-route/

How to use same Component for routing in Angular

I am using the same component for my router, on the first click the component affected, but on the next click the component still in the first state.
Here is the script for changing the route
<a [routerLink]="['react/1']">link 1</a>
<a [routerLink]="['react/2']">link 2</a>
Here is my router module
panel-routing.module.ts
import { NgModule } from '#angular/core';
import { Routes, RouterModule } from '#angular/router'
import { PanelCoursesComponent } from 'src/app/components/panel-courses/panel-courses.component';
import { PanelHomeComponent } from 'src/app/components/panel-home/panel-home.component';
import { PanelIntroComponent } from 'src/app/components/panel-intro/panel-intro.component';
const routes: Routes = [
{ path: '', component: PanelHomeComponent },
{ path: 'react', component: PanelIntroComponent },
{ path: 'react/:no', component: PanelCoursesComponent } //the target
]
#NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class PanelRoutingModule { }
panel-course.component.ts
import { Component, OnInit } from '#angular/core';
import { Router, ActivatedRoute } from '#angular/router'
#Component({
selector: 'app-panel-courses',
templateUrl: './panel-courses.component.html',
styleUrls: ['./panel-courses.component.scss']
})
export class PanelCoursesComponent implements OnInit {
url!: any
constructor(private route: ActivatedRoute, private router: Router) {
console.log('route')
}
ngOnInit(): void {
this.url = this.router.url
console.log(this.route.snapshot.params) //the test script
}
}
On the PanelCourseComponent I try to console log the params, but that's only executed one time on the first click.
Am I missing something?
You can use this.route.params.subscribe method for this case
Here is the example
ngOnInit(): void {
this.route.params.subscribe(params => {
console.log(params) // It will be executed whenever you click the link
})
}
by default pathMatch is set to 'prefix'. so paths will be matched against your current location and the first one witch "matches" will render its component. to make your paths match only "exact" match add pathMatch: 'full' for your routes
const routes: Routes = [
{ path: '', component: PanelHomeComponent, pathMatch: 'full' },
{ path: 'react', component: PanelIntroComponent, pathMatch: 'full' },
{ path: 'react/:no', component: PanelCoursesComponent } //the target
]

Redirecting to maintenance page

I am looking for ways of redirecting a page to the maintenance page in angular but i am new and am research different methods for turning on maintenance mode
i found a possible solution here: # the approved answer
Angular JS redirect to page within module config
however i don't know how to implement it
if there someone who could explain it, i would appreciate it greatly
using an authGuard will solve this problem
auth-guard.service.ts file:
import { Injectable } from '#angular/core';
import { CanActivate, Router, RouterStateSnapshot, ActivatedRouteSnapshot } from '#angular/router';
import { AuthService } from './auth.service';
import { Observable } from 'rxjs';
#Injectable()
export class AuthGuardMaintenance implements CanActivate {
constructor(
private authService: AuthService, private router: Router
) {}
canActivate(): Observable<boolean> | Promise<boolean> | boolean {
if (this.authService.inMaintenance()) {
alert('This Site Is Still Under Maintenance')
this.router.navigate(['/maintenance']);
return false;
} else {
this.router.navigate(['/']);
return true;
}
}
}
auth.service file:
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class AuthService {
constructor() { }
inMaintenance() {
return false;
}
}
then import it in the app.module.ts file and add it to providers
then import the auth guard to the app-routing.module.ts file add the property
canActivate: [AuthGuardMaintenance]
to the the root route
eg
export const routes: Routes = [
{ path: '', component: MainComponent, canActivate: [AuthGuardMaintenance] },
{ path: 'maintenance', component: MaintenanceViewComponent },
{ path: '**', component: PageNotFoundComponent },
];

change data on route event in Angular

How to change data in one component relatively to others on route event in Angular?
For e.g. if I have three components: "nav.component", "about.component" and "service.component".
So I want to display different text in "nav.component" when I switch between about and service pages in my app.
My "app.router.ts" file:
import { ModuleWithProviders } from '#angular/core';
import { Routes, RouterModule } from '#angular/router';
import { AppComponent } from './app.component';
import { AboutComponent } from './about/about.component';
import { ServiceComponent } from './service/service.component';
export const router: Routes = [
{ path: '', redirectTo: 'about', pathMatch: 'full' },
{ path: 'about', component: AboutComponent },
{ path: 'service', component: ServiceComponent }
];
export const routes: ModuleWithProviders = RouterModule.forRoot(router);
I don't want to display just page name text in my nav bar while switching between these pages, it would be a custom text for each component.
Also I would like to store this data/text directly in "about.component.ts" and "service.component.ts" but not in the "app.router.ts" due to maintainability and scalability.
Is it possible?
U.P.D.
This is my "app.component.html" file:
<div class="container">
<!-- Nav Bar (text changes here) -->
<app-nav></app-nav>
<!-- Pages (components which are included in app.router.ts) -->
<router-outlet></router-outlet>
</div>
For e.g. this is "about.component.ts" file:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-about',
templateUrl: './about.component.html',
styleUrls: ['./about.component.scss']
})
export class AboutComponent implements OnInit {
const text_for_nav_bar = "This is my new About page."; // <-- text that should be displayed in nav component for this page on router event.
constructor() { }
ngOnInit() {
}
}
Using below code you will able to subscribe router change events. You need to add this code on nav bar.
Import router and Navigation start
import { Router, ActivatedRoute, NavigationStart } from '#angular/router';
import "rxjs/add/operator/filter";
import "rxjs/add/operator/pairwise";
Add below code in constrictor.
this.router.events
.filter(event => event instanceof NavigationStart)
.pairwise()
.subscribe((value: [NavigationStart, NavigationStart]) => {
let nextUrl = value[1].url;
if (nextUrl == '/about') {
// your code here for next url
}
},
(err) => {
},
() => { });
}
});
One way would be with *ngIf (or [hidden] if you want to load all contents to the DOM at once). And to catch current route, inject Router module:
class NavComponent {
constructor(private router: Router){
}
}
and in nav.component.html:
<div *ngIf="router.url === '/some/route'">
text for this route
</div>
<div *ngIf="router.url === '/other/route'">
text for other route
</div>
Doing same in component.ts, could be:
nav.component.html:
<h1>{{yourText}}</h1>
component.ts:
ngOnInit() {
if(this.router.url == '/some/route') {
yourText = 'Text'
} elseif(this.router.url == '/other/route') {
yourText = 'Other text'
}
}
You should use router datas
export const router: Routes = [
{ path: '', redirectTo: 'about', pathMatch: 'full' },
{ path: 'about', component: AboutComponent, data: {navigationText: 'Some text'} },
{ path: 'service', component: ServiceComponent, data: {navigationText: 'Some other text'} }
];
and in app.component.html
<div class="container">
<app-nav text="outlet.activatedRouteData.navigationText"></app-nav>
<router-outlet #outlet="outlet"></router-outlet>
</div>
Of course you need to add a "#Input text: string" property in nav.component.ts

Capture redirect route with guard in Angular2

I'm having trouble capturing the original navigating route using a guard in Angular 2.
My site consists of a core module protected by an authorization guard, and a login page that's unprotected.
The core module has it's own sub routes defined in it's own app routing file, and any undefined routes are redirected to the root path.
Here's my top level routing module.
import { NgModule } from '#angular/core';
import { RouterModule, Routes } from '#angular/router';
import { AuthGuard } from './auth';
const routes: Routes = [
// Login module is public
{ path: 'login', loadChildren: 'app/auth/auth.module#AuthModule' },
// Core route protected by auth guard
{ path: '', loadChildren: 'app/core/core.module#CoreModule', canLoad: [AuthGuard] },
// otherwise redirect to home
{ path: '**', redirectTo: '' }
];
#NgModule({
imports: [ RouterModule.forRoot(routes) ],
exports: [ RouterModule ]
})
export class AppRoutingModule {}
And here is the AuthGuard class.
import { Injectable } from '#angular/core';
import { Router, CanLoad, Route } from '#angular/router';
import { AuthService } from './auth.service';
#Injectable()
export class AuthGuard implements CanLoad {
constructor(
private authService: AuthService,
private router: Router
) {}
canLoad(route: Route): boolean {
this.authService.redirectUrl = `/${route.path}`;
console.log('path:' + route.path);
if (this.authService.isLoggedIn()) {
return true;
} else {
this.router.navigate(['/login']);
return false;
}
}
}
This is a pretty straightforward login/redirect scheme, however the route.path value is always empty, regardless of what URL I navigate to. I have a hunch that it has something to do with the { path: '**', redirectTo: '' } route but I'm not sure.
I don't want to use canActivate because I only want the main module loaded if the user is actually logged in.
What I expected was that if I navigate to /foobar then route.path would be set to foobar in the AuthGuard class but it is not. It is always empty, thus I am unable to do a correct redirect after the user logs in.
Try adding the pathMatch: 'full' like this:
{path: '**', redirectTo: '', pathMatch: 'full'}
or
import {CanActivate, RouterStateSnapshot, ActivatedRouteSnapshot} from "#angular/router";
import { Subscription, Observable } from "rxjs/Rx";
export class HomepageGuard implements CanActivate {
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
console.log(`[homepage.guard.ts]-[canActivate()]`);
console.log(route);
console.log(state);
// are you allowed to continue
return true;
}
}

Categories