Introduction

A few months ago I wrote a post about using the PnP JS Core to create a mock backend as part of an Angular 2.0 SharePoint app written in TypeScript.  At the time, I was just doing some proof of concept work and feeling my way through things.  Now two months later I thought I would give an update on what that POC has become and what else I’ve learned since then.

Regarding our technology stack, not much has changed except we are using Angular 4.0 now and that transition was seamless.  To be honest, I didn’t even realize we were on 4.0 and not 2.0 until I went looking.  The little skunkworks stuff using PnP Core JS and our own Custom HttpClientImpl has turned out even better than I expected.  We can now run the application on localhost against mocked SharePoint lists, Users, and Permissions/SharePoint Groups and then deploy to SharePoint with no code or configuration changes.  Running locally, we use local storage to preserve all data changes until we want to start over.  My very non-scientific guess is that this has made us 25%-50% faster at developing our user interface and REST calls.

What I would like to share with you now is some information on how we’ve used routing in our application.  If you are new to routing, I would check out the Angular routing tutorial.  I honestly don’t know where we would be without it.  In our application we are using multiple levels of router outlets, lazy loading routing modules and using route guards, all of which I learned through the Angular tutorials.  Fundamentally, routing is just about mapping a URL path to components in your application.  Angular makes it so easy to plug and play all these components so you can assemble them and orchestrate a complex UI with ease.

App Structure and Router Outlets

Before getting into the routing code, I wanted to give you an overview of the components and router outlets used as part of a user navigating to a route in our application.  Below is an example of a “snapshot” of what is in play when a user is looking at a list of business requirements for a business requirements document.  This is the “deepest” a user might navigate based on the way the application is currently configured.

You are probably looking at the picture above and wondering why I tried to create what looks like an Infinity Mirror.  The goal is to show how all these Angular components and router outlets work together to produce a responsive, flexible and usable single page application.   An application where you navigate around at a global level among “top level” components or at a “document level” among document level components.

Notice that there are two router outlets in the above image.  This gives us the ability to swap out progressively more specific pieces of the application when a user moves from one route to another.  Router outlet #1 would deal with my navigation between larger sections of the app.  Router outlet #2 would deal with my navigation within a specific section of the app.  Take the following URL as a hypothetical example of this, note that routes do not have to follow this pattern, but it’s meant to show a parent-child URL relationship that is then supported by our router outlets.

http://localhost:4200/#/{section}/{sub-section}

Routing Information

Consider the URL (http://localhost:4200/#/documents) as a more concrete example for the prefix of a route.  Looking at it you’re probably pretty sure I’m looking at documents, probably a list of them.  At this point, I’m only within router outlet #1.  Now, if I click on one of those list items, maybe I end up at (http://localhost:4200/#/documents/BR/1) which is some detail or list screen for a specific document.  At this point, the details were emitted by router outlet #2.  Now I can navigate all around the specific documents to URLs like these:

The cool thing is that the information obtained and code that was executed as part of router outlet #1 isn’t exercised again.  Not until I make one of those big jumps to another document or another completely different section will that code need to be invoked.  In addition to some of the implicit magic, the routes and router outlets give us we do utilize services to manage states and allow for sibling type components to communicate and share information.

Other Cool Routing “Stuff”

I was going to dive a little deeper into our implementation for these, but then I figured there are tons of examples and I wanted to keep this brief, so I instead thought I would give two brief code snippets and provide some background of what is used and why.

]
export const routes: Routes = [
    { path: 'view/:documentType/:docId', component: DocumentPrintComponent },
    {
        path: '',
        component: AppComponent,
        children: [
            { path: 'releases', pathMatch: 'prefix', loadChildren: 'app/releases/release.module#ReleaseModule' },
            { path: 'projects', pathMatch: 'prefix', loadChildren: 'app/projects/project.module#ProjectModule' },
            { path: 'documents', pathMatch: 'prefix', loadChildren: 'app/documents/document.module#DocumentModule' },
            { path: 'roles', component: UserRolesComponent },
            { path: 'noaccess', component: ErrorNoAccessComponent },
            { path: '', component: HomeComponent },
            { path: '**', component: ErrorNotFoundComponent }
        ]
    }
]

Lazily Loaded Routes

I stumbled across this in the Angular routing tutorial but found it useful in keeping some “separation of concerns” around our routes.  As you can see above, we look for the prefixes releases, projects, and documents in the main routes for the application and then lazy load their children defined in other files.  Defining the child routes in a file that’s in the same folder structure as the components that are going to be invoked as part of those child routes just seems to help when working on specific areas of the application.  I know there are also startup benefits since lazily loaded routes aren’t loaded at startup.  In our case, we probably have dozens of routes, so that’s probably not a big deal but if you had hundreds or thousands of routes, I could see this helping performance.

]
export const routes: Routes = [
    {
        path: 'new', component: DocNewComponent, canDeactivate: [CanDeactivateGuard], canActivate: [ProjectCreatorAuthGuard]
    },
    {
        path: 'BR/:docId',
        component: DocumentComponent,
        canActivate: [DocumentStatusAuthGuard],
        children: [
            { path: '', redirectTo: 'overview' },
            { path: 'overview', component: OverviewComponent },
            { path: 'requirements', component: RequirementsComponent, canDeactivate: [CanDeactivateGuard] },
            { path: 'groups', component: GroupsComponent, canDeactivate: [CanDeactivateGuard]  },
            { path: 'attachments', component: AttachmentsComponent, canDeactivate: [CanDeactivateGuard] }
        ],
    },
    {
        path: 'AA/:docId',
        component: DocumentComponent,
        canActivate: [DocumentStatusAuthGuard],
        children: [
            { path: '', redirectTo: 'overview' },
            { path: 'overview', component: OverviewComponent },
            { path: 'systems', component: SystemsComponent, canDeactivate: [CanDeactivateGuard] },
            { path: 'work', component: WorkComponent, canDeactivate: [CanDeactivateGuard] },
            { path: 'attachments', component: AttachmentsComponent, canDeactivate: [CanDeactivateGuard] },
        ],
    },
    /... other routes excluded for brevity .../
    { path: '', component: DocumentListComponent }
]

Route Guards

We have also started using route guards significantly in our application, and I can’t imagine any genuinely robust application not using them.  At least not without a lot of ugly and messy code in your components with router redirects.

  • CanDeactivateGuard – As the name implies, we use this route to determine if the user can deactivate or leave a route. We have lots of forms in the application that users will perform edits on and then may try to leave the page without saving.  This routing feature combined with a dialog service allows the “Are you sure” dialog that used to be such a horrible pain in plain old JavaScript days.
  • CanActivate – On the other side of the equation there is the option to check whether a route can be activated. The most obvious checks are authentication checks but since we are inside of SharePoint much of that has been handled, and so we can deal with more specific application role based permission checks and any business centric logic necessary to know if the user can access an URL.  You can never trust that a user is going to access your application based on the prescribed flow you’ve defined, always assume they know every URL that is possible and will try to access it. If you notice in the above route definition, the guard is defined at a parent route and therefore guards all child routes.

Hopefully, you can take something away from all the routing information I’ve shared with you.  As much as I’ve learned along the way I do feel like I’m continually learning more and improving and tweaking the patterns. If you have any thoughts on what you would do differently or how you’ve used Angular routing in your application, feel free to share them by commenting on this post below.

Share and Enjoy !

Related Content: