WARNING: This series of articles uses Angular 5 and RxJS 5. Please be aware that code changes are necessary to use Angular 6 and RxJS 6 with this tutorial. We are in the process of upgrading the series to latest versions. In the meantime, you can follow the update instructions here for more information. Thank you for your patience!

TL;DR: This 8-part tutorial series covers building and deploying a full-stack JavaScript application from the ground up with hosted MongoDB, Express, Angular (v2+), and Node.js (MEAN stack). The completed code is available in the mean-rsvp-auth0 GitHub repo and a deployed sample app is available at https://rsvp.kmaida.net. Part 4 of the tutorial series covers access management with Angular, displaying admin data, and setting up detail pages with tabs.

Angular: Access Management

In the Admin Authorization section of Part 2, we enabled administrative rights for a specific user login. In the API Events section, we authorized an /api/event/:id API endpoint that required authentication and an /api/events/admin endpoint that required authentication and admin access. We'll now take measures to protect authorized routes on the front end and manage access to components utilizing protected API routes.

Route Guards

We'll implement two route guards in our application. Route guards determine whether a user should be allowed to access a specific route or not. If the guard evaluates to true, navigation is allowed to complete. If the guard evaluates to false, the route is not activated and the user's attempted navigation does not take place. Multiple route guards can be used on a single route in a chaining fashion, meaning a user can be required to pass multiple checks before they're granted access to a route—similar to how we added multiple middleware functions to our Node API endpoints.

We have two levels of authorization for various routes that require guards:

Redirecting To and From Login

There will be an important extra feature in our authentication route guard that will require some updates to our authentication service: redirection to and from login. When an unauthenticated user arrives at our app, we want to limit disruptions to their experience as much as possible. This means that we'll surface links to authenticated routes in our public event listing, and then prompt the user to log in when such links are clicked. After they authenticate, they'll be redirected to the protected route.

This reduces friction in the application because we're taking care not to redirect users back to a homepage. This would force them to try to find their own way back to the route they were originally trying to access. It also helps keep the user on track if they manually typed in a URL or clicked a link someone else sent them.

Note: Route guards are for the UI only. They don't confer any security when it comes to accessing an API. However, we are enforcing authentication and authorization in our API (as you should do in all your apps), so we can take advantage of guards to authenticate and redirect users as well as stop unauthorized navigation.

Create an Authenticated Route Guard

Let's create a new route guard. The first thing we need to know is whether or not the user is logged in. We'll call this route guard AuthGuard.

The boilerplate imports the CanActivate interface to implement the logic declaring whether or not the user should be allowed access to the route. We also need both ActivatedRouteSnapshot and RouterStateSnapshot to gain access to route information for redirection. RxJS provides Observable for type annotation and finally, we need to add the AuthService to access its methods.

The logic in the canActivate() function is pretty straightforward. Route guards operate on returning true or false based on a condition that has to be fulfilled to permit navigation. Our condition is auth.tokenValid from our AuthService. If the user is authenticated with an unexpired token, we can return true and navigation continues.

However, if the user is not authenticated, we'll send the guarded route to the auth.login() method. This will allow us to redirect after returning from the hosted Auth0 login, which is outside the application. We'll prompt the user to log in to continue with navigation and return false to ensure navigation cannot complete.

Update Authentication Service to Manage Redirects

The route guard contains a URL to redirect to on successful authentication, so our auth.service.ts needs to utilize it. Let's make the necessary changes to this file:

In the login() method, we'll now check for a redirect parameter. If there isn't one, this means the user initialized the login() method from the header link and not from the route guard. In this case, we'll set _redirect to the current URL so the user returns here after authenticating. We'll then set the _redirect in local storage.

If the hash is successfully parsed with the appropriate tokens in the handleAuth() function, we'll redirect the user in the _getProfile() method. If an error occurs, we'll clear the redirect (method declared further down in code), navigate to the homepage, and display the error in the console.

As mentioned above, the _getProfile() method will now navigate to the stored redirect URL (or as a failsafe, to the homepage). It will then clear the redirect from local storage to ensure no lingering data is left behind.

The _clearRedirect() method is simply a shortcut that removes the authRedirect item from local storage, since we do this several times throughout the service.

Finally, on logout() we'll clear the redirect. Since Home is the only component that does not require authentication to view, we'll navigate to the homepage.

Create an Admin Route Guard

Now that we have our authentication guard and service updated, the admin guard will be simple by comparison. Create a new guard:

The admin guard will run after the authentication guard, so we'll get all the benefits of the authentication guard too (such as auth checking and redirection). All the admin guard needs to do is check if the authenticated user is an admin and if not, navigate to the homepage.

Import Guards in Routing Module

Finally, in order to use our route guards, we need to import them in our app-routing.module.ts:

We'll import our two route guards and then add them to the providers array.

We're now ready to guard routes. The next step is to create protected routes!

Angular: Admin Component Event List

We want an Admin component to display a list of all events, including past and private events (unlike the Home component, which only shows upcoming, public events). The Admin component also needs to be protected by both the authentication guard and the admin guard.

Let's create an Admin component with the CLI now:

$ ng g component pages/admin

Admin Component Route

Now we'll add the Admin component to our routes in the app-routing.module.ts file:

Import the new AdminComponent. You'll notice we've set up this route a bit differently. The /admin route will eventually have other child routes, including pages to create and update events. We want all routes under the admin URL segment to be protected, so we'll add a canActivate array containing our two route guards, AuthGuard and AdminGuard. For now, we just have a root child route which uses the Admin component. We'll add the other children later.

Add Admin Link in Navigation

Let's add a link to the Admin page in our off-canvas navigation. To do this, open the header.component.html template:

We'll add an "Admin" link that only shows if the user is auth.loggedIn and auth.isAdmin. Because our admin route has children, we'll also add exact: true to the [routerLinkActiveOptions] directive to prevent the parent "Admin" link from being marked as active when any of its children are active.

This link should now appear in the navigation when an admin user is logged in.

This is very similar to the Home component we set up in Angular: Home Component Event List. The only difference is that we'll import the Auth service. Other than that, we'll set the title, get the full admin events list, implement search functionality, and unsubscribe on destruction of the component.

Admin Component Template

Before we create our template, let's add a couple of icons to our assets folder. Download this calendar icon SVG and eye icon SVG. Right-click the links and save both icons to your src/assets/images folder.

Again, this is very similar to our Home component's implementation. However, we'll start with a paragraph greeting our admin user. We're showing icons in our event list indicating if an event is in the past (with the calendar icon) or if it has viewPublic: false. Also, we're only linking the title of the event to its detail page instead of the entire list item because we'll be adding "Edit" and "Delete" buttons to each event later.

Now open admin.component.scss to add a few styles for our event icons:

Because our Admin component and API route are protected, you'll have to log into the app with the admin user you specified in the Admin Authorization section of Part 2 in order to view the page. Once you're logged in, the Admin component should look something like this:

If unauthenticated users attempt to access this page, they'll be prompted to log in. If they are admin upon logging in, they'll be granted access. If they don't have admin rights, they'll be redirected to the homepage by our admin route guard. If a user logs out from this page, they'll also be redirected to the homepage. Try it out!

Security Note: Even if a user was somehow able to circumvent the front end protection, the Node API would not return the events data without the correct admin role concealed in the access token.

That's all we'll do with the Admin page for now. Later on, we'll add links to create and update events.

Angular: Event Component

In our Home and Admin components, we linked individual events by their IDs. Now it's time to create the event detail page that these links lead to.

We'll import and add our event/:id route. The Event component requires authentication to access, so we'll also add AuthGuard to it. We can now access events in the browser by clicking on them from an event listing (from the Home or Admin pages).

Create Event Detail and RSVP Components

Our Event component is going to have two tabs with child components: an Event Detail component and an RSVP component. The routed Event component will provide data to the child components via input binding, so we'll manage tab navigation with route parameters.

Note: The other alternative would be to use Resolve in the parent route to fetch API data and then use child routes that observe the parent's resolve data. However, we are avoiding route resolves for reasons cited earlier in this tutorial, such as the appearance of sluggish navigation. However, feel free to explore this approach on your own if you prefer.

Use the Angular CLI to generate components for the child components like so:

As always, first we'll add our imports. Let's dive right into the class, covering the imports as we go through the code.

This time, we won't set a pageTitle immediately. We first need to retrieve the event data from the API. We'll also grab the event's id by subscribing to the ActivatedRoute route parameters observable. We'll subscribe to the route's query parameters observable to set the tab. As usual, we'll get our event data from the API service, annotating results with the EventModel type. For this component and its children, we also want to know if the event is has already ended so we'll use an eventPast property to track this with the eventPast() method we added to our UtilsService in the Angular: Create a Utility Service section of Part 3.

Note: Users should not RSVP to events in the past.

In our ngOnInit() method, we'll subscribe to the route params, set the local id property, and execute the method that fetches the event from the API (_getEvent()). Then we'll subscribe to the query params to set the tab. If there is no query parameter present, we'll default to the details tab.

In our _getEvent() method, we'll also set the pageTitle with the title of the retrieved event using a _setPageTitle() method. We'll also check to see if the event is in the past. If an error occurs, we'll set the page title to Event Details.

Finally, we'll unsubscribe from all three subscriptions when the component is destroyed.

Once the API call has been made and an event has been retrieved, we'll show an alert if the event is in the past. We'll then display our tabs in a Bootstrap card component. We'll use the RouterLink directive with [queryParams] to set the tab, and update the active class accordingly.

Below the tab navigation, we'll load the Event Detail or RSVP component conditionally, passing in necessary data: the full [event] for Event Detail and [eventId] and [eventPast] for Rsvp.

Aside: "Private" Events

Some of our events are set to viewPublic: false. If you recall, all this means is that these events don't show up in a public listing. They still appear in the Admin component listing and can also be direct-linked. If you're an admin and you have an event you'd like to share only with specific people, you can access the page through the Admin listing and email invitees the direct link, which might look something like this: /event/590a642ef36d281a3dc29522.

Note: It's important to understand there is no security conferred here. All events are still accessible to any authenticated user, we're just making it slightly more difficult for people to discover certain events without a direct link. If you'd like to implement security to ensure that users need a real invitation code in order to view and/or RSVP to events, that would be a great feature to work through on your own after completing this tutorial series. Keep in mind this must be implemented both on the client and server for proper security.

Add Tab Support to Auth Redirection

Now that we have working tabs, we need to update our AuthService to support redirection with query parameters. In Angular: Access Management, we added support to redirect the user to a previous route after logging in. However, at that time, we did not set this up in a way that supported query parameters.

Let's create a private function called _redirect(). This will assess the authRedirect string stored in local storage and split it into the appropriate Angular path and query parameters, if necessary. Then it will navigate to the route.

This component class only has a few simple responsibilities. It needs to accept the [event] input passed in from the parent using the @Input decorator. This data is one-way bound to the child component and can be used like any locally-declared property. We also need to make methods from UtilsService and AuthService available to the template. We do this by passing them as public to the constructor method.

This child component lives inside the Event component and all it needs to do is show event information. In a Bootstrap list group, we'll display the event dates and times and the location. In order to show the dates/times in a reader-friendly way, we'll use the eventDatesTimes() method from our utility service. The location should also be followed by a link to Google Maps so the user can get directions if needed. This can open in a new tab.

The event description is set with the [innerHTML] DOM property directive so that, after automatic sanitization, it will render safe markup if any is present.

Last, we'll add a link to edit the event if the user is an admin.

Note: Right now, this "Edit" link won't go anywhere since we haven't created the Update Event component or route. We'll add the component later and then the link will function.

We're now finished with our Event Detail tab component! It should look something like this in the browser:

Now that we have our event details, we're ready to implement the logic to manage RSVPs next time.

Summary

In Part 4 of our Real-World Angular Series, we've covered access management with Angular, displaying admin data, and setting up detail pages with tabs. In the next part of the tutorial series, we'll tackle simple animation as well as creating and updating data with a template-driven form.