In Angular, a component is a special directive, and its special feature is that it has its own template (html) and style (css). Therefore, using components can make our code highly decoupled, reusable, and easily extensible. The usual components are defined as follows:
demo.component.ts:
demo.component.html:
demo.component.scss:
When we reference the component, the parsed content of the component is rendered:
Suppose there is a need for the component to accept externally projected content, which means that the component ends up presenting more than just what it defines. At this time, the protagonist of this article is invitedng-content。
Simple projection
Let's start with the simplest, add the modified demo.component.html and demo.component.scss to demo.component.html, as follows:
demo.component.html:
demo.component.scss:
For the sake of effect, the background color of the container is deliberately defined as orange.
At this point we can cast content from the outside when referencing the component, and the external content will be displayed in the orange area:
Targeted projection
If there are several at the same time, how will the external content be projected?
Let's take a look at an example first, for the sake of distinction, I added a blue area, the modified demo.component.html and demo.component.scss as follows:
demo.component.html:
demo.component.scss:
To reference the component:
At this point, we will see that the external content is projected to the blue area:
Of course, if you put the orange area code after the blue area code, then the external content will be projected to the orange area:
So from the example above, we can see that if there is also a simple , then the external content will be projected in the last one of the component template.
So knowing this problem, we may wonder, can we project external content in a targeted manner? The answer is obviously yes.
To deal with this, support oneselectAttributes that allow you to project specific content in a specific place. This property supports CSS selectors (tag selector, class selector, attribute selector、... ) to match what you want. If no select attribute is set on ng-content, it will receive the full content, or the content that does not match any other ng-content element.
Looking directly at the example, the modified demo.component.html and demo.component.scss are as follows:
demo.component.html:
demo.component.scss:
As you can see from the above code, the blue area will receive the part of the tag header, the red area will receive the part of the div with class "demo2", the green area will receive the part of the div with the property name "demo3", and the orange area will receive the rest of the external content (start, I am the content of the external embed, end).
To reference the component:
At this point, we will see the external content projected into the specified .
Expand knowledge
ngProjectAs
Now we know that the select property of ng-content allows you to specify that external content is projected into a given .
To correctly project content based on the select attribute, there is a limitation - whether it is a tag header, a div with a class of "demo2", or a div with a property name of "demo3", these tags are all direct subnodes of the component tag.
What would happen if it weren't for being a direct subnode? Let's simply modify the code that references the demo-component component, put the tag header in a div, and modify it as follows:
At this point, we see that the tab header part of the content is no longer projected into the blue area, but into the orange area. The reason is that <ng-content select="header"></ng-content> cannot match the previous tag header, so this part of the content is projected to the <ng-content></ng-content> orange area.
To solve this problem, we have to use the ngProjectAs property, which can be applied to any element. The details are as follows:
By setting the ngProjectAs property, the div where the tag header is located points to select="header", and the part of the tag header is projected to the blue area:
<ng-content>Do not "generate" content Do an experiment To do an experiment, first define a demo-child-component component:
demo-component component to:
Then in demo-component cast demo-child-component:
At this point, in the console we see that the demo-child-component initialization is complete! These words. But when we click the button to switch operations, demo-child-component initialization is complete! It is no longer printed, which means that our demo-child-component component is only instantiated once - never destroyed and recreated.
Why is this happening?
Causes of occurrence
<ng-content> It doesn't "produce" content, it just projects existing content. You can think of it as equivalent to node.appendChild(el) or the $(node).append(el) method in jQuery: with these methods, the node is not cloned, it is simply moved to its new location. Therefore, the lifecycle of projected content will be bound to where it is declared, not displayed in place.
This also explains the previous question from the principle:If there are several at the same time, how will the external content be projected?
This behavior is done for two reasons: consistency and performance. What "desired consistency" means that as a developer, you can guess the behavior of your application based on its code. Let's say I wrote the following code:
Obviously the demo-child-component component will be instantiated once, but now if we use a component from a third-party library:
If a third-party library has control over the lifecycle of a demo-child-component component, I won't have a way of knowing how many times it's been instantiated. The only way to do this is to look at the code of third-party libraries to understand their internal processing logic. The meaning of tying the lifecycle of a component to our application components instead of wrappers is that developers can control that counters are instantiated only once, without knowing the internal code of third-party libraries.
Reasons for performanceis more important. Because ng-content is just a moving element, it can be done at compile time, not at runtime, which greatly reduces the workload of the actual application.
Workaround
In order for the component to control the instantiation of the projected subcomponents, we can do this in two ways: by using <ng-template> elements and ngTemplateOutlets around our content, or by using structure directives with "*" syntax. For simplicity, we'll use Syntax in the example <ng-template> .
demo-component component to:
Then we include demo-child-component in ng-template:
At this point, when we click the button to switch, the console will print outdemo-child-component initialization completed!These words.
|