0

I am working on a large Angular-based project with many (let's say, > 200) lazy-loaded modules. Each module has the same UI, so each module can be represented by the same component. (The difference between modules is that they offer different dynamically instantiated sub-components. Thus, what I really want to achieve is having the same UI in each module, but supplying a different set of available sub-components depending on the route - you can imagine that as e.g. Type<MySubComponent> instances that are going to be fed to component outlets.)

Each module has an individual route, that is, the route determines which module we are in.

How do I define this in Angular?

  • As far as I've understood, routes in Angular always point to a component (which is to be displayed when navigating to said route). Not to some underlying service or other class that provides data, but to the UI itself.
  • Thus, a straightforward OOP way would be to define my one module-UI-component as a base class, then derive a module-specific component in each module that "fills in some blanks" (i.e. implements some abstract methods that supply the data).
    The problem here is: Apparently, the @Component decorator is not inherited even if my subclass does not specify one itself. As a result, I'd have to duplicate the exact same @Component decorator with the same template and style URLs for each of the 200+ modules, which I consider a severe violation of the DRY principle and thus bad design. Without doing so, I'm getting error NG6001, as can be reproduced by this code:
@Component({
    template: '<div></div>'
})
abstract class BaseComponent {
    abstract get moduleSpecificStuff(): string;
}

export class ConcreteComponent extends BaseComponent {
    get moduleSpecificStuff(): string {
        return 'This is the concrete module!';
    }
}

Thus, how can I route to the same UI everywhere without having lots of duplicate code and getting some different data/contents supplied under each route?


As a further caveat, there must not be any global list of all routes. I've got a list of special modules, each of which will know the routes to its sub-modules and lazy-load them as required. That is, for each of those routes, the same component must be registered, but different data must be supplied.

F-H
  • 663
  • 1
  • 10
  • 21

2 Answers2

0

EDIT: Did not actually answer the question, did some testing and came up with this.

You could try loading a component by it's name (string). If you can manage to get the list of component names to the BasePageComponent (via api, local storage, route parameters, etc), you should be able to get the Type of the components and then load them.

Used this thread

test.component.ts

@ComponentLookup('TestComponent') // Add this decorator to components

Couple of exports:

export const ComponentLookupRegistry: Map<string, any> = new Map();

export const ComponentLookup = (key: string): any => {
    return (cls: any) => {
        ComponentLookupRegistry.set(key, cls);
    };
};

Code to load a component by string:

// This will get the TestComponent class
const classRef = ComponentLookupRegistry.get('TestComponent'); 


// Not important stuff
const viewContainerRef = this.adHost.viewContainerRef;
const componentRef = viewContainerRef.createComponent(classRef);
Chris Gray
  • 86
  • 4
  • The sub-components should not be addressed with routes. I'm just looking at using one router outlet, I think, for the module UI itself. The sub-components are rendered using [ngComponentOutlet](https://angular.io/api/common/NgComponentOutlet), and that part already works flawlessly - as long as we can find a way to supply the module-specific list of `Type` to the `BasePageComponent`. – F-H Jun 27 '22 at 11:57
  • So what you're looking for is to load a different Service inside of `BasePageComponent` based on what route was used? – Chris Gray Jun 27 '22 at 12:01
  • Or I guess more specifically, the route tells the `BasePageComponent` what Components it should load. – Chris Gray Jun 27 '22 at 12:08
  • It's more complicated than that - `BasePageComponent` actually contains a dynamic layouting system in which users can insert one to x sub-components - but essentially, the route tells `BasePageComponent` which set of sub-components is available, yes. – F-H Jun 27 '22 at 12:09
  • Alright, I changed my answer to how I would do it. It's hard to tell if it resolves your problem because it sounds like your codebase is a bit unique. – Chris Gray Jun 27 '22 at 13:04
  • Thank you. I must admit I'm a bit confused now. Which component is being loaded by its name now? The one that I called "sub-component"? If so, that problem is solved; `ngComponentOutlet` works perfectly fine. But "If you can manage to get the list of component names to the BasePageComponent" - no, this I can't, that's precisely the problem I'm facing. I do not know how to get the list of available components for the currently chosen route/module. – F-H Jun 27 '22 at 13:24
  • Where is that list of components coming from? – Chris Gray Jun 27 '22 at 14:02
  • It's hard-coded in each module. For instance, *module 1* might contain the list ( `SubComponent1`, `SubComponent2`, `SubComponent3` ), all three of which are defined in *module 1*. And *module 2* might contain the list ( `SubComponent4`, `SubComponent5`, `SubComponent6`, `SubComponent2`), where the last component is imported from *module 1* and the others are declared within *module 2*. So, each module brings its own sub-components, and usually offers these and possibly some imported sub-components from other modules, where appropriate. – F-H Jun 27 '22 at 14:08
  • Added to my answer. A possible way to pass on the names of the sub-components – Chris Gray Jun 27 '22 at 14:29
  • Thank you, the query parameters look like an interesting approach. As you say, though, crafting the anchor tag might be tricky. Users might want to navigate to the route by writing the route (e.g. `/test`) into their address bar, and they'd never come across any link that leads to the route. But assuming that can be solved somehow, and each module can somehow define route parameters for its default route `''`, it appears that those route parameters are converted to strings (to appear in a regular URL). That would force me to identify sub-components by name (not compiler-checked!) rather ... – F-H Jun 27 '22 at 15:07
  • ... than by class reference (i.e. something like a `Type` value). That's certainly a drawback, as it removes an important bit of type-safety/compile-time checking. The module could add the sub-component mappings it knows about to the lookup upon module initialization. Then I'd just have to check whether the query parameters would be fit for all the module-specific data (in reality, we're not just talking about ~3 sub-components, but more like 50 - 100 sub-components per module, plus a whole bunch of other module-specific things) and somehow work without an anchor tag. – F-H Jun 27 '22 at 15:14
  • Wait, doesn't supporting this internal functionality via query parameters mean that users might spoof the query parameters and add any set of sub-components they like? – F-H Jun 27 '22 at 18:11
  • Yeah you're right, they could. Scratch that. What does your Routes look like? Is every url going to the same BasePageComponent? – Chris Gray Jun 27 '22 at 18:53
0

It seems that the Route.data attribute is the right place to store this kind of data. This is where arbitrary route-specific data can be supplied.

The component (even if it is the same component for many routes) can then inject ActivatedRoute and retrieve the data either as an observable from data or once, e.g. upon initialization, via the snapshot property.

F-H
  • 663
  • 1
  • 10
  • 21