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.
Related
I have a "settings" page in my react app. The page has several tabs rendering different parts of settings.
It would be better UX if a user can share urls with other users.
What I want is (inside "settings" page):
user A clicks a tab
url changes with a #tabname appended
user A send that url to user B, and user B open that url
user B sees the same tab as user A
But with react router, the whole page re-renders if the url changed:
import { withRouter } from "react-router-dom"
const MyComp = (props) => {
...
const onTabChange = () => {
// append #tabname here
props.history.replace(...); // or `push`
...
}
...
export default withRouter(MyComp)
}
After a lot of searches, I found a solution to use window.history:
const onTabChange = () => {
window.history.pushState(null, null, "#tabname");
...
}
This does the trick, but little information and explanation, and I'd love to know the consequences of using this trick.
Is this a valid solution (for a react app)? Will this cause any problem?
(PS. I know how to parse a url)
More details:
To be more specific, there is a AuthChecker wrapper for all pages. When react router's location changes, it checks for the route's allowed auths and current user's auth.
I've tried /path/:id and everything but all change location, so auth checked and page rerendered.
And I've given up a solution in react router and just want to know: is it safe to change url with window.history in a react app using react router to manage routes?
this question is already answerd at this post.
so it says window has a property called history and there is a method on history which helps you update the history state without react-router-dom understanding it.
like this:
window.history.replaceState(null, 'New Page Title', '/new_url');
What do I want to achieve
I want to update my current view based on an Id. So say that I have side navigation with the following tabs:
Customer A
Customer B
Customer C
What I want is that the user can click on Customer A and that the current customer view gets updated based on the Customer Id.
What is my problem achieving this
I thought the best way to solve this issue was to navigate to the page and provide the Id directly as follows:
router.navigateToRoute("customer", { currentCustomerId });
Then on the Customer page I am receiving the Id in the activate method as following:
public activate(params) {
this.currrentCustomerId = params.currentCustomerId;
}
Actually, this is working the first time you navigate to a customer. But when I am clicking on another Customer page, the view does not get updated because the activate method does not get triggered for a second time. It is only working if I navigate to another page (not customer page) and go back or simply refresh the whole page.
So what can I use to achieve what I want? I reckon that I have to use something else than activate()?
I appreciate it if someone could give me some insight into this issue.
Regards.
This is due to the default activation strategy wherein, if the URL only changes in terms of a parameter value, the component is reused and hooks are not invoked.
To obtain the desired behavior, you can customize the this behavior at the component level or the route level.
At the component level:
import {activationStrategy} from 'aurelia-router';
export class CustomerComponent {
determineActivationStrategy() {
return activationStrategy.replace;
}
activate(params: {currrentCustomerId: string}) {
this.currentCustomerId = params.currentCustomerId;
}
}
At the route level:
import {Router, RouterConfiguration} from 'aurelia-router';
export class App {
configureRouter(config: RouterConfiguration, router: Router) {
config.map([{
name: 'customer',
moduleId: './customer',
route: 'customer/:currentCustomerId',
activationStrategy: 'replace'
}]);
this.router = router;
}
}
I have a website as a single page that is using Next.js. I have the home page on route / that show a list of products. The code of this page is located in pages/index.js. Each product has an id so I can jump to it using /#product-id.
To make it more url friendly I replicate this behaviour using the product-id as a second param in the route like that: /product-id.
What I do is simply looking on the product-id param using useRouter():
const selectedProductId = useRouter().query['product-id']
And then scroll to the element with this id using js:
document.getElementById(selectedProductId ).scrollIntoView()
So I to change my script name from /pages/index.js to /pages/[product-id].js.
So now the route /1234 work has expected but if I go to / I get error 404.
So has someone an idea how I can match / and /param using one js file?
Optional catch all routes
Catch all routes can be made optional by including the parameter in double brackets ([[...slug]]).
Nextjs has file system based routing, so if you remove /pages/index.js of course you will get an 404 error. Also /pages/index.js and /pages/[product-id].js will render two separate pages.
To answer your question, if it is possible to match two routes like / and /[productId] in one file using nextjs I don't think that is possible however similar results can be achieved by using shallow routing specific to your use case.
So for your use case, I suggest using shallow routing unless you want to render the same component in both pages just to get the product-id or want to make use of hash URLs.
You can make product-id a query string parameter and update it using shallow-routing. Here is an example,
Keep /pages/index.js
import { useRouter } from 'next/router';
const router = useRouter()
// when want to change the productId call
router.push('/?productId=1234', undefined, { shallow: true })
// call the scrollToView inside a useEffect hook
useEffect(() => {
const productId = router.query.productId
// get the element using the productId above then call scrollIntoView()
})
// if using useEffect with the dependency router.query.productId,
// when you change the productId once and scroll up and try to change to the same -
// productId again it will not scroll to view, hence not using the dependency array
// at all
To explain more on what shallow routing does
Shallow routing will allow the change of URL without running the data fetching methods i.e getStaticProps or getServerSideProps again. Which will make the updated query and pathname available without changing the state. Read more about it nextjs docs.
Option 1: Extract the shared code
You could extract a Page component to a separate file and then import it in both /pages/index.js and /pages/[product-id].js, so the code is not duplicated.
Option 2: Use experimental rewrites feature
Assuming you have /pages/[product-id].js you can show this page when a user requests /.
You would need to add next.config.js.
module.exports = {
experimental: {
async rewrites() {
return [
{ source: "/", destination: "/[product-id]" },
];
}
}
}
So, when a user requests / they would see the content of /[product-id], just with the empty product id.
Note, that at the moment rewrite doesn't support auto-rendered dynamic pages, so you have to disable auto-rendering for the dynamic page.
You can do that by adding getServerSideProps to /pages/[product-id].js.
export async function getServerSideProps() {
return {
props: {},
}
}
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')
I am creating my personal website/blog as a a single page application using Mithril.js. All pages and blog posts on my website are rendered using Page and Post components, and the correct page is loaded based on the :slug in the URL.
The problem I have is that whenever I try and switch between pages, the content of the page does not update. Switching between pages and posts works because I am alternating between Page and Post components. But when I try and use the same component twice in a row, going from page to page, it doesn't update the webpage.
m.route(document.body, '/', {
// `Home` is a wrapper around `Page`
// so I can route to `/` instead of `/home`
'/': Home,
'/:slug': Page,
'/blog/:slug': Post
});
const Home = {
view() {
return m(Page, { slug: 'home' });
}
};
Here is the Page component (the Post component is very similar). Both components render correctly.
const Page = {
content: {},
oninit(vnode) {
m.request({
method: 'GET',
url: 'content.json',
}).then((response) => {
Page.content = response.pages[vnode.attrs.slug];
});
},
view() {
if (Page.content) {
return [
m('#content', m.trust(Page.content.body))
];
}
}
};
Why isn't Mithril recognizing that the slug changed?
The docs page for m.route has a solution for you.
When a user navigates from a parameterized route to the same route with a different parameter (e.g. going from /page/1 to /page/2 given a route /page/:id, the component would not be recreated from scratch since both routes resolve to the same component, and thus result in a virtual dom in-place diff. This has the side-effect of triggering the onupdate hook, rather than oninit/oncreate. However, it's relatively common for a developer to want to synchronize the recreation of the component to the route change event.