25

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?

13
  • Possible dup of stackoverflow.com/questions/44796671/… Commented Jul 5, 2017 at 7:22
  • In my case the use of a lazy loading module is mandatory Commented Jul 5, 2017 at 7:30
  • @smartmouse there is a line r.url.lastIndexOf("/")); illegal. Commented Jul 13, 2017 at 14:21
  • And there are double paths for UsersManagementRoutingModule Commented Jul 13, 2017 at 15:09
  • 1
    two path property in first level of routes config of UsersManagementRoutingModule Commented Jul 13, 2017 at 16:47

5 Answers 5

11

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.

Sign up to request clarification or add additional context in comments.

7 Comments

I think you've posted a wrong Plunker link... Anyway as I figure out if a direct child have component in the router path it can see the data property but it eats it and then inhibits it for its children. That's the problem.
i am sorry, fix it now.
@e-cloud Can you elaborate on "Approach 1 Cons: But you can use some route lifecycle hooks to define them for target ancestor routes"
quote from angular.io/guide/router "If you need to see what events are happening during the navigation lifecycle, there is the enableTracing option as part of the router's default configuration. This outputs each router event that took place during each navigation lifecycle to the browser console. This should only be used for debugging purposes. You set the enableTracing: true option in the object passed as the second argument to the RouterModule.forRoot() method." - so half answered my own comment..
@JGFMK, enableTracing is not what i suggested. Router has lots of events you can listen to/subscribe on. Define query params in those event, that's what i mean.
|
9
+50

You can traverse the parent routes by using activatedRoute.pathFromRoot, and get the data from the nearest ancestor that has it available. Something like this seems to work:

  ngOnInit(): void {
    let idx = this.activatedRoute.pathFromRoot.length - 1;
    while(this.sharedData == null && idx > 0){
      this.sub = this.activatedRoute.pathFromRoot[idx].data.subscribe(subData => {

        if(subData.id)
          this.sharedData = subData;

        console.log('shared data', this.sharedData);
      });
      idx--;
    }
  }

https://plnkr.co/edit/K5Rb1ZvTvSQNM6tOLeFp

6 Comments

Yes, I like your solution. Anyway I can't figure out why I have to loop on all ActivatedRoute instead of directly access to currect activated route to get the data (or params) I need...
traversing upward is kind of dirty and there are memory leaks without unsubscribe action.
Yes, it is kind of dirty, but this is the currect state in Angular. About memory leak you can just add unsubscribe method in the loop.
Could new paramsInheritanceStrategy added in Angular 5.2 fix this problem?
|
1
@NgModule({
    imports: [
        RouterModule.forChild([
            {
                path: 'users',
                loadChildren: 'app/documents/documents.module#DocumentsModule',
                resolve : {
                     data: DataResolver
                }
            {
        ])
    ],
    exports: [
        RouterModule
    ]
})

export class UsersManagementRoutingModule {
}


@Injectable()
export class DataResolverimplements Resolve<Data> {
  resolve() {
    return this.serice.getData();
  }
}

2 Comments

Did this work for anyone? I'm also trying this approach but the default component of lazy loaded module is never called.
Yes this works angular.io/api/router/Resolve. You need to make sure to provide your service in the parent module (providedIn: root for example in newer versions) and make sure your Observable is invoked (angular.io/guide/router#resolve-pre-fetching-component-data I was using map instead of mergeMap in my pipe causing my observable not to be invoked)
0

Why don't you use service to share the data between the modules or any components. You can have this service imported at the main module provider and hence can be used in all other modules (lazy loading module) and share the data.

The service can have functions to set and get values and hence you can make use of these functions in services to get your required data

2 Comments

If I use shared service it works only if you don't go to lazy loading module by direct url. If you go to a page that belongs to lazy loading module not passing by previous module that send data to shared service, you have no way to get the data!
Yeah, I understood your problem now.
0

If you end up here nowadays, check out the router configuration option paramsInheritanceStrategy. It allows you to refine 'how the router merges parameters, data, and resolved data from parent to child routes.' I had some resolved data that wasn't being sent to lazy loaded modules, and setting the parameter to 'always' solved this problem for me.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.