0

Context

I'm working on a project using Angular 2, TypeScript, @ngrx/store and SystemJS. I've built a module loader so I can build my application by assembling modules. What I'll show you first is the main module, which provide the root of my app (nav, router, subnav and the content zone).

Before to go further, I'll explain what I have, what I want and why. Check this screenshot :

graphics

You can see different pages (Account, Channel, Playlist...). Those pages are listed in the mainNav. Each pages provies a sub menu (named sidenav in this case) and a content. It's a pretty common design I know :).

Let's go deeper. I have a main angular component named MainComponent. This is my root component.

main.component.ts

@Component({
    selector: 'app-view',
    directives: [ Devtools, MainNavComponent, SidenavLayoutComponent ],
    template: `
        <ngrx-devtools *ngIf="prodEnv === false"></ngrx-devtools>
        <cmp-main-nav></cmp-main-nav>
        <cmp-main-layout [prodEnv]="prodEnv"></cmp-main-layout>
    `
})
@RouteConfig([
    { path: '/home',    name: 'Home',   component: HomeComponent,   useAsDefault: true },
    { path: '/medias',  name: 'Medias', component: MediasComponent },
])
export class MainComponent {
    private prodEnv: boolean = initialState.prodEnv;

    constructor (private _router:Router, private _store:Store<AppStore>) {
        _router.subscribe(url => _store.dispatch(changeUrl(url)));
    }
}

Let's take a look at MainNavComponent which obviously... Render the mainNav part.

mainNav.component.ts

@Component({
    selector: 'cmp-main-nav',
    directives: [ ROUTER_DIRECTIVES, MainLinkComponent ],
    styleUrls: ['src/modules/component@main/src/styles/mainNav.css'],
    template: `
        <nav class="main-nav">
            <ul>
                <cmp-main-link [routeName]="['Home']" [icon]="'face'">Account</cmp-main-link>
                <cmp-main-link [routeName]="['Home']" [icon]="'face'">Channel</cmp-main-link>
                <cmp-main-link [routeName]="['Home']" [icon]="'face'">Playlist</cmp-main-link>
                <cmp-main-link [routeName]="['Home']" [icon]="'face'">Medias</cmp-main-link>
                <cmp-main-link [routeName]="['Home']" [icon]="'face'">Security</cmp-main-link>
            </ul>                    
        </nav>
    `
})
export class MainNavComponent { }

Finally, sidenavLayout.component.ts wrap inside an angular material component the sidenav and uses router-outlet to display the content provides by the host component (see main.component.ts :

@Component({
    selector: 'cmp-main-layout',
    directives: [ /* ... */ ],
    styleUrls: [ /* ... */ ],
    template: `
        <md-sidenav-layout [style.right]="prodEnv ? '' : '30%'" class="sidenav-layout-wrapper">            
            <md-sidenav class="sidenav" mode="side" [opened]="sidenavIsOpened">
                <ul>
                    <li><cmp-sidenav-link [icon]="'home'" [routeName]="['Home']">Home</cmp-sidenav-link></li>
                    <li><cmp-sidenav-link [icon]="'input'" [routeName]="['Medias']">Médias</cmp-sidenav-link></li>
                </ul>

                <button md-button (click)="closeSidenav()">CLOSE</button>
            </md-sidenav>

            <button md-button (click)="openSidenav()">OPEN</button>
            <router-outlet></router-outlet>
        </md-sidenav-layout>
    `
})
export class SidenavLayoutComponent {
    /* ... */
}

And what do you want ?

I've previously said that I've built a module loader. I want my pages (account, channel...) to be modules. So one "module" provide me :

  • a sidenav content (yes, pages have different sidebars)
  • a content (it'll be put inside the green content zone)

At the moment, the sidebar is the same across all my app. I'm looking for a way to be able to update sidebar depending on the page.

What have you tried ?

I've tried to remove the SidenavLayoutComponent. A page (Account for example) provides me an AccountComponent. This component contains the sidenav and the content. Then, I can include this AccountComponent via <router-outlet> in my main.component.ts.

But nope : let says I want to put some generic things inside my sidenav. I need to put this generic content on each modules (pages). I really want to have a 'root' sidenav. Then modules put content inside. (There is other reasons but nevermind).

What do you want ?

Take a look at this code. It have to be inside the SidenavLayoutComponent class:

updateSidenavContent() { 
    // @todo : retrieve the template of the host component (from the _router) and insert it into the template.
    // Maybe using this :
    // this._router._outlet._componentRef.__zone_symbol__value.instance
}

constructor (private _router:Router) {
    // only if the host component have changed :)
    this._router.subscribe(route => this.updateSidenavContent()); 
}

I want to get the formatted string representing a component's template from an instance (or the class but this is weird).

All this reading to ask this question ?!

I really doubt about retrieving the template like this to be a solution. So I've presented my case to make you able to find another solution, maybe better.

I'm relatively new to Angular 2 so maybe I've missed something important.

I don't want to present you all my environment and this "module loader" can be weird. I think all we need to know is here.

Thanks !

Deadpool
  • 7,811
  • 9
  • 44
  • 88
Clément Flodrops
  • 1,114
  • 1
  • 13
  • 29
  • I know [this answer](http://stackoverflow.com/a/34856313/5542588). But I don't know if it's possible to add `directives` dynamically. I don't think so. **EDIT:** Maybe I don't need directive if I use `_router`. I'm trying this. – Clément Flodrops Apr 13 '16 at 10:33

1 Answers1

1

If you want to add components dynamically use DynamicComponentLoader.

You can use DI to configure what components to insert if that is helpful in your example..

 bootstrap(AppComponent, [provide('navComponent', {useValue: SomeNavComponent})]);

and then request it like

class AppComponent {
  constructor(@Inject('navComponent') private navComponent, 
       private dcl:DynamicComponentLoader, private elementRef:ElementRef) {}

  ngOnInit() {
    this.dcl.load...
  }
}

Hint: [OpaqueToken]](https://angular.io/docs/ts/latest/api/core/OpaqueToken-class.html) should be preferred over plain strings for DI keys.

You could also load routes dynamically with different routes (and different components) depending on configuration values.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • Thanks a lot. In my case it'll be tricky to use DI. Check this : `this._router._outlet._componentRef.__zone_symbol__value.hostComponent`. Normally, `_outlet` is private. So do you know a way to get the host component without invoking private properties ? – Clément Flodrops Apr 13 '16 at 11:38