Skip to main content
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: Matthew Orstad
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: Matthew Orstad ( @rocketmidwest )

Mixing Specific And Non-Specific ng-content Transclusion In Angular 2.4.1

By on

When I first started digging into Angular 2, I remember thinking that the ng-content directive was a bit limited in how it would "project" or "transclude" content. But, the other day, I was looking through some of the Google Material Design code and saw ng-content being used in a way that I had not seen before - I was surprised to see both specific and non-specific ng-content projection working side-by-side. Clearly I'm behind in my learning of Angular 2; so I wanted to do a little experimenting.

Run this demo in my JavaScript Demos project on GitHub.

I could totally be wrong about this, but when I first experimented with ng-content, I could swear that it basically had two modes: project everthing; or, project a particular Angular 2 Directive type. It's hard to say - it was never really documented and I didn't do that much digging. That said, ng-content seems to be much more flexible these days. Jecelyn Yeen has a great article on the various ways "select" can be used in transclusion.

For this demo, however, I'm not so much interested in how the "select" input of the ng-content tag works; really, I'm more interested in how specific and non-specific ng-content tags coexist in the same Angular 2 component. To experiment with this, I created a simple App component that consumes a Layout component. The App component provides an articulated header and footer portion of the layout content; but, it also provides non-contained "body" content:

// Import the core angular services.
import { Component } from "@angular/core";

@Component({
	moduleId: module.id,
	selector: "my-app",
	styleUrls: [ "./app.component.css" ],
	template:
	`
		<my-layout>

			<ng-container role="header">
				This is my header content.
			</ng-container>

			<p>
				This is some body content.
			</p>

			<p>
				And some more content, which is so good.
			</p>

			<ng-container role="footer">
				This is my footer content.
			</ng-container>

		</my-layout>
	`
})
export class AppComponent {
	// ...
}

As you can see, the App component is providing the Layout component with a header, a footer, and then "everything else."

In the Layout component, we can then project / transclude these three "areas of content" into the Layout view using ng-content. For the header and footer, we can use the "select" attribute to target the ng-container directives. And, for the "body" content, we can use a non-specific ng-content directive:

// Import the core angular services.
import { Component } from "@angular/core";

@Component({
	moduleId: module.id,
	selector: "my-layout",
	styleUrls: [ "./layout.component.css" ],
	template:
	`
		<div class="header-panel">

			<!-- Notice the duplicate usage - just to explore how it works. -->
			<ng-content select="ng-container[role=header]"></ng-content>
			<ng-content select="ng-container[role=header]"></ng-content>

		</div>

		<div class="body-panel">

			<!--
				Here, we project / transclude any content that is not being transcluded
				by more specific ng-content select methods. Notice that the wild-card
				ng-content does not have to be the last ng-content in the template.
			-->
			<ng-content></ng-content>

		</div>

		<div class="footer-panel">

			<!-- Notice the duplicate usage - just to explore how it works. -->
			<ng-content select="ng-container[role=footer]"></ng-content>
			<ng-content select="ng-container[role=footer]"></ng-content>

		</div>
	`
})
export class LayoutComponent {
	// ...
}

Here, you can see that I'm using specific ng-content[select] and non-specific ng-content directives side-by-side, in the same component template. And, for the sake of experimentation, I'm even duplicating the specific ng-content directives just to see how they behave. When we run this Angular 2 application, we get the following output:

Mixing specific ng-content[select] and non-specific ng-content in the same component view template in Angular 2.

As you can see, everything "just worked!" The specific ng-content[select] directives transcluded the header and footer ng-containers; and, the non-specific ng-content directive transcluded all of the content that wasn't going to be transcluded by a more-specific ng-content directive. So cool!

You can't necessarily see this from the code, but the order of the content, as defined in the App Component, doesn't actually matter. Right now, the order of content in the App component and the transclusion of said content into the Layout component is the same. But, if you watch the video, you can see that the order doesn't matter - the App component content can be shifted around and the Layout component still renders exactly as we would hope.

And of course, we can see that the duplicate ng-content tags don't explode - they just fail to transclude any additional content.

The behavior and functionality of the ng-content directive has come a long way in Angular 2. With flexible selectors and the ability to have specific and non-specific ng-content directives working side-by-side, we can create easy-to-consume components that allow for more natural content definitions. Very cool stuff!

Want to use code from this post? Check out the license.

Reader Comments

15,674 Comments

@Mark,

Yes, correct. In my post, I linked to Jecelyn Yeen's article where she covers many way in which the [select] attribute can be used in ng-content. In my particular demo, I just wanted to look at what happens when you use [select] and non-[select] versions of ng-content in the same component. It was not something I had ever seen before.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel