Angular compiler calculates so called ngContentIndex. You can think of it as a place where transcluded node should be projected.
So, having AppComponent template
<app-first>
<ng-container class="my-class">CHILD</ng-container>
</app-first>
<br>--------<br>
<app-second>
<ng-container class="my-class">CHILD</ng-container>
</app-second>
Angular gives each node its ngContentIndex(take a look at parentheses):
<app-first>(null)
<ng-container class="my-class">CHILD</ng-container>(1)
</app-first>
<br>--------<br>(null)
<app-second>(null)
<ng-container class="my-class">CHILD</ng-container>(0)
</app-second>
It means that first CHILD should go to second ng-content and the second CHILD to the first.
Why did it happen like this?
The asnwer lies in source code of Angular compiler.
By default <ng-content></ng-content> is the same as
<ng-content select="*"></ng-content>
For each template Angular knows all its ngContentSelectors:
FirstComponent ['*', '*']
SecondComponent ['.my-class', '.my-class']
And here is the answer:
const ngContentSelectors = component.directive.template !.ngContentSelectors;
for (let i = 0; i < ngContentSelectors.length; i++) {
const selector = ngContentSelectors[i];
if (selector === '*') {
wildcardNgContentIndex = i;
} else {
matcher.addSelectables(CssSelector.parse(ngContentSelectors[i]), i);
}
}
You can see that Angular treats * specially and updates wildcardNgContentIndex in a loop so that it will be 1. For ['.my-class', '.my-class'] array it adds selectors to matches.
Finally, Angular uses this method:
findNgContentIndex(selector: CssSelector): number | null {
const ngContentIndices: number[] = [];
this._ngContentIndexMatcher.match(selector, (selector, ngContentIndex) => {
ngContentIndices.push(ngContentIndex);
});
ngContentIndices.sort();
if (this._wildcardNgContentIndex != null) {
ngContentIndices.push(this._wildcardNgContentIndex);
}
return ngContentIndices.length > 0 ? ngContentIndices[0] : null;
}
which will return saved 1 value for projectable node in FirstComponent.