/ngx-material-navigation

Builds material navigation elements like a combined navbar and sidenav or footers based on the supplied configuration data. Will automatically move elements from the navbar to the sidenav at the provided breakpoints.

Primary LanguageTypeScriptMIT LicenseMIT

NgxMaterialNavigation

Builds material navigation elements like a combined navbar and sidenav or footers based on the supplied configuration data. Will automatically move elements from the navbar to the sidenav at the provided breakpoints.

Supports nested menus and brings functionality which allow you to extract angular routes from an Configuration if you want to define the routes directly there.

Table of Contents

Requirements

This package relies on the angular material library to render its components.

Basic Usage

Define your configuration

import { NavbarRow, NavRoute } from 'ngx-material-navigation';

/**
 * The configuration consists of multiple NavbarRows.
 * This is needed if you want to have a toolbar with multiple rows.
 * 
 * In most cases you probably only define one NavbarRow in this array.
 * 
 * You can also provide a generic type for any angular routes in the elements.
 * This is helpful if you want to have type safety when using the routes data property.
 * Or when you want to enforce that a title/path etc. is required on every route.
*/
export const navbarRows: NavbarRow<NavRoute>[] = [
    {
        elements: [
            {
                type: NavElementTypes.IMAGE_WITH_INTERNAL_LINK,
                url: 'https://www.my-great-website.de/my-great-picture.png',
                link: {
                    route: 'home',
                },
                collapse: 'never'
            },
            {
                type: NavElementTypes.TITLE_WITH_INTERNAL_LINK,
                title: 'Showcase Project',
                link: {
                    route: 'home'
                },
                collapse: 'sm'
            },
            {
                type: NavElementTypes.INTERNAL_LINK,
                name: 'Home',
                route: { // This can also just be a string.
                    title: 'Home',
                    path: 'home',
                    loadChildren: () => import('./components/home/home.module').then(m => m.HomeModule)
                },
                collapse: 'md'
            },
            {
                type: NavElementTypes.MENU,
                name: 'Menu',
                elements: [
                    {
                        type: NavElementTypes.INTERNAL_LINK,
                        name: 'menu item #1',
                        route: 'menu-item/1'
                    },
                    {
                        type: NavElementTypes.INTERNAL_LINK,
                        name: 'menu item #2',
                        route: 'menu-item/2'
                    }
                ],
                position: 'center',
                collapse: 'md'
            },
            {
                type: NavElementTypes.BUTTON,
                name: 'Reload the page',
                action: () => location.reload(),
                position: 'right',
                collapse: 'sm'
            }
        ]
    }
];

Extract the angular routes

⚠️ Optional:
You only need this if you want to define your angular routes inside the NavbarRows.

// Define any additional routes that are not defined in the NavbarRows.
const extraRoute: NavRoute = {
    title: '404 Page not found',
    path: '**',
    component: NgxMatNavigationNotFoundComponent,
    data: { // this is type safe
        pageNotFoundConfig: {
            homeRoute: '/home'
        }
    }
};
// Extract the angular routes from the given configuration. This can be used in the app.routing.module.ts
export const routes: NavRoute[] = NavUtilities.getAngularRoutes<NavRoute>(navbarRows, [extraRoute]);

Use the elements

In app.component.html:

<ngx-mat-navigation-navbar [minHeight]="70" [minSidenavWidth]="'30%'" [minHeightOtherElements]="70" [navbarRows]="navbarRows">
    <!-- The content of your app needs to be put inside the navbar -->
    <router-outlet></router-outlet>
</ngx-mat-navigation-navbar>

<app-footer [minHeight]="70"></app-footer>

Please note that all of your content needs to be put inside the navbar. This is needed to put it inside the mat-sidenav-content.

The minHeight and minHeightOtherElements is needed internally to set the min-height of the content accordingly.

Actions and conditions

In your navigation data you can define actions that are executed when a button is clicked. You can also define a condition function which defines whether or not the navigation element should be visible:

...
{
    type: 'button',
    name: 'Reload the page',
    action: () => location.reload(),
    condition: conditionWithInjection,
    position: 'right',
    collapse: 'sm'
}
...

When the above button is clicked the current window gets reloaded. For the isUserLoggedIn-function this is a bit more tricky, as normally injections only work inside an angular context. We work around this by using the EnvironmentInjector, however THIS REQUIRES YOU TO DEFINE ANY CONDITION AS AN EXTRA FUNCTION (at least if you want to use injections).

// This will work:
...
{
    type: 'button',
    name: 'Reload the page',
    action: () => location.reload(),
    condition: conditionWithInjection,
    position: 'right',
    collapse: 'sm'
}
...

function conditionWithInjection(): boolean {
    const router = inject(Router);
    console.log(router.url);
    return true;
}
// This wont work:
...
{
    type: 'button',
    name: 'Reload the page',
    action: () => inject(Router).url.length > 10,
    condition: conditionWithInjection,
    position: 'right',
    collapse: 'sm'
}
...

Dynamic Anchors

This library supports adding anchors (different sections the user can scroll to) dynamically to the navbar.

First, define anchor elements in your routes configuration:

{
    type: NavElementTypes.INTERNAL_LINK,
    name: 'Scrolling',
    route: {
        title: 'Scrolling',
        path: 'scrolling',
        loadComponent: () => import('./components/scrolling/scrolling.component').then(m => m.ScrollingComponent),
        data: {
            anchors: [
                {
                    name: 'Bottom',
                    fragment: 'bottom',
                    icon: 'fas fa-home'
                },
                {
                    name: 'Middle',
                    fragment: 'middle'
                }
            ]
        }
    },
    collapse: 'md'
}

On the scrolling component there need to be elements with the ids "bottom" and "middle".

Then configure your routing options:

const routerOptions: ExtraOptions = {
    scrollPositionRestoration: 'enabled',
    onSameUrlNavigation: 'reload',
    anchorScrolling: 'enabled'
};

@NgModule({
    imports: [RouterModule.forRoot(routes, routerOptions)],
    exports: [RouterModule]
})
export class AppRoutingModule { }

If you want to enable smooth scrolling you can add html { scroll-behavior: smooth; } to your styles.scss.

⚠️ No anchor toolbar is shown:

The NgxMatNavigationService handles getting the anchor toolbar from the route configuration by subscribing to router.events.

Usually the navbar is used in your app.component.html, which means the service gets initialized and everything should work out of the box. But it could be possible that route navigation happens without the service being initialized.

In that case you need to add the following to your app.module.ts provider array:

{
   provide: APP_INITIALIZER,
   useFactory: () => {
       return () => {};
   },
   deps: [NgxMatNavigationService],
   multi: true
}

Custom Components

You have the option to easily add custom components if the provided components don't fully cover your requirements.

Create the custom component

The only restriction for the component is that it needs to extends NgxMatNavigationBaseNavElementComponent:

import { NavElementTypes, NgxMatNavigationBaseNavElementComponent } from 'ngx-material-navigation';

@Component({
    selector: 'app-custom',
    templateUrl: './custom.component.html',
    styleUrls: ['./custom.component.scss']
})
export class CustomComponent extends NgxMatNavigationBaseNavElementComponent<NavElementTypes.CUSTOM> {}

Use it in your routes

import { NavElementTypes } from 'ngx-material-navigation';

...
{
    type: NavElementTypes.CUSTOM,
    component: CustomComponent
},
...

NavRoute

/**
 * An opinionated model of the Angular Route.
 * This makes the title and path attributes required.
 * It also adds the option to use a generic for the route data.
 */
export interface NavRoute<DataType extends Data = DefaultNavRouteDataType> extends Route {
    title: string | Type<Resolve<string>> | ResolveFn<string>,
    path: string,
    data?: DataType
}

/**
 * The default data type for a nav route.
 * Will be used if no generic is provided.
 */
export interface DefaultNavRouteDataType extends Data {
    /**
     * The configuration for the 404 page not found component.
     */
    pageNotFoundConfig?: PageNotFoundConfig
}

NgxMatNavigationNotFoundComponent

This is a simple 404-Error Page that can be used in your routing:

import { PageNotFoundConfig, NavRoute, NgxMatNavigationNotFoundComponent } from 'ngx-material-navigation';

const pageNotFoundConfig: PageNotFoundConfig = {
    title?: string,
    message?: string,
    buttonLabel?: string,
    homeRoute?: string
}

const pageNotFoundRoute: NavRoute = {
    title: '404 Page not found',
    path: '**',
    component: NgxMatNavigationNotFoundComponent,
    data: {
        // this is optional for overriding the default values. When using NavRoute this is also type safe.
        pageNotFoundConfig: pageNotFoundConfig
    }
};