I have a working code which injects any component via a service to the HTML:
ModalWindow.ts:
@Component({
selector: \'modal-window\'
Answers:
1) Angular takes ComponentFactory
and create component instance with given element injector and with array of projectable nodes
windowCmpFactory.create(this._injector, [[contentCmpt.location.nativeElement]]);
1.1 Element Injector will be used when angular will resolve dependency
const value = startView.root.injector.get(depDef.token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR);
Here is also simple illustration of dependency resolution algorithm for app without lazy loading. With lazy loading it will look a litte more complicated.
For more details see design doc element injector vs module injector
1.2 Projectable nodes are the node elements, which are "projected"(transcluded) in the ng-content
that we have in the template of our component.
In order to project something our component template has to contain ng-content
node.
@Component({
selector: 'modal-window',
template: `
`
})
export class ModalWindow {
We can use component above in parent component template as follows:
Some other content
So our final result will look like:
So when we're passing projectable nodes to create method
windowCmpFactory.create(this._injector, [[contentCmpt.location.nativeElement]]);
we do the same things as described above.
We'are getting reference (contentCmpt.location
) to the host element of created early contentCmpt
component. This is modal-content
element. And then angular will do all magic to project it in ng-content
place.
In example above i added one div
Some other content <== here
So the real code should looks like:
let div = document.createElement('div');
div.textContent = 'Some other content';
windowCmpFactory.create(this._injector, [[contentCmpt.location.nativeElement, div]]);
In conclusion Why is projectableNodes an any[][]?
2) During the next line
document.querySelector('body').appendChild(windowCmpt.location.nativeElement);
we're getting reference to created in memory modal-window
element. ComponentRef
allows us to do this because it stores reference to the host element in location
getter
export abstract class ComponentRef {
/**
* Location of the Host Element of this Component Instance.
*/
abstract get location(): ElementRef;
and then inseting it in document.body
tag as last child. So we see it on the page.
3) Let's say our ModalContent
has not just static content but will perform some operations for interaction.
@Component({
selector: 'modal-content',
template: `
I'm beeing opened as modal! {{ counter }}
`
})
export class ModalContent {
counter = 1;
}
If we remove
this._appRef.attachView(contentCmpt.hostView);
then our view will not being updated during change detection cycle because we created view via ComponentFactory.create
and our view is not part of any item in change detection tree (unlike creation via ViewContainerRef.createComponent
). Angular opened API for such purposes and we can easily add view to root views
https://github.com/angular/angular/blob/master/packages/core/src/application_ref.ts#L428 and after that our component will be updated during Application.tick
https://github.com/angular/angular/blob/master/packages/core/src/application_ref.ts#L558