5

I'm trying to load different 'parent' components and inject route content into these different parent components by targeting ng-content in each parent component. Essentially, each parent component handles the navigation and other boilerplate stuff based on device widths (small/mobile, medium/tablet, and large/desktop).

The content generated for each route in my application needs to get transcluded (injected) into the parent components. The content for each route targets specific injection points using ng-content.

I'm able to swap out the parent components using <ng-container *ngComponentOutlet="ResponsiveComponent;"></ng-container>. The problem is my route-generated content is not getting injected into the targeted ng-content locations in each parent component.

Here's the code for swapping out parent components. AppPageComponent extends ResponsiveComponent which monitors device width based on media queries.

@Component({
    selector: 'app-page',
    template: `
        <ng-container *ngComponentOutlet="ResponsivePageComponent;"></ng-container>     
    `,  
    styleUrls: [ './page.component.scss'],
    providers: []
})
export class AppPageComponent extends ResponsiveComponent implements OnInit, OnDestroy
{
    ResponsivePageComponent;

    constructor(
        injector: Injector,
        zone: NgZone)
    {
        super(zone);    
    }

    ngOnInit()
    {       
        this.IsSmallEnvironmentStream
            .subscribe
            (
                (isSmall: boolean) =>
                { 
                    if (isSmall)
                    { 
                        this.ResponsivePageComponent= AppPageSmallComponent;
                    }                   
                },
                (err) => { },
                () => {}
            );

        this.IsMediumEnvironmentStream
            .subscribe
            (
                (isMedium: boolean) =>
                { 
                    if (isMedium)
                    { 
                        this.ResponsivePageComponent = AppPageMediumComponent;
                    }                   
                },
                (err) => { },
                () => {}
            );

        this.IsLargeEnvironmentStream
            .subscribe
            (
                (isLarge: boolean) =>
                { 
                    if (isLarge)
                    { 
                        this.ResponsivePageComponent = AppPageLargeComponent;
                    }                   
                },
                (err) => { },
                () => {}
            );      
    }
}

Angular's documentation for NgComponentOutlet shows you can target optional list of projectable nodes to insert into the content (ng-content) placeholders. I followed the examples from Angular's documentation and was able to inject text nodes into the different ng-content placeholders in my parent components and it works for my small/medium/large parent components.

How do I get the content from my routes to inject into these different placeholders? It seems like I'm really close to getting this to work. I can inject in essence static content into the palceholders, but I can not figure out how to inject the content generated by routes into the placeholders.

UPDATE - 07/25/2017

I created this plunker demonstrating this scenario (https://plnkr.co/edit/hOwRX1Ml6gX0S8em6nd5?p=preview).

In the plunker, page-wrapper component uses ngComponentOutlet to load small/medium/large component depending upon device width. page-wrapper component is injecting static content into the placeholders in each small/medium/large component instead of injecting route content from page 1.

My objective is to have a page wrapper component to handle responsive needs like different navigation elements for small/medium/large responsive designs.

So, how can 'static' content be injected into dynamically loaded component's ng-content placeholders yet route generated content is not being injected into ng-content placeholders?

Thanks for your help.

Tom Schreck
  • 5,177
  • 12
  • 68
  • 122
  • `router-outlet` is not defined in the HTML. – Aravind Jul 23 '17 at 05:38
  • Hello! Can you say what exactly doesn't work? Route 3 doesn't transclude ng-content by selector? Would be great if you reduced the code in your plunker to minimal reproduction – yurzui Jul 25 '17 at 05:21
  • @yurzui - thanks for the feedback. I've clarified the plunker. The plunker just has 2 routes now. Route 1 uses a 'responsive' parent component and its content does not get injected into parent's ng-content placeholders. Route 2 uses a 'non-responsive' parent component and its content does get transcluded into its parent's ng-content placeholders. – Tom Schreck Jul 25 '17 at 13:22
  • Thanks for the clarification. You want to inject transcluded content in dynamic component instead `staticContent` It will only work if `ng-content` is located on direct child component. We need some workaround here – yurzui Jul 25 '17 at 13:27

2 Answers2

9

1) You can define ng-content places in your PageWrapperComponent and transclude them to dynamic component

page-wrapper.component.html

<ng-container *ngComponentOutlet="ResponsiveComponent; 
                                content: [[el1],[el2]]"></ng-container>
<div #el1>
  <ng-content></ng-content>
</div>
<div #el2>
  <ng-content select="[named-injection-point]"></ng-content>
</div>

Plunker Example

2) You can also use template reference variables(or custom directive) in parent component and @ContentChild in PageWrapperComponent to get transcluding sections.

page-one.component.html

<page-wrapper>
    <section #mainPoint>
        <h2>Page One</h2>
    </section>
    <section #namedPoint>
        <h3>Page One Content:</h3>
        <p>Lorem ipsum dolor.</p>
    </section>
</page-wrapper>

page-wrapper.component.ts

@ContentChild('mainPoint') mainPoint;
@ContentChild('namedPoint') namedPoint;

page-wrapper.component.html

<ng-container *ngComponentOutlet="ResponsiveComponent; 
        content: [[mainPoint.nativeElement],[namedPoint.nativeElement]]"></ng-container>

Plunker Example

yurzui
  • 205,937
  • 32
  • 433
  • 399
  • The ngComponentOutlet directive uses an array of @ContentChild nativeElements to pass content to dynamically loaded components. I have to put the array in a specific order to get the injected content into the correct ng-content. Is there a way to guarantee what item in array gets injected into the correct ng-content in the dynamically loaded component? – Tom Schreck Jul 25 '17 at 15:08
  • @Tom Schreck You have to keep order. https://stackoverflow.com/questions/44284026/creating-a-angular2-component-with-ng-content-dynamically/44289996#44289996 You should pass array in the same order as appropriate `ng-content` is appeared in template. Angular doesn't use `select` attribute in this case, only index – yurzui Jul 25 '17 at 15:10
  • I tried to pass value using content, but i am not able to read content value in another component. I am using angular 5 – Venkateswaran R Jan 29 '18 at 12:56
  • @VenkateswaranR Can you setup a small repro? – yurzui Jan 29 '18 at 12:57
  • I followed your 2nd example of plunker. But i didn't get content value – Venkateswaran R Jan 29 '18 at 12:59
  • @VenkateswaranR Is it working as intended in my plunker? – yurzui Jan 29 '18 at 13:00
  • I have created https://plnkr.co/edit/0t3VgzBEIRAUogYASjGO?p=preview, kindly check. Not able to get content value – Venkateswaran R Jan 29 '18 at 13:25
  • @Venkateswaran Your example is completely different from my. I use wrapper but you pass content directly. https://plnkr.co/edit/lHFWI9KEHXPaJT4YKDkD?p=preview – yurzui Jan 29 '18 at 13:41
  • @yurzui I understood, but my requirement is to pass content value from one component to another component using ng-container – Venkateswaran R Jan 30 '18 at 04:01
  • @yurzui, is it possible to pass values from one component to another component using ngComponentOutlet in ng-container – Venkateswaran R Jan 30 '18 at 08:36
  • @VenkateswaranR This should help you https://stackoverflow.com/questions/48188151/angular-5-dynamic-component-creation-with-constructor/48188791#48188791 – yurzui Jan 30 '18 at 09:08
0

Is this what you're expecting? I've only tested the medium sized component BTW...

https://plnkr.co/edit/JgCQb4JVSHnHCueamerV?p=preview

I think it's because you're trying to use ng-content in a component that is not directly called from the component that has the content you want to inject. All I did was add an ng-content on the PageWrapperComponent, with it's own selector, then passed that on to the next component, with the selector that one is expecting.

PageWrapperComponent...

<ng-content select="[injected-content]" named-injection-point></ng-content>

Then in the PageOneComponent used the new selector the Wrapper is expecting

<section injected-content>
  <h3>Page One Content:</h3>
  <p>Lorem ipsum ...</p>
</section>       

I can now see Page One Content below Jimmy and Fred when I click on the page 1 link.

Matt Sugden
  • 844
  • 6
  • 12
  • Thank you for your response. I need to replace Jimmy and Fred with content from route, not place route data below Jimmy & Fred. I was just using Jimmy and Fred as an example of content getting placed inside dynamic components. I could not figure out how to get route data loaded instead. – Tom Schreck Jul 25 '17 at 14:29