Angular router: how to pass data to lazy loading module?

smartmouse picture smartmouse · Jul 5, 2017 · Viewed 15.6k times · Source

I have the following routing paths for a module of my Angular app:

@NgModule({
    imports: [
        RouterModule.forChild([
            {
                path: 'documents',
                data: { myObject: MyConstants.OPTION_ONE },
                children: [
                    {
                        path: ':ID_DOC',
                        children: [
                            { path: 'edit', component: EditDocumentComponent },
                            { path: '', component: DocumentDetailsComponent },
                        ]
                    },
                    { path: 'add', component: AddDocumentComponent },
                    { path: '', component: DocumentsListComponent }
                ]
            }
        ])
    ],
    exports: [
        RouterModule
    ]
})

export class DocumentsManagementRoutingModule {
}

As you can see I use data property to pass some data to every path in "documents", so I can get it from any of the Components declared in the routing paths:

For example here is how I get data in DocumentDetailsComponent:

export class DocumentDetailsComponent implements OnDestroy {
    private obsData: Subscription;
    private option: any;

    constructor(private route: ActivatedRoute) {
        this.obsData = this.route.data.subscribe(data => {
            this.option = data['myObject'];
        });
    }

    ngOnDestroy(): void {
        this.obsData.unsubscribe();
    }
}

Now I changed the routing structure of the entire app and I call the above module from other modules, in lazy loading, using loadChildren attribute. And I pass data in the same way:

@NgModule({
    imports: [
        RouterModule.forChild([
            {
                path: 'users',
                loadChildren: 'app/documents/documents.module#DocumentsModule',
                data: { myObject: MyConstants.OPTION_ONE }},
            {
                path: ':ID_USER',
                children: [
                    { path: 'edit', component: EditUserComponent },
                    { path: '', component: UserDetailsComponent },
                ]
            },
            { path: 'add', component: AddUserComponent },
            { path: '', component: UserListComponent }
        ])
    ],
    exports: [
        RouterModule
    ]
})

export class UsersManagementRoutingModule {
}

And I did the same for all the other modules that calls DocumentsManagementRoutingModule, changing myObject property to MyConstants.OPTION_TWO, MyConstants.OPTION_THREE and so on, according to my needs.

Then, since I don't know the module and its related path that calls my lazy loading module, how to get data property from the caller module?

Answer

e-cloud picture e-cloud · Jul 19, 2017

I think I may understand what's the question is asking about.


Problem

Here is the routing graph of the plunker @smartmouse provided.

- home
  - shop
  - contact
    - map
  - about

There is a data object configured on the home route config. The direct child routes can access this data object. The problem is, the non-direct child map wants to access the data object, but apparently, it can't.

And we can know @smartmouse wants push that even further -- nested non-direct child routes can access data from ancestor.

Why it can't?

See the plunker I've modified. https://plnkr.co/edit/FA7mGmspUXHhU8YvgDpT

You can see that I define a extra child component DefaultComponent for contact. It shows that it is the direct and default child of route contact, which can be interpreted as it is also the direct child of superior route home. So, it can access the data object.

Solution

Look at the plunker provided by me again.

NOTE: The Angular packages I'm using is v4.3.0

Approach #1 -- query params

I define some query params in home.component.ts for contact route. You can see how to use it.

Pros:

  • you can access query params across any level of routes.
  • works well with lazyload modules

Cons:

  • you can't define query params in route config.

    But you can use some route lifecycle hooks to define them for target ancestor routes

Approach #2 -- service

Define a data share service on top level of the application playing as Singleton Pattern. Also, use some util to prevent it from being initiated twice. Then you can put some data in it, and retrieve it anywhere.

Pros:

  • global visible via DI;
  • works well with lazyload modules

Cons:

  • you can't update data in route config.
  • you have to define data somewhere else. If you can't control it source, it may be a problem.

Approach #3 -- route resolve

Someone may find out I've also defined resolve sample code in the plunker. It's to show it has the same behavior like data in route config, except it actually is for dynamic data retrieving.

https://vsavkin.com/angular-router-understanding-router-state-7b5b95a12eab#5400
The data property is used for passing a fixed object to an activated route. It does not change throughout the lifetime of the application. The resolve property is used for dynamic data.

Take @smartmouse's plunkr as example.

We have data defined in 'home' route config. We can define a data resolver in ContactModule for contact's default route, and proxy the data passed from superior route. Then all its direct child routes can access the resolved data object.

Pros:

  • works well with any level of nested routes
  • works well with lazyload modules

Cons:

  • You may have to declare that resolve property in every parent route whose child routes wants to access that data. It's kind of dirty work.

There is no perfect and universal solution, for now, with common usage of Angular. Someone should weigh the pros and cons and choose the proper one.