Creating an AngularJS Feature Module with exported components & routing

Intro

The AngularJS framework is built upon the promise of modularity and we should always strive to write reusable code when applicable. The easiest way to accomplish this is by creating feature modules to encapsulate your services and components.

If you have been following along with our AngularJS series you will know that we have recently spent some time turning an application we created with Angular CLI into a feature module that we have published on npm. If you have missed them, feel free to read up here: Part One, Part Two.

In the code we discussed in our previous posts we explicitly exported our components & services for re-use (you can see this code here) in this post we will talk about why we did this and how to use this exported code. We will talk about how to extend and overload the exported routes and how to extend the provided components so you can add functionality after you import a feature module.

Scope & Encapsulation of your Feature Module

One of the biggest advantages to creating a feature module is that you can encapsulate your services from the parent application. This gives us a whole namespace to work inside and keeps the parent application free from some random services.

Exporting Routes

Routing is a VERY integral part of an AngularJS application because as your application becomes more complex, so will your routing scheme. There are two ways of getting routes contained in your feature module to the parent app module:

  • Lazy Loading
  • Exporting the feature module routes as another module

Plenty has been written on the subject of lazy loading, so I would like to focus on the second option.

This snippet illustrates our feature routing module:


import { AuthGuard } from './auth.guard';
...
import { UserListComponent } from './user-list/user-list.component';
...
// clang-format off
const userAdminRoutes = [
  ...
  {
    path: 'list',
    canActivate: [
      AuthGuard
    ],
    component: UserListComponent
  },
  ...
];
// clang-format on

export const UserAdminRouting = RouterModule.forChild(userAdminRoutes);

And this is how we export the routing module (in an explicit way) so we can use this in our parent application:


export { UserAdminRouting } from './src/user-admin.routing';

Exporting also allows you to add some routing on top of this exported module as well.

Here’s another snippet showing how we import this into our module and add a route not present in this routing definition:


import { NotFoundComponent } from './not-found/not-found.component'; // clang-format off const appRoutes: Routes = [ { path: '**', component: NotFoundComponent } ]; // clang-format on export const AppRouting = RouterModule.forRoot(appRoutes, { useHash: false });

We can provide routes not originally provided by the package we import by adjusting the order of our imports. This is most easily explained by an example:


...
import { AppRouting } from './app.routing'; import { UserAdminRouting } from '@erdiko/ngx-user-admin'; @NgModule({ declarations: [ ... ], imports: [ ... UserAdminRouting, // Import the package routes AppRouting // import our application routes ], providers: [ ... ], bootstrap: [AppComponent] }) export class AppModule { }

Over-riding your routes with resetConfig

In the example above we show how you can add a route to your module’s exported routes if one does not exist. While this is great, this does not help you overload a route config module that already exists. What if you wanted to replace the “Home” route with a new component, or you wanted to redirect a user when they attempt to go to a route you wish to deprecate? Enter the “resetConfig” method.

This method, which the current AngularJS documentation that leaves something to be desired to say the least, allows you to “reset” your route config and push in new route definitions! If this sounds clunky, that’s because in my opinion it is, but this appears to work well. Let’s take a look at a snippet that improves upon the example above:


// Import Router and Route into your component
import { Router, Route } from '@angular/router'; // Import the component we wish to overload the existing route config import { NotFoundComponent } from './not-found/not-found.component'; ... export class AppComponent implements OnInit { // make sure we inject the router into this component constructor(private router: Router) { } ngOnInit() { /** * Define a new route to overload the existing one for 'list' so * we can show the "Not Found" as an example. * * PLEASE NOTE: cast the object to a Route before attempting to add * this to your list of routes * */ let r: Route = { path: 'list', component: NotFoundComponent }; /** * call the resetConfig method and push in your newly defined route. * */ this.router.resetConfig([r, ...this.router.config]); } }

Here is what the route looks like based on the import routing config from `ngx-user-admin`:

Now here is the route after we use ‘resetConfig’ to overload it to display a component created in our Angular app:

Please note that this example shows a very simple example route defined in a component. It is good practice to move route definitions like this into a separate file for inclusion.

Exporting Components for reuse

It’s always a great practice to be explicit in what you expose to the parent application from your feature module. In most cases, you are creating a feature module to wrap a series of components and related services. The parent application really only needs to have the components exposed for use in its module.

Here is an example where we demonstrate exporting some components from our UserAdmin feature module in `ngx-user-admin`:


@NgModule({
  imports: [
    ...
  ],
  declarations: [
    ...
  ],
  exports: [
     HeaderComponent,
     LoginComponent,
     HomeComponent,
     ...
  ],
  providers: [
    ...
  ]
})

As you see above, we explicitly export our components exactly what we want to expose to the parent application.

Let’s take a look at the snippet below as a practical example of extending a feature module component and route config:

Here we create a new component to extend the existing feature module HomeComponent:


import { AppRouting } from './app.routing'; import { UserAdminRouting } from '@erdiko/ngx-user-admin'; // create a new component we created to extend the ngx-user-admin HomeComponent import { MyhomeComponent } from './myhome/myhome.component'; @NgModule({ declarations: [ AppComponent, NotFoundComponent, // Include our new component MyhomeComponent ], imports: [ // include the feature module UserAdminModule.forRoot(), // include the feature module routing defintion UserAdminRouting, AppRouting ], providers: [ ], // add your new component as an entry point to the application // so the routing can access it bootstrap: [AppComponent, MyhomeComponent] }) export class AppModule { }

From the previous example where a new route was created, we overload the “home” route to use the new component below. Now take a look at this new snippet where we overload the “home” route to use a new component:


ngOnInit() {

/**
* Define a new route to overload the existing one for 'Home' so
* we can use our newly extended component instead of the one from
* the feature module
*/
let r: Route = {
  path: '',
  component: MyhomeComponent
};

this.router.resetConfig([r, ...this.router.config]);

}

And finally here is our new component definition where we extend the feature module provided component:


import { Component, OnInit } from '@angular/core'; import { HomeComponent } from '@erdiko/ngx-user-admin' @Component({ ... }) export class MyhomeComponent extends HomeComponent { }

Here is what the “home” route looks like based on the import routing config from `ngx-user-admin`:

Here is what the “home” route looks like after we extend and overload the routing config:

Conclusion

As you develop Angular applications you will find yourself creating modules to encapsulate feature sets, common utilities and even define basic routes. How you export your code for use is dependent on your application and certainly affects how you can extend these for use in other AngularJS applications.

Next Post

Comments

See how we can help

Lets talk!

Stay up to date on the latest technologies

Join our mailing list, we promise not to spam.