Im searching the best way to show/hide components on my angular application using the current location path like, homepage, loginpage, logoutpage, etc.
I'm suscribed to the router events that gives me the current path, so If Im in the login page I should hide the "navbar-component" and if I'm in the home page I should show it.
This approach should work with different components in different current pages. So I was thinking in this method inside of a *ngIf:
app.component.html
<nav *ngIf="myService.isComponentPartOfTheCurrentPage('navbar')">
...some navigation buttons here
</nav>
myService.ts
isComponentPartOfTheCurrentPage(componentName: string): boolean {
const url = getCurrentPath(); // This works fine
return currenPathContainsThisComponent(componentName, url); // This is gonna return true or false.
}
The main problem with this approach is that the angular cycling is calling this function a lot of times. Also I've read some blogs that not recommend this king of things.
Is there a better way to accomplish this?
In the subscription to your route events update a behavior subject on the service with the current
section$ = new BehaviorSubject(null);
in your route sub
sections$.next(section);
then in your component you listen to the behaviour subject with the async pipe
section$ = this.service.section$;
and in the template
<nav *ngIf="section$ | async as section">
<div *ngIf="section === 'navbar'">Something</div>
</nav>
The section variable will imagically update each time the behavior subject emits.
You could watch for router events, specifically NavigationEnd, assign the current route to an observable and then do the checks in the components for what route we are currently on.
Service:
import { Router, NavigationEnd } from "#angular/router";
import { filter, map } from "rxjs/operators";
currentRoute$: Observable<string>;
constructor(private router: Router) {
this.currentRoute$ = router.events.pipe(
filter(e => e instanceof NavigationEnd),
map((e: NavigationEnd) => e.url)
);
}
Then listen to the observable using async pipe and do the checks
*ngIf="(myService.currentRoute$ | async) === '/login'"
You can also perhaps use includes if you need to check that if that url fragment is included in url, you can then do:
*ngIf="(myService.currentRoute$ | async).includes('login')
Related
For example, I have 2 components, A and B.
In A.component.html there is code as follow
<B></B>
A is accessed via router, and I want B to be refreshed, by calling B's ngOnInit(), everytime A is visited, even with the same URL.
I have
set onSameUrlNavigation: 'reload' in RouterModule.forRoot
runGuardsAndResolvers: 'always' in A's path
subscribe to router.events in B's constructor as shown below
this.navigationSubscription = this.router.events.subscribe((e: any) => {
if (e instanceof NavigationEnd) {
this.ngOnInit();
}
});
But it doesn't work. B is not refreshed.
I guess that is because B is NOT accessed via router directly but as a child of A?
Then how to refresh B everytime A is visited?
Thanks in advance!
This can be done using onSameUrlNavigation.
you can Define what the router should do if it receives a navigation request to the current URL.
app.module.ts
#NgModule({
imports: [RouterModule.forRoot(routes, { onSameUrlNavigation: 'reload' })]
})
class MyNgModule {}
Now,Inject your router
app.component.ts
import { Router } from '#angular/router';
constructor(private router: Router) {
this.router.routeReuseStrategy.shouldReuseRoute = () => false;
}
DEMO
More Detail
I don’t think onnginit is suppose to be called directly. Avoid that.
Would listening to this.router.events.subscribe inside B solve the problem?
If not, use a service to share data between them. Inside the service you could have a subject that B could subscribe to. On navigation in A you trigger the subject
It turned out that the problem is in the Apollo GraphQL library.
It was using cache so no request was made toward BE...
I want to hide a Dropdown (it's elements included) on a certain page. I'd do it with a '*ngIf'-request, however I'm not sure about the condition.
The router path is '/project' but I can't access it - therefore *ngIf="path==='/project'" won't work.
Any ideas on what condition should be used? Or a better solution to the problem.
There also are subpaths like /project/id, on which the Dropdown should be available.
In your component/controller, try something like:
showDropdown: boolean = false;
ngOnInit() {
this.showDropdown = path === '/project'
}
path is maybe a Input-variable of your component?
#Input
path: string;
In your HTML-Template, you do something like:
<div *ngIf="showDropdown">test</div>
In your controller, add a boolean visible ( true or false )
and in you html simply add : *ngIf="visible" to your div
As I understood your situation, you need to do the following:
In the navigation bar component you have to subscribe to routing to get the current path which will help you to realize whether show or not your dropdown, do the following
import { Router, NavigationEnd } from '#angular/router';
public showDropDown: boolean;
constructor(private router: Router) {}
...
ngOnInit() {
this.router.events.subscribe(event => {
if (event instanceof NavigationEnd) {
this.showDropdown = event.url.indexOf('/project') >= 0;
}
}
}
In your html where your dropdown is present
<div class="my-dropdown" *ngIf="showDropdown">
... (your dropdown)
</div>
this will help you to avoid dependencies from other components and be able to manipulate data according to routing. Surely there will always be a way to improve this. Everything depends on what you want as a result.
I have a page that has a form that checks if the user has unsaved changes before navigating away from it.
The problem is that even with a preventDefault() and return false, the user is still able to click away from the component.
Is there a way to prevent the ngOnDestroy or click event from happening?
Note: User is not going to a different route, just another tab from the same component.
ngOnDestroy() {
if (this.myForm.dirty) {
let save = confirm('You are about to leave the page with unsaved changes. Do you want to continue?');
if (!save) {
window.event.preventDefault();
return false;
}
}
}
You would wanna use CanDeactivate guard.
Here is an example:
1. Create a guard service/provider.
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 CanDeactivateGuard implements CanDeactivate<CanComponentDeactivate> {
canDeactivate(component: CanComponentDeactivate) {
return component.canDeactivate ? component.canDeactivate() : true;
}
}
2. Add your guard service (CanDeactivateGuard) in your app.module providers
providers: [
CanDeactivateGuard,
]
3. Update your routing, to something like this:
{
path: "pipeline/:id",
component: OnePipelineComponent,
canDeactivate: [CanDeactivateGuard],
},
4. Implement canDeactivate method in your component where you want to prevent ngOnDestroy. In my case, it was OnePipelineComponent as mentioned in the route above.
canDeactivate() {
console.log('i am navigating away');
// you logic goes here, whatever that may be
// and it must return either True or False
if (this.user.name !== this.editName) {
return window.confirm('Discard changes?');
}
return true;
}
Note: Follow steps 1 & 2 only once, obviously, & just repeat steps 3 & 4 for every other component where you want the same behaviour, meaning, to
prevent ngOnDestroy or to do something before a
component can be destroyed).
Check out these articles for code sample & an explanation for the code written above. CanDeactivate & CanDeactivate Guard Example
You are mixing two concepts - navigating away means to a new route. The correct angular solution to this is implementing a CanDeactivateGuard. The docs on that are here.
A stack overflow question with answers is here.
In your situation the user is not navigating to a new page (ie. the route does not change). They are simply clicking on a new tab.
Without seeing more code, it's hard to know if both tabs are part of the same form or in two different forms.
But regardless, you need a click handler on the other tab button and in THAT click handler you need to check if the data for the current tab is unsaved (ie. if that tab is one single form, is that form dirty?).
So basically move the code you posted from ngOnDestroy and into that click handler.
I have a situation in Angular 4.0.3 where I have two <router-outlet>'s on a page.
<router-outlet></router-outlet>
<router-outlet name="nav"></router-outlet>
The first outlet will accept routes for content, and the second will accept routes for navigation. I achieve the navigation using this;
router.navigate(['', {outlets: { nav: [route] } }],{skipLocationChange: true });
This changes the outlet's contents without updating the URL - since I don't want any url that look like .. (nav:user).
The problem is the remaining outlet. I do want the URL to update when those are clicked, for instance ...
.../user/profile
Functionally, I can get both outlets to have the proper content, but it keeps appending the nav outlet's route to the url, like this ...
.../user/profile(nav:user)
Is there any way I can stop it from adding the (nav:user) part?
Unless there is some trick I'm not aware of ... I don't think you can. The address bar is what maintains the route state. So without the secondary outlet information in the address bar, the router won't know how to keep the correct routed component in the secondary outlet.
You could try overriding the navigateByUrl method as shown here: http://plnkr.co/edit/78Hp5OcEzN1jj2N20XHT?p=preview
export class AppModule { constructor(router: Router) {
const navigateByUrl = router.navigateByUrl;
router.navigateByUrl = function(url: string|UrlTree, extras: NavigationExtras = {skipLocationChange: false}): Promise<boolean> {
return navigateByUrl.call(router, url, { ...extras, skipLocationChange: true });
} } }
You could potentially add logic here then to check which routes you need to do this on.
I was attempting to create an observable that could be used to conditionally hide the main navigation menu in my app - so, when the current route was the login page, the navigation would be hidden, otherwise it would be shown.
However, i couldn't figure out a simple way to do this. I know that you could inject the ActivatedRoute, which contains observables to monitor the params and the currently activated URL segment, but the observable I need is required on the AppComponent, which sits outside the router outlet.
My eventual solution was to monitor the router itself for navigation events and compose my required observable manually:
/** Returns a boolean observable that indicates, for each route,
* if the menu should be displayed or not
*/
isMenuVisible$(): Observable<boolean>{
let isMenuCurrentlyVisible = !this.router.isActive('login', true);
return this.router.events
.filter(e => e instanceof NavigationEnd) //every route change fires multiple events - only care about the route change as a whole
.map(() => !this.router.isActive('login', true))
.startWith(isMenuCurrentlyVisible)
;
}
However, this use case doesn't seem that strange, so i was wondering if there was an easier way of accomplishing what i wanted?