DEV Community

Cover image for Dynamic Layout parts in Angular
negue
negue

Posted on

Dynamic Layout parts in Angular

The "Problem"

While working on a simple application I wanted to show some content in the title / header bar, but this should be only visible on a specific route.

header.component.html:

<div class="header">
  <ng-container *ngIf="!showSuperSelection">{{ title }}</ng-container>
  <!-- this component is only needed in an "/selected-page" - route -->
  <app-your-big-component *ngIf="showSuperSelection">
  </app-your-big-component>
</div>
Enter fullscreen mode Exit fullscreen mode

1st solution: *ngIf

  • "just" hide (*ngIf="showSuperSelection", exampled in the problem) the content in the header-bar until the specific route was opened
Pro's Con's
easy to use, just an *ngIf its "bad" if this content is a bigger component (byte size), which adds a lot of to the first loading time (every byte counts), which is only "ok" if you are building an intranet-application

2nd solution: Service - ng-template - template outlet

  • You could create a directive to get TemplateRef
  • then using a service to save that reference
  • then in your target view, get this reference and use it like
  <ng-container *ngIf="templateToShow$ | async as templateRef"
                [ngTemplateOutlet]="templateRef">
  </ng-container>
Enter fullscreen mode Exit fullscreen mode

There you have your dynamic templates / layout parts in your app.

Pro's Con's
now this template would be only visible when you need it, and also not adding additional bytes to load you would've to do it for every part you need

My solution: 🚀
npm install @gewd/ng-utils -S

Building up on the 2nd's solution, I kinda wanted to have it a bit more dynamic, so I refacted it to be used on a ID per portal/source.

import { DynamicPortalModule } from '@gewd/ng-utils/dynamic-portal';

@NgModule({
  imports: [
    // ...
    DynamicPortalModule
  ]
})
Enter fullscreen mode Exit fullscreen mode

header.component.html:

<div class="header">
  <dynamic-portal key="headerSelection" class="your-style">
    {{ title }}
  </dynamic-portal>
</div>
Enter fullscreen mode Exit fullscreen mode

selected-page.component.html: which is a lazy-loaded route

<div class="stuff">
  Much stuff, details, you name it :-)
</div>

<ng-template dynamicPortalSource="headerSelection">
   <app-your-big-component></app-your-big-component>
</ng-template>
Enter fullscreen mode Exit fullscreen mode

> Demo < -> Open the Dynamic Portal Component Part

Pro's Con's
portal's / portal-sources can be placed anywhere you'd need to use this package or copy it 😁

GitHub logo negue / gewd

List of utilities / components around Angular

Any ideas / issues / suggestions, write here or open an issue :)

Top comments (13)

Collapse
 
anduser96 profile image
Andrei Gatej

Hi!

I’d follow this approach:

  • create a custom injection token: DYNAMIC_HEADER_INJECTOR
  • register the components that should be inside the ‘header tag’ in a module(AppModule) under the above token
  • create a directive that will deal with rendering logic
<header>
  <ng-container yourDirective><ng-container>
<header>

Then, in your directive, you’d inject the ActivatedRoute and the ViewContainerRef and based on your logic, you’d render one component from the array(meaning that you’d also inject DYNAMIC_HEADER_INJECTOR).

Collapse
 
negue profile image
negue

Hi :)

First.... Congratulations, you are the first comment of my posts 🎉

Back to topic:

I haven't thought about doing this with injection tokens, lets play it out:

  • I'd create the DYNAMIC_HEADER_INJECTOR and provide it with
{
      provide: DYNAMIC_HEADER_INJECTOR,
      useValue: {
        components: [
          {
            path: '/some-sub-page',
            component: MyComponent
          },
          {
            path: '/some-other-page',
            component: MySuperSelectionComponent
          }
        ]
      }
    }
  • and the directive would create those components if the specific route was navigated to..

but wouldn't it be then like in the 1st solution ?

The components would have to be registered and imported to the AppModule which ends-up raising the initial load of your app

Thats why I wanted to have it loaded only where it really needed to be (and not in the AppModule in my case)

or did I misunderstood your idea?

Collapse
 
anduser96 profile image
Andrei Gatej

No, you’re right.

I think there is a way to mitigate this issue, but this implies more work from the developer.

If we don’t want to include the component in the main bundle, we can put it in its module and in the directive, based on some conditions, you can load the module on demand by using import(path/to/module).then() and injecting the compiler.

Thread Thread
 
negue profile image
negue

Yeah like you wrote, another way would be to lazy-load the components of it, there are some helper libaries for this.

I also have one 😁

Its better to have many possible ways to solve those issues, instead of none

Thread Thread
 
anduser96 profile image
Andrei Gatej • Edited

Thanks for sharing!

I’m skeptical about libraries, but I do like to explore them!

Collapse
 
johannesjo profile image
Johannes Millan

That's a cool article. Not sure why it hasn't more likes and stuff. Because of Christmas maybe?

Collapse
 
negue profile image
negue

Hehe thank you!

Yeah I wish I'd know why ^^, maybe not enough tags?

Collapse
 
johannesjo profile image
Johannes Millan • Edited

Maybe the point of time when you posted this? I also found that a little advertisement on Twitter and Reddit helps.

Thread Thread
 
negue profile image
negue

When is the best time to post? I don't really have a clue there ^^

On my next I'll try twitter / reddit

Thread Thread
 
johannesjo profile image
Johannes Millan

As for my own posts I can definitely say that they received more attention on regular work days. Apart from that I am really not sure :)

Collapse
 
jwp profile image
John Peters

Just an idea. Create a header component with just a ngcontent tag.

Now create a Header1 and Header2 component. On each page inject header1 or header2.

You can default the header component to header1 and use flags to hide it when header2 is injected.

You can also use grid template areas for "header" "main" and "footer" areas.

Collapse
 
negue profile image
negue

Hey!

yeah this could be also a valid workaround too. If every (lazy-loaded) page has its own header/content/footer then this would be a valid option.

In my projects I mostly want to have the header only once created in a root (or the App itself) and under that header, comes the <router-outlet></router-outlet>.

But yeah then comes the niche-issue "I want to have a different header just for this one route".

Also this was just one example for those dynamic layouts there probably many more, which I haven't encountered yet, but probably will help me in the long run :D

Collapse
 
jwp profile image
John Peters

Understood, that design requires a smart overideable header component. Something easily done using Event services. But will still require notification when other header is wanted.