Route change error

In angular2 what is the equivalent of these angular1 events? $routeChangeStart , $routeChangeSuccess ,$routeChangeError I want to use these events in root app component to deal with any route

In angular2 what is the equivalent of these angular1 events?

$routeChangeStart , $routeChangeSuccess ,$routeChangeError

I want to use these events in root app component to deal with any route changing.

thanks in advance

update

  • If i use OnActivate, OnDeactivate events, it needs to add it to every component :(

  • Also i tried to implement my own RouterOutlet but i can’t access previous instruction on activate, deactivate events, also i don’t know how to handle the route Change Error

  • Also router.subscribe gave me the name only but i want to get the current and next instruction objects to change some properties

My target is:

1- Show loading indicator when start change page and hide it on page load.

2- Make transition animation rtl or ltr for current and next instruction

danday74's user avatar

danday74

49.9k48 gold badges222 silver badges270 bronze badges

asked Nov 5, 2015 at 10:32

Bahgat Mashaly's user avatar

3

There are some events such as OnActivate, OnDeactivate and guard functions CanActivate and CanDeactivate that you can look at.

See the documentation for Router

answered Nov 5, 2015 at 11:03

Chandermani's user avatar

ChandermaniChandermani

42.5k12 gold badges85 silver badges87 bronze badges

6

i solved the problem, for any one who face this problem , you can make your own RouterOutlet directive which inherit from RouterOutlet then use it as a proxy

answered Mar 15, 2016 at 20:57

Bahgat Mashaly's user avatar

John Lindquist: I’ve kind of coded myself into a corner here to prove a very important point. You might be thinking to yourself, what happens if the data fails to load? I’m going to get rid of the second promise. Instead of resolving this, I’m going to reject it. Hit refresh and you’ll see that if we have it set up this way, absolutely nothing will happen if the data fails to load and there’s nothing you can do on your controller to grab it.

Controlling will never be instantiated. Your view will never load. You’re pretty much out of luck. What you should do when your site first loads up is always have a controller that can handle that sort of failure. Instead of using your app controller to be the controller that gets tied to the view, we’re going to rename this to «view controller.» We’re going to use the view controller whenever this is created.

Now, instead of having the app controller do all that work, you grab this one. As far as setting up the scope, we’re going to have our app controller handle the more basic stuff, like what happens when route changes fail. The app controller has the root scope because this guy is going to listen for any of the events that come up to the root scope. We’ll get more into advanced root scope later on. It’s good to be at least aware of what’s going on.

Let’s say root scope on, and the event we’re going to listen for is route change error. Then we’ll have a callback in here to say «failed to change routes.» Basically what we can do now is refresh and it says «failed to change routes.» We successfully handled this error. This would be something like if we made an http request and it failed to load. You can just reject it and send it back. We can do something now that this has failed.

Now that we can do that, there are some other parameters you should know about. The first one will give you more info about the event. The second one is the previous route, the route that existed before you got to this one. I think it’s current then previous. I’ll double check. The last one is the rejection.

If you want to pass a custom rejection message, you can just pass it through here. Say your network is down and then we could log out or handle that however you wanted. We’ll just say «rejection.» This is just going to pass straight through here, fire it into here. You can handle it here.

Your network is down. Let’s just shorten this for brevity’s sake. Let’s double check the current and previous. We’ll log out of current. This is right because that’s the current path we’re trying to get to. Then the previous should be undefined because we didn’t have a path before we got here. We’re good there.

Then, just look at the event real quick before we get into other events. You can see we have «route change error» as the name of the event. The other things that it passes along, like the scope, with any of the events they could fire it off.

Again, just to recap what we did there, we refactored what we use as the app controller into a view controller; something that will get used whenever that view is loaded. Then we created a new app controller and reassigned it to the root element of our app.

We’re going to use that to handle these kinds of global things that can fail or at least global within our module, such as a route change error. Within a route change error we can handle it using properties from the event, the current route or the previous route.

We can get more into advanced routing to look at the differences between current and previous or just send some sort of rejection object you can handle and throw any sort of error you want to the user.

Before moving forward, we recommend you to read Routing Introduction first.

useRouter

If you want to access the router object inside any function component in your app, you can use the useRouter hook, take a look at the following example:

import { useRouter } from 'next/router'

function ActiveLink({ children, href }) {
  const router = useRouter()
  const style = {
    marginRight: 10,
    color: router.asPath === href ? 'red' : 'black',
  }

  const handleClick = (e) => {
    e.preventDefault()
    router.push(href)
  }

  return (
    <a href={href} onClick={handleClick} style={style}>
      {children}
    </a>
  )
}

export default ActiveLink

useRouter is a React Hook, meaning it cannot be used with classes. You can either use withRouter or wrap your class in a function component.

router object

The following is the definition of the router object returned by both useRouter and withRouter:

  • pathname: String — The path for current route file that comes after /pages. Therefore, basePath, locale and trailing slash (trailingSlash: true) are not included.
  • query: Object — The query string parsed to an object, including dynamic route parameters. It will be an empty object during prerendering if the page doesn’t use Server-side Rendering. Defaults to {}
  • asPath: String — The path as shown in the browser including the search params and respecting the trailingSlash configuration. basePath and locale are not included.
  • isFallback: boolean — Whether the current page is in fallback mode.
  • basePath: String — The active basePath (if enabled).
  • locale: String — The active locale (if enabled).
  • locales: String[] — All supported locales (if enabled).
  • defaultLocale: String — The current default locale (if enabled).
  • domainLocales: Array<{domain, defaultLocale, locales}> — Any configured domain locales.
  • isReady: boolean — Whether the router fields are updated client-side and ready for use. Should only be used inside of useEffect methods and not for conditionally rendering on the server. See related docs for use case with automatically statically optimized pages
  • isPreview: boolean — Whether the application is currently in preview mode.

Using the asPath field may lead to a mismatch between client and server if the page is rendered using server-side rendering or automatic static optimization. Avoid using asPath until the isReady field is true.

The following methods are included inside router:

router.push

Examples

  • Using Router

Handles client-side transitions, this method is useful for cases where next/link is not enough.

router.push(url, as, options)
  • url: UrlObject | String — The URL to navigate to (see Node.JS URL module documentation for UrlObject properties).
  • as: UrlObject | String — Optional decorator for the path that will be shown in the browser URL bar. Before Next.js 9.5.3 this was used for dynamic routes, check our previous docs to see how it worked. Note: when this path differs from the one provided in href the previous href/as behavior is used as shown in the previous docs
  • options — Optional object with the following configuration options:
    • scroll — Optional boolean, controls scrolling to the top of the page after navigation. Defaults to true
    • shallow: Update the path of the current page without rerunning getStaticProps, getServerSideProps or getInitialProps. Defaults to false
    • locale — Optional string, indicates locale of the new page

You don’t need to use router.push for external URLs. window.location is better suited for those cases.

Usage

Navigating to pages/about.js, which is a predefined route:

import { useRouter } from 'next/router'

export default function Page() {
  const router = useRouter()

  return (
    <button type="button" onClick={() => router.push('/about')}>
      Click me
    </button>
  )
}

Navigating pages/post/[pid].js, which is a dynamic route:

import { useRouter } from 'next/router'

export default function Page() {
  const router = useRouter()

  return (
    <button type="button" onClick={() => router.push('/post/abc')}>
      Click me
    </button>
  )
}

Redirecting the user to pages/login.js, useful for pages behind authentication:

import { useEffect } from 'react'
import { useRouter } from 'next/router'

// Here you would fetch and return the user
const useUser = () => ({ user: null, loading: false })

export default function Page() {
  const { user, loading } = useUser()
  const router = useRouter()

  useEffect(() => {
    if (!(user || loading)) {
      router.push('/login')
    }
  }, [user, loading])

  return <p>Redirecting...</p>
}

Resetting state after navigation

When navigating to the same page in Next.js, the page’s state will not be reset by default as react does not unmount unless the parent component has changed.

// pages/[slug].js
import Link from 'next/link'
import { useState } from 'react'
import { useRouter } from 'next/router'

export default function Page(props) {
  const router = useRouter()
  const [count, setCount] = useState(0)
  return (
    <div>
      <h1>Page: {router.query.slug}</h1>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increase count</button>
      <Link href="/one">one</Link> <Link href="/two">two</Link>
    </div>
  )
}

In the above example, navigating between /one and /two will not reset the count . The useState is maintained between renders because the top-level React component, Page, is the same.

If you do not want this behavior, you have a couple of options:

  1. Manually ensure each state is updated using useEffect. In the above example, that could look like:
useEffect(() => {
  setCount(0)
}, [router.query.slug])
  1. Use a React key to tell React to remount the component. To do this for all pages, you can use a custom app:
// pages/_app.js
import { useRouter } from 'next/router'

export default function MyApp({ Component, pageProps }) {
  const router = useRouter()
  return <Component key={router.asPath} {...pageProps} />
}

With URL object

You can use a URL object in the same way you can use it for next/link. Works for both the url and as parameters:

import { useRouter } from 'next/router'

export default function ReadMore({ post }) {
  const router = useRouter()

  return (
    <button
      type="button"
      onClick={() => {
        router.push({
          pathname: '/post/[pid]',
          query: { pid: post.id },
        })
      }}
    >
      Click here to read more
    </button>
  )
}

router.replace

Similar to the replace prop in next/link, router.replace will prevent adding a new URL entry into the history stack.

router.replace(url, as, options)
  • The API for router.replace is exactly the same as the API for router.push.

Usage

Take a look at the following example:

import { useRouter } from 'next/router'

export default function Page() {
  const router = useRouter()

  return (
    <button type="button" onClick={() => router.replace('/home')}>
      Click me
    </button>
  )
}

router.prefetch

Prefetch pages for faster client-side transitions. This method is only useful for navigations without next/link, as next/link takes care of prefetching pages automatically.

This is a production only feature. Next.js doesn’t prefetch pages in development.

router.prefetch(url, as, options)
  • url — The URL to prefetch, including explicit routes (e.g. /dashboard) and dynamic routes (e.g. /product/[id])
  • as — Optional decorator for url. Before Next.js 9.5.3 this was used to prefetch dynamic routes, check our previous docs to see how it worked
  • options — Optional object with the following allowed fields:
    • locale — allows providing a different locale from the active one. If false, url has to include the locale as the active locale won’t be used.

Usage

Let’s say you have a login page, and after a login, you redirect the user to the dashboard. For that case, we can prefetch the dashboard to make a faster transition, like in the following example:

import { useCallback, useEffect } from 'react'
import { useRouter } from 'next/router'

export default function Login() {
  const router = useRouter()
  const handleSubmit = useCallback((e) => {
    e.preventDefault()

    fetch('/api/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        /* Form data */
      }),
    }).then((res) => {
      // Do a fast client-side transition to the already prefetched dashboard page
      if (res.ok) router.push('/dashboard')
    })
  }, [])

  useEffect(() => {
    // Prefetch the dashboard page
    router.prefetch('/dashboard')
  }, [])

  return (
    <form onSubmit={handleSubmit}>
      {/* Form fields */}
      <button type="submit">Login</button>
    </form>
  )
}

router.beforePopState

In some cases (for example, if using a Custom Server), you may wish to listen to popstate and do something before the router acts on it.

router.beforePopState(cb)
  • cb — The function to run on incoming popstate events. The function receives the state of the event as an object with the following props:
    • url: String — the route for the new state. This is usually the name of a page
    • as: String — the url that will be shown in the browser
    • options: Object — Additional options sent by router.push

If cb returns false, the Next.js router will not handle popstate, and you’ll be responsible for handling it in that case. See Disabling file-system routing.

Usage

You could use beforePopState to manipulate the request, or force a SSR refresh, as in the following example:

import { useEffect } from 'react'
import { useRouter } from 'next/router'

export default function Page() {
  const router = useRouter()

  useEffect(() => {
    router.beforePopState(({ url, as, options }) => {
      // I only want to allow these two routes!
      if (as !== '/' && as !== '/other') {
        // Have SSR render bad routes as a 404.
        window.location.href = as
        return false
      }

      return true
    })
  }, [])

  return <p>Welcome to the page</p>
}

router.back

Navigate back in history. Equivalent to clicking the browser’s back button. It executes window.history.back().

Usage

import { useRouter } from 'next/router'

export default function Page() {
  const router = useRouter()

  return (
    <button type="button" onClick={() => router.back()}>
      Click here to go back
    </button>
  )
}

router.reload

Reload the current URL. Equivalent to clicking the browser’s refresh button. It executes window.location.reload().

Usage

import { useRouter } from 'next/router'

export default function Page() {
  const router = useRouter()

  return (
    <button type="button" onClick={() => router.reload()}>
      Click here to reload
    </button>
  )
}

router.events

Examples

  • With a page loading indicator

You can listen to different events happening inside the Next.js Router. Here’s a list of supported events:

  • routeChangeStart(url, { shallow }) — Fires when a route starts to change
  • routeChangeComplete(url, { shallow }) — Fires when a route changed completely
  • routeChangeError(err, url, { shallow }) — Fires when there’s an error when changing routes, or a route load is cancelled
    • err.cancelled — Indicates if the navigation was cancelled
  • beforeHistoryChange(url, { shallow }) — Fires before changing the browser’s history
  • hashChangeStart(url, { shallow }) — Fires when the hash will change but not the page
  • hashChangeComplete(url, { shallow }) — Fires when the hash has changed but not the page

Note: Here url is the URL shown in the browser, including the basePath.

Usage

For example, to listen to the router event routeChangeStart, open or create pages/_app.js and subscribe to the event, like so:

import { useEffect } from 'react'
import { useRouter } from 'next/router'

export default function MyApp({ Component, pageProps }) {
  const router = useRouter()

  useEffect(() => {
    const handleRouteChange = (url, { shallow }) => {
      console.log(
        `App is changing to ${url} ${
          shallow ? 'with' : 'without'
        } shallow routing`
      )
    }

    router.events.on('routeChangeStart', handleRouteChange)

    // If the component is unmounted, unsubscribe
    // from the event with the `off` method:
    return () => {
      router.events.off('routeChangeStart', handleRouteChange)
    }
  }, [])

  return <Component {...pageProps} />
}

We use a Custom App (pages/_app.js) for this example to subscribe to the event because it’s not unmounted on page navigations, but you can subscribe to router events on any component in your application.

Router events should be registered when a component mounts (useEffect or componentDidMount / componentWillUnmount) or imperatively when an event happens.

If a route load is cancelled (for example, by clicking two links rapidly in succession), routeChangeError will fire. And the passed err will contain a cancelled property set to true, as in the following example:

import { useEffect } from 'react'
import { useRouter } from 'next/router'

export default function MyApp({ Component, pageProps }) {
  const router = useRouter()

  useEffect(() => {
    const handleRouteChangeError = (err, url) => {
      if (err.cancelled) {
        console.log(`Route to ${url} was cancelled!`)
      }
    }

    router.events.on('routeChangeError', handleRouteChangeError)

    // If the component is unmounted, unsubscribe
    // from the event with the `off` method:
    return () => {
      router.events.off('routeChangeError', handleRouteChangeError)
    }
  }, [])

  return <Component {...pageProps} />
}

Potential ESLint errors

Certain methods accessible on the router object return a Promise. If you have the ESLint rule, no-floating-promises enabled, consider disabling it either globally, or for the affected line.

If your application needs this rule, you should either void the promise – or use an async function, await the Promise, then void the function call. This is not applicable when the method is called from inside an onClick handler.

The affected methods are:

  • router.push
  • router.replace
  • router.prefetch

Potential solutions

import { useEffect } from 'react'
import { useRouter } from 'next/router'

// Here you would fetch and return the user
const useUser = () => ({ user: null, loading: false })

export default function Page() {
  const { user, loading } = useUser()
  const router = useRouter()

  useEffect(() => {
    // disable the linting on the next line - This is the cleanest solution
    // eslint-disable-next-line no-floating-promises
    router.push('/login')

    // void the Promise returned by router.push
    if (!(user || loading)) {
      void router.push('/login')
    }
    // or use an async function, await the Promise, then void the function call
    async function handleRouteChange() {
      if (!(user || loading)) {
        await router.push('/login')
      }
    }
    void handleRouteChange()
  }, [user, loading])

  return <p>Redirecting...</p>
}

withRouter

If useRouter is not the best fit for you, withRouter can also add the same router object to any component.

Usage

import { withRouter } from 'next/router'

function Page({ router }) {
  return <p>{router.pathname}</p>
}

export default withRouter(Page)

TypeScript

To use class components with withRouter, the component needs to accept a router prop:

import React from 'react'
import { withRouter, NextRouter } from 'next/router'

interface WithRouterProps {
  router: NextRouter
}

interface MyComponentProps extends WithRouterProps {}

class MyComponent extends React.Component<MyComponentProps> {
  render() {
    return <p>{this.props.router.pathname}</p>
  }
}

export default withRouter(MyComponent)

AngularJS routing doesn’t support route authorization out of the box and nor does its popular cousin ui-router even though the latter supports state change cancellation with later continuation (using $urlRouter.sync(); check documentation). But in non-trivial Angular SPAs this feature is regularly needed. So I went off implementing it.

Work of others

It is possible to find several working examples for this problem on the internet that do route authorization, but I haven’t come across an elegant example that wouldn’t authorize synchronously. With this I mean requesting server for authorization info (be it general per-user or granular per-route). This means that an asynchronous request has to be sent to server during routing phase. And that’s exactly what said examples most commonly don’t implement. They also don’t reveal how they initially obtain user roles? Whether they’re obtained before SPA bootstraping (this would require manual bootstraping) or after.

What’s already supported by ngRoute

The first thing we think of when we mention promises (which an anyc server request is) and routing are route resolves (check resolve property of the route parameter object in Angular documentation). This parameter defines any promises that need to be resolved before your routed view is being processed. Documentation states that this is a mapping of dependencies that will be injected into controller some of which may be promises that will first get resolved before controller is instantiated. Ok, so there is some already supported mechanism in ngRoute that can get us closer to route authorization. The problem is that we should be manually adding resolves to all routes that require some form of authorization. That’s quite a bit of code duplication on the routing configuration as we’d have to be doing something along these lines:

// some route definition object
{
  templateUrl: "viewTemplate.html",
  controller: "SomeController",
  controllerAs: "vm",
  resolve: {
    authorize: ["userService", "$location", function(userService, $location) {
      return userService.getUserInfo()
                        .then(function(userInfo) {
                          if (userInfo.isAnonymous)
                          {
                            $location.path("/");
                          }
                          return userInfo;
                        });
    }]
  }
}

…for every route. Not to mention if we wanted to implement role based authorization. And if we wanted to introduce some change to this process we’d have to change all routes that apply. Maintainability nightmare!

There’s also one much greater problem with upper technique that you should be aware of. Authorized route resolution is taking place even on unauthorized route requests. And that is particularly problematic, because authorization-secured route controller gets instantiated possibly making some server resource requests in the process. Not to mention that authorization-secured view also gets processed along the way. But at least this major problem can be solved by throwing an error in our resolve which basically stops route from processing all the way to success rather ending up in a route change error state.

// some route definition object
{
  templateUrl: "viewTemplate.html",
  controller: "SomeController",
  controllerAs: "vm",
  resolve: {
    authorize: ["userService", "$location", function(userService, $location) {
      return userService.getUserInfo()
                        .then(function(userInfo) {
                          if (userInfo.isAnonymous)
                          {
                            $location.path("/");
                            throw 302; // THIS ERROR as HTTP 302 Found
                          }
                          return userInfo;
                        });
    }]
  }
}

Which gets us to centralized solution

Specifically that route change error makes us think of centralized set it and forget it solution to this problem with minor code additions to routing configuration and no code duplication. There are three routing events in ngRoute that we can add listeners to and interfere with the process to our requirements.

  • $routeChangeStart which fires before route starts processing its resolves
  • $routeChangeSuccess which fires right after all route resolves are successfully resolved and
  • $routeChangeError which fires after resolving route resolves but at least one failed resolution

If we think of server authorization process it works like this:

  1. client issues an HTTP request for some authorization-secured resource
  2. server processes authorization
  3. if authorization succeeds authorization-secured resource is returned
    if authorization fails an HTTP 302 Found error is being returned instead with Location header pointing to redirection resource — usually login (although this may vary on the server and platform we’re using)

A similar process should be utilized in the case of client-side routing authorization.

Final solution process

Following solution shows simple authenticated flag authorization but it could easily be changed to support role-based authentication as well. I’ll explain how to do it later on. In any way the process works like this:

  1. client side routing starts for authorized route (authorized routes have an additional custom property authorize: true)
  2. $routeChangeStart event fires where we inject authorization resolve making sure we only inject it once
  3. authorization resolve makes a server request getting authorization information and checks its result with authorization requirements
  4. if authorization succeeds, nothing is particularly done so routing will continue to execute
    if authorization fails, we throw a specific error type (similar to server responding with 302 and not doing anything further)
  5. on fail $routeChangeError event handler executes where error type is being checked (don’t assume it’s always authorization problem as other resolves may fail for other reasons) and if it matches our custom AuthorizationError a redirect is being done to login route

And that’s basically how it works. now let’s see the code.

Routing configuration

Routing still needs some sort of configuration so we define somehow which routes require authorization. But since we’re building a centralized solution to this problem, we want to simplify this as much as possible. So we only add a simple route configuration property.

// some route definition object
{
  templateUrl: "viewTemplate.html",
  controller: "SomeController",
  controllerAs: "vm",
  authorize: true
}

Now this is a lot less code than previously with route resolves. We only mark those routes that require authorization and keep others as they are.

Route change events

Main logic is part of these events. We have to inject a missing resolve in routes that require it and then handle authorization errors by redirecting to login route. Nothing particularly complicated.

   1:  angular
   2:      .module("ModuleName", ["ngRoute"])
   3:      .config(/* routing configuration */)
   4:      .run(["$rootScope", "$location", function($rootScope, $location) {
   5:          $rootScope.$on("$routeChangeStart", function(evt, to, from) {
   6:              // requires authorization?
   7:              if (to.authorize === true)
   8:              {
   9:                  to.resolve = to.resolve || {};
  10:                  if (!to.resolve.authorizationResolver)
  11:                  {
  12:                      // inject resolver
  13:                      to.resolve.authorizationResolver = ["authService", function(authService) {
  14:                          return authService.authorize();
  15:                      }];
  16:                  }
  17:              }
  18:          });
  19:   
  20:          $rootScope.$on("$routeChangeError", function(evt, to, from, error) {
  21:              if (error instanceof AuthorizationError)
  22:              {
  23:                  // redirect to login with original path we'll be returning back to
  24:                  $location
  25:                      .path("/login")
  26:                      .search("returnTo", to.originalPath);
  27:              }
  28:          });
  29:      }]);

Other minor things

There are other minor things that need implementation like AuthorizationError type or AuthorizationService implementation, but especially the latter is completely up to you.

Complete code of a running example

I’ve created a plnkr example that you can play with and see how it works. If you want to see how individual parts execute and in what order, make sure you open developer console and observe logs created by the code. Below code is without the additional logging but would run just the same.

   1:  <!DOCTYPE html>
   2:  <html ng-app="Test">
   3:    <head>
   4:      <meta charset="utf-8" />
   5:      <title>Angular routing authentication example</title>
   6:      <style>
   7:          body {
   8:              font-family: Sans-serif;
   9:          }
  10:          section {
  11:              margin-top: 2em;
  12:              border: 1px solid #ccc;
  13:              padding: 0 2em 1em;
  14:          }
  15:          .important {
  16:              color: #e00;
  17:          }
  18:      </style>
  19:    </head>
  20:   
  21:    <body>
  22:      <h3>Angular routing authorization implementation</h3>
  23:      <p>
  24:          Home and login are public views and load immediately because they don't have
  25:          any promises to resolve (set by <code>route.resolve</code>).
  26:          Authorized view is only accessible after user is authenticated.
  27:          Authorization promise resolves in 1 second.
  28:      </p>
  29:      <p>
  30:          Anonymous users are <strong>auto-redirected to login</strong> when trying to
  31:          access Authorized view. After login they're auto-redirected back to where they
  32:          were before redirection to login view. If users manually access login view
  33:          (clicking Login link) they're redirected to Home after login/logout.
  34:      </p>
  35:      <p class="important">Open <strong>development console</strong> to observe execution log.</p>
  36:      <a href="#/home">Home</a> |
  37:      <a href="#/private">Authorized</a> |
  38:      <a href="#/login">Login</a>
  39:      
  40:      <section>
  41:          <ng-view></ng-view>
  42:      </section>
  43:      
  44:      <script type="text/ng-template" id="login">
  45:          <h1>Login view</h1>
  46:          <p>This is a publicly accessible login view</p>
  47:          <p><a href="" ng-click="context.login()">click here</a> to auto-authenticate</p>
  48:          <p><a href="" ng-click="context.logout()">logout</a> to prevent authorized view access.</p>
  49:      </script>
  50:   
  51:      <script type="text/ng-template" id="public">
  52:          <h1>Public view</h1>
  53:          <p>This is a publicly accessible view</p>
  54:      </script>
  55:   
  56:      <script type="text/ng-template" id="private">
  57:          <h1>Authorized view</h1>
  58:          <p>This is an authorized view</p>
  59:      </script>
  60:   
  61:      <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.6/angular.min.js"></script>
  62:      <script src="https://code.angularjs.org/1.4.3/angular-route.js"></script>
  63:      <script type="text/javascript">
  64:          (function(angular){
  65:   
  66:              "use strict";
  67:   
  68:              angular
  69:                  .module("Test", ["ngRoute"])
  70:                  
  71:                  .config(function($routeProvider){
  72:                      $routeProvider
  73:                          .when("/", {
  74:                              templateUrl: "public",
  75:                              controller: "GeneralController",
  76:                              controllerAs: "context"
  77:                          })
  78:                          .when("/login", {
  79:                              templateUrl: "login",
  80:                              controller: "LoginController",
  81:                              controllerAs: "context"
  82:                          })
  83:                          .when("/private", {
  84:                              templateUrl: "private",
  85:                              controller: "GeneralController",
  86:                              controllerAs: "context",
  87:                              authorize: true
  88:                          })
  89:                          .otherwise({
  90:                              redirectTo: "/"
  91:                          });
  92:                  })
  93:          
  94:                  .run(function($rootScope, $location){
  95:                      $rootScope.$on("$routeChangeStart", function(evt, to, from){
  96:                          if (to.authorize === true)
  97:                          {
  98:                              to.resolve = to.resolve || {};
  99:                              if (!to.resolve.authorizationResolver)
 100:                              {
 101:                                  to.resolve.authorizationResolver = function(authService) {
 102:                                      return authService.authorize();
 103:                                  };
 104:                              }
 105:                          }
 106:                      });
 107:                      
 108:                      $rootScope.$on("$routeChangeError", function(evt, to, from, error){
 109:                          if (error instanceof AuthorizationError)
 110:                          {
 111:                              $location.path("/login").search("returnTo", to.originalPath);
 112:                          }
 113:                      });
 114:                  })
 115:                  
 116:                  .controller("LoginController", function($location, authService){
 117:                      this.login = login(true, $location.search().returnTo);
 118:                      this.logout = login(false, "/");
 119:                      // DRY helper
 120:                      function login(doWhat, whereTo){
 121:                          return function() {
 122:                              authService.authenticated = doWhat;
 123:                              $location.path(whereTo && whereTo || "/");
 124:                          };
 125:                      }
 126:                  })
 127:                  
 128:                  .controller("GeneralController", function(){
 129:                  })
 130:                  
 131:                  .service("authService", function($q, $timeout){
 132:                      var self = this;
 133:                      this.authenticated = false;
 134:                      this.authorize = function() {
 135:                          return this
 136:                              .getInfo()
 137:                              .then(function(info){
 138:                                  if (info.authenticated === true)
 139:                                      return true;
 140:                                  // anonymous
 141:                                  throw new AuthorizationError();
 142:                              });
 143:                      };
 144:                      this.getInfo = function() {
 145:                          return $timeout(function(){
 146:                              return self;
 147:                          }, 1000);
 148:                      };
 149:                  });
 150:                  
 151:                  // Custom error type
 152:                  function AuthorizationError(description) {
 153:                      this.message = "Forbidden";
 154:                      this.description = description || "User authentication required.";
 155:                  }
 156:                  
 157:                  AuthorizationError.prototype = Object.create(Error.prototype);
 158:                  AuthorizationError.prototype.constructor = AuthorizationError;
 159:          
 160:          })(angular);
 161:      </script>
 162:   
 163:    </body>
 164:   
 165:  </html>

How about those user roles

The same code with minor modifications can also be used for role-based authorization. Depending on how your role security should work you’d mainly have to make changes below. By how I mean how authorization should be. Whether you would be granting permission to single role or a combination. Suppose we define three distinct roles: anonymous, authenticated and administrator.

  1. routing configuration should replace authorize: true to one of the these:
    • authorize: «authenticated» when you’d like to authorise against single roles
    • authorize: ["authenticated", "administrator"] or authorize: "authenticated|administrator" when you’d like to authorize against several roles and depending whether role matching would be done using array’s .indexOf() or string’s regular expression .test() matching
  2. authorization service’s .authorize(roles) should now accept a parameter with route roles which you’d use to decide on the outcome of authorization execution
  3. $routeChangeStart should provide roles when injecting authorization resolve so that authorization service would work as per previous alinee

Anything else? I don’t think so. That should enable you to implement role-base route authorization in your Angular SPA.

One warning though

I should warn you about one last thing. Some very old AngularJS bug that’s been around since 2013 or at least that was the time it was reported. The bug is about browser history. When routing starts resolving an and even if it ends up in the $routeChangeError state, your browser’s location (URL) would still be changed as if routing took place successfully. This is particularly problematic in our case where we deliberately fail route changes due to failed authorization checks.

Don’t say I didn’t warn you. You can mitigate this problem somehow by rewriting URL using $location.path(fromRouteURL).rewrite() which would replace failed route’s URL to previous one. You can also call $window.histroy.back() after it, so you don’t end up with two identical states in your history if you’d be clicking back button in your browser. Until your next navigation you’d have a forward state defined, but users are a lot more likely to click back than forward.

If you have any suggestions of your own, you’re welcome to participate on GitHub’s issue or comment on this post and if I find your idea particularly interesting I’ll pass it forward to develpers on GitHub.

OS?

Windows 10

Versions

angular-cli@1.0.0-rc.0

Background

Angular App was created using angular-cli. Angular App contains a NavbarComponent that displays two menu items «Home» and «About». Angular App uses the Angular Router and a NavbarComponent to allow the user to navigate to two component views: HomeComponent View and AboutComponent View. The base route for the app is set inside index.html as . A Router Configuration File is defined and defines two routes, one route as the HomeComponent default route with a path: » and one route as the AboutComponent Path with a path: ‘about’. The ConfiguredRouterModule is exported from app.routing.ts The ConfiguredRouterModule is imported into app.module.ts. A directive is defined inside the AppComponent HTML Template. A routerLink directive is defined for each menu item link inside the NavbarComponent. To build the project I use the angular-cli command «ng build —prod». I copy files from «dist» folder to destination web server folder. Note that the destination web server folder is a sub folder under the root folder on the web server. Example «root/projects/router» and not just the «root» folder

NavbarComponent Menu Item Links correctly render Component Views

When a user clicks on either or the NavbarComponent menu item links «Home» or «About» the correct Component View displays inside the inside the AppComponent HTML Template

Issue

When a user manually changes the URL in the browser
from «http://www.abc.com/projects/router/» to «http://www.abc.com/projects/router/about» instead of the AboutComponent displaying, a 404 Page Not Found Error is displayed. Note that this behavior does not produce a 404 Page Not Found Error when running the ng serve command in the development environment. This behavior only produces a 404 Page Not Found Error when building and deploying code to a web server.

After I execute the command ng build --prod I have to change the <base href="value"> from <base href="/"> to <base href="/projects/router/">.

Note that if I try to build the project using the angular-cli command ng build --prod --base-href projects/router, after executing command if I open the index.html file inside the «dist» folder I see that angular-cli changed the <base href="value"> from <base href="/" to <base href="C:/Program Files/Git/projects/router/>

My Questions

How do I build the Angular Project so that the <base href="value"> is set correctly? More importantly why is the Angular App giving a 404 Error if a user manually changes the URL in the browser to a valid router route path? Example http://www.abc.com/projects/router/about»

what is the command to modify metric of an existing route entry in linux?
I am able to change gateway of an existing entry using «ip route change» command as below but not able to change metrics. Is there any other command for that?

route –n
40.2.2.0        30.1.3.2        255.255.255.0   eth2

ip route change 40.2.2.0/24 via 30.1.2.2

route -n
40.2.2.0        30.1.2.2        255.255.255.0   eth1

asked Nov 24, 2015 at 17:59

user1762571's user avatar

3

(Combining various comments into an answer)

Currently, it is not possible to modify a route’s metric. As a 2005 message on LKML states:

[…] The metric/priority cannot be changed because we do not have separate fields for the fields to match and the new values so if you specify a metric the entry simply won’t be found and the request fails with ENOENT because NLM_F_CREATE is not specified. This is a limitation of the current protocol and it might be a good idea to to change this, however it’s non trivial […]

This seems to apply both to ip route change and ip route replace — the former results in an error for me, while the latter creates an additional route as advertised (its man page states that replace will replace or create a route). This is consistent with the kernel responding with ENOENT, and ip route replace following up with a route creation request.


So, the solution is to delete the existing route and add a new one.
e.g.

ip route del 40.2.2.0/24 via 30.1.2.2
ip route add 40.2.2.0/24 via 30.1.2.2 metric 1234

answered Jul 23, 2018 at 16:57

Mark's user avatar

MarkMark

3911 gold badge3 silver badges5 bronze badges

3

ifmetric will allow you to change your route metric on the fly, given the interface the routes you want to change are going through.

Usage

ifmetric <iface> [<metric>]

For example, use ifmetric tun0 12 to change the metric to 12 for all routes going through tun0.

muru's user avatar

muru

67.3k11 gold badges187 silver badges281 bronze badges

answered Jun 5, 2019 at 16:01

AeroClassics's user avatar

0

As noted in a comment to the question, quoting a message on the linux-net mailing list: «The metric/priority cannot be changed […] This is a limitation of the current protocol […].»
The only way is to delete the route and add a new one.

This is done using the route command, example:

sudo route add -net default gw 10.10.0.1 netmask 0.0.0.0 dev wlan0 metric 1

Debian manpage for the route command

fra-san's user avatar

fra-san

9,5612 gold badges22 silver badges40 bronze badges

answered Nov 24, 2015 at 19:40

Jan's user avatar

JanJan

7,4401 gold badge33 silver badges40 bronze badges

3

It can be done by nmcli as mentioned below:-

nmcli connection modify ACTUAL_CONNECTION_NAME ipv4.route-metric 100
nmcli connection up ACTUAL_CONNECTION_NAME

schemacs's user avatar

schemacs

2671 gold badge4 silver badges9 bronze badges

answered Jul 3, 2021 at 7:34

NKishor's user avatar

Понравилась статья? Поделить с друзьями:
  • Roundoff error is detected in the extrapolation table
  • Roundcube smtp ошибка 250 ошибка авторизации
  • Roundcube internal server error 500
  • Roundcube imap error login failed
  • Roundcube error log