I would like to set the body of <ng-content> while instantiating a component dynamically using ComponentFactoryResolver.
I see that I can get access to input & output using ComponentRef, but not a way to set <ng-content>.
Please note <ng-content> I'm planning on setting can contain simple text/can span dynamically created components
#Component({
selector: 'app-component-to-project',
template: `<ng-content></ng-content>`
})
export class ComponentToProject implements AfterContentInit {
ngAfterContentInit() {
// We will do something important with content here
}
}
#Directive({
selector: 'appProjectionMarker'
})
export class ProjectionMarkerDirective implements OnInit {
constructor(private viewContainerRef: ViewContainerRef, private componentFactoryResolver: ComponentFactoryResolver) {
}
ngOnInit() {
const componentFactory: ComponentFactory<ComponentToProject> = this.componentFactoryResolver.resolveComponentFactory(ComponentToProject);
const componentRef: ComponentRef<ComponentToProject> = this.viewContainerRef.createComponent(componentFactory);
// Question: How to set content before the child's afterContentInit is invoked
}
}
#Component({
selector: 'appTestComponent',
template: `<div appProjectionMarker></div>`
})
export class TestComponent {}
There is the projectableNodes parameter for the vcRef.createComponent method
createComponent<C>(componentFactory: ComponentFactory<C>, index?: number, injector?: Injector, projectableNodes?: any[][], ngModule?: NgModuleRef<any>): ComponentRef<C>;
You can use it to dynamically inject one component into another.
Let's say we have the following component
#Component({
selector: 'card',
template: `
<div class="card__top">
<h2>Creating a angular2 component with ng-content dynamically</h2>
</div>
<div class="card__body">
<ng-content></ng-content>
</div>
<div class="card__bottom">
<ng-content></ng-content>
</div>
`
})
export class CardComponent {}
We want to create it dynamically and insert some controls to its ng-content locations. It could be done like follows:
const bodyFactory = this.cfr.resolveComponentFactory(CardBodyComponent);
const footerFactory = this.cfr.resolveComponentFactory(CardFooterComponent);
let bodyRef = this.vcRef.createComponent(bodyFactory);
let footerRef = this.vcRef.createComponent(footerFactory);
const cardFactory = this.cfr.resolveComponentFactory(CardComponent);
const cardRef = this.vcRef.createComponent(
cardFactory,
0,
undefined,
[
[bodyRef.location.nativeElement],
[footerRef.location.nativeElement]
]
);
Plunker Example
See also
Why is projectableNodes an any[][]?
Pawel Kozlowski - Reactive parenting with Angular 2 - NG-BE 2016
Related
currently I trying to project a third component in a child component which is projected inside ngFor loop (inside child), but in parent whenever I change or set some property in the projected content using index of query list (ViewChildren('#thirdComponent')) in parent all the child's projected content shows same change. Is there any proper way of doing this.
Is it due to duplicating of select property binding at the place of content projection in child component.Child's projection is done inside a accordion with one or many panels opened at a time.
#Component({
selector: "my-app",
template: `
<child-comp #child>
<ng-container selected>
<some-other-comp #someOtherComp></some-other-comp>
</ng-container>
</child-comp>
`,
styleUrls: ["./app.component.css"]
})
export class AppComponent implements AfterViewInit {
h = 0;
i = 1;
j = 2;
k = 3;
#ViewChildren("someOtherComp") otherCompList: QueryList<SomeOtherComponent>;
ngAfterViewInit(): void {
this.otherCompList.toArray()[this.h].prop = this.h;
// below will result in undefined due to QueryList of size 1
// this.otherCompList.toArray()[this.i].prop = this.i;
// this.otherCompList.toArray()[this.j].prop = this.j;
// this.otherCompList.toArray()[this.k].prop = this.k;
}
}
#Component({
selector: "child-comp",
template: `
<div *ngFor="let value of [1, 2, 3]; let i = index">
<!-- if ngIf is removed than only the last projection is diplayed -->
<div *ngIf="i === 0">
<ng-content select="[selected]"> </ng-content>
</div>
</div>
`,
styleUrls: ["./app.component.css"]
})
export class ChildComponent {}
#Component({
selector: "some-other-comp",
template: `
<p>{{ prop }}</p>
`,
styleUrls: ["./app.component.css"]
})
export class SomeOtherComponent {
prop: any;
}
Stackblitz
Utilizing *ngTemplateOutlet and let-variables
We can pass along a template into our child-component, and utilize the #Input() decorator in conjunction with *ngTemplateOutlet to directly access the property from the HTML template in the parent.
Example
First, I've defined an array in my parent component which I want to use as the basis for my loop in my outer-child component.
Parent Component
#Component({
selector: 'parent',
templateUrl: 'parent.component.html',
styleUrls: ['parent.component.scss']
})
export class ParentComponent implements OnInit {
dataItems: { title: string, description: string }[] = [{
title: 'First Element',
description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eveniet, nihil!'
}...] // remaining items truncated for brevity.
constructor() {
}
ngOnInit(): void {
}
}
This parent component then has a child component, which takes an input of the entire list of items
<child [items]="dataItems"></child>
Child-Component (fist level)
#Component({
selector: 'child',
templateUrl: 'child.component.html',
styleUrls: ['child.component.scss']
})
export class ChildComponent implements OnInit {
#Input() items!: any[];
constructor() {
}
ngOnInit(): void {
}
}
<ng-container *ngFor="let childItem of items">
<projected [item]="childItem">
<ng-template let-item>
<h4>{{item.title}}</h4>
<p>{{item.description}}</p>
</ng-template>
</projected>
</ng-container>
Projected component (sub-child)
#Component({
selector: 'projected',
templateUrl: 'projected.component.html',
styleUrls: ['projected.component.scss']
})
export class ProjectedComponent implements OnInit {
#Input() item: any;
#ContentChild(TemplateRef) templateOutlet!: TemplateRef<any>
constructor() {
}
ngOnInit(): void {
}
}
<ng-container *ngTemplateOutlet="templateOutlet; context: {$implicit: item}"></ng-container>
<ng-content></ng-content>
How does it work
The Parent Component isn't strictly necessary in this relationship, as we aren't projecting content directly from the parent into the ProjectedComponent, I simply chose to define a list of items here to keep a hierarchy similar to your question.
The Child Component
The child component does two things:
Defines a *ngFor loop to loop thru some collection of elements.
Defines a template for how these elements should be utilized in the ProjectedComponent's template.
In the ProjectedComponent we utilize the #ContentChild decorator to select the TemplateRef which we expect to be given via <ng-content>
This template is then put into a container using the *ngTemplateOutlet which also allows us to create a data-binding context to a local variable.
the context: {$implicit: item} tells Angular that any let-* variable defined on the template without any explicit binding should bind to the item property in our component.
Thus, we are able to reference this property in the template at the parent-component level.
Edit
Technically, the context binding is not necessary if you want to define the template directly inside of the child component, as you have a direct reference to the *ngFor template, however it becomes necessary if you want to lift the template out to the ParentComponent level to make the solution more reusable.
You are correct the reason for the bug (changing just the last element) is because when rendered you have multiple elements with the same select value.
A possible solution is to use template reference to pass the desired child component from the top level to the place where you want it to be projected.
Here is a working StackBlitz
import {
AfterViewInit,
Component,
Input,
QueryList,
ViewChildren
} from "#angular/core";
#Component({
selector: "my-app",
template: `
<child-comp #child [templateRef]="templateRef"> </child-comp>
<ng-template #templateRef>
<some-other-comp #someOtherComp></some-other-comp>
</ng-template>
`,
styleUrls: ["./app.component.css"]
})
export class AppComponent implements AfterViewInit {
h = 0;
i = 1;
j = 2;
k = 3;
#ViewChildren("someOtherComp") otherCompList: QueryList<SomeOtherComponent>;
ngAfterViewInit(): void {
this.otherCompList.toArray()[this.h].prop = this.h;
this.otherCompList.toArray()[this.i].prop = this.i;
this.otherCompList.toArray()[this.j].prop = this.j;
this.otherCompList.toArray()[this.k].prop = this.k;
}
}
#Component({
selector: "child-comp",
template: `
<div *ngFor="let value of [1, 2, 3, 4]; let i = index">
<!-- if ngIf is removed than only the last projection is diplayed -->
<ng-container *ngTemplateOutlet="templateRef"></ng-container>
</div>
`,
styleUrls: ["./app.component.css"]
})
export class ChildComponent {
#Input() templateRef;
}
#Component({
selector: "some-other-comp",
template: `
<p>{{ prop }}</p>
`,
styleUrls: ["./app.component.css"]
})
export class SomeOtherComponent {
prop: any;
}
I would like to create a ViewRef from markup that is dynamically inserted into a template. Is this possible based on the following code sample?
template.html:
<ng-container *ngTemplateOutlet="dynamic; context: cntx"></ng-container>
<ng-template #dynamic>
<div [innerHTML]="markup"></div>
</ng-template>
Injected markup from API call to bind to div's innerHTML attribute:
<div>
<div id="forViewRef"></div>
</div>
component.ts:
#ContentChild('#forViewRef', { read: ViewContainerRef }): someHndl;
private _nativeElem: any;
constructor(
private sanitizer: DomSanitizer,
private _vcRef: ViewContainerRef,
private _resolver: ComponentFactoryResolver) {
// to ensure template has been created, #dynamic
this._nativeElem = this._vcRef.element.nativeElement;
}
// listen to lifecycle hook
ngAfterContentChecked() {
if (this._nativeElem !== undefined)
// childContent ref is undefined
console.log(this.someHndl);
// markup is in the DOM
console.log(this._nativeElem.querySelectorAll('#forViewRef'));
}
To create component dynamically inside <div id="forViewRef"></div> you can do the following:
Let's say we need to load the following component
#Component({
selector: 'dynamic-comp',
template: `
<h2>Dynamic component</h2>
<button (click)="counter = counter + 1">+</button> {{ counter }}
`
})
export class DynamicComponent {
counter = 1;
}
so first add it to declarations and entryComponents array of your #NgModule
...
declarations: [ ..., DynamicComponent ],
entryComponents: [ DynamicComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
after that create
template.html
<button (click)="createComponent()">Create component</button>
<div id="forViewRef"></div>
and finally write
component.ts
export class AppComponent {
compRef: ComponentRef<DynamicComponent>;
constructor(private injector: Injector,
private resolver: ComponentFactoryResolver,
private appRef: ApplicationRef) {}
createComponent() {
const compFactory = this.resolver.resolveComponentFactory(DynamicComponent);
this.compRef = compFactory.create(this.injector, null, '#forViewRef');
this.appRef.attachView(this.compRef.hostView);
}
ngOnDestroy() {
if(this.compRef) {
this.compRef.destroy();
}
}
}
I use appRef.attachView in order to include dynamic component to change detection cycle
Plunker Example
See also
Display custom tag in google maps infowindow angular2
Angular2 - Component into dynamicaly created element
Add a component dynamically to a child element using a directive
I have created dynamic component instances by selecting pre-existing components. For example,
#Component({
selector: 'dynamic-component',
template: `<div #container><ng-content></ng-content></div>`
})
export class DynamicComponent {
#ViewChild('container', {read: ViewContainerRef}) container: ViewContainerRef;
public addComponent(ngItem: Type<WidgetComponent>,selectedPlugin:Plugin): WidgetComponent {
let factory = this.compFactoryResolver.resolveComponentFactory(ngItem);
const ref = this.container.createComponent(factory);
const newItem: WidgetComponent = ref.instance;
newItem.pluginId = Math.random() + '';
newItem.plugin = selectedPlugin;
this._elements.push(newItem);
return newItem;
}
}
My pre-existed components are ChartWidget and PatientWidget which extended the class WidgetComponent that I wanted to add in the container. For example,
#Component({
selector: 'chart-widget',
templateUrl: 'chart-widget.component.html',
providers: [{provide: WidgetComponent, useExisting: forwardRef(() => ChartWidget) }]
})
export class ChartWidget extends WidgetComponent implements OnInit {
constructor(ngEl: ElementRef, renderer: Renderer) {
super(ngEl, renderer);
}
ngOnInit() {}
close(){
console.log('close');
}
refresh(){
console.log('refresh');
}
...
}
chart-widget.compoment.html (using primeng Panel)
<p-panel [style]="{'margin-bottom':'20px'}">
<p-header>
<div class="ui-helper-clearfix">
<span class="ui-panel-title" style="font-size:14px;display:inline-block;margin-top:2px">Chart Widget</span>
<div class="ui-toolbar-group-right">
<button pButton type="button" icon="fa-window-minimize" (click)="minimize()"</button>
<button pButton type="button" icon="fa-refresh" (click)="refresh()"></button>
<button pButton type="button" icon="fa-expand" (click)="expand()" ></button>
<button pButton type="button" (click)="close()" icon="fa-window-close"></button>
</div>
</div>
</p-header>
some data
</p-panel>
data-widget.compoment.html (same as chart-widget using primeng Panel)
#Component({
selector: 'data-widget',
templateUrl: 'data-widget.component.html',
providers: [{provide: WidgetComponent, useExisting: forwardRef(() =>DataWidget) }]
})
export class DataWidget extends WidgetComponent implements OnInit {
constructor(ngEl: ElementRef, renderer: Renderer) {
super(ngEl, renderer);
}
ngOnInit() {}
close(){
console.log('close');
}
refresh(){
console.log('refresh');
}
...
}
WidgetComponent.ts
#Component({
selector: 'widget',
template: '<ng-content></ng-content>'
})
export class WidgetComponent{
}
Now I added the components by selecting a component from the existed components (e.g. chart-widget and data-widget) in the following way and stored the instances into an array.
#Component({
templateUrl: 'main.component.html',
entryComponents: [ChartWidget, DataWidget],
})
export class MainComponent implements OnInit {
private elements: Array<WidgetComponent>=[];
private WidgetClasses = {
'ChartWidget': ChartWidget,
'DataWidget': DataWidget
}
#ViewChild(DynamicComponent) dynamicComponent: DynamicComponent;
addComponent(): void{
let ref= this.dynamicComponent.addComponent(this.WidgetClasses[this.selectedComponent], this.selectedComponent);
this.elements.push(ref);
this.dynamicComponent.resetContainer();
}
}
Now, I am facing problem to render the components using innerHtml in main.component.html. It render the html but I am not able to use button click event or other event on it. I have also tried to render chart using primeng but its also not working.
main.component.html
<dynamic-component [hidden]="true" ></dynamic-component>
<widget *ngFor="let item of elements">
<div [innerHTML]="item._ngEl.nativeElement.innerHTML | sanitizeHtml">
</div>
</widget>
I have also implemented a sanitizeHtml Pipe but its giving still same result. So, as I understand innerHTML is only showing the html data but I can't use any button event as well as the js chart. I have also tried to show the items like this {{item}} under tag. But it display like a text [object object]. So, could anyone give a solution for it? How can I render the components allowing the button events and js chart? Thanks.
EDIT: See my Plunker here https://plnkr.co/edit/lugU2pPsSBd3XhPHiUP1?p=preview
You can see here, it is possible to add chart or data widget dynamically and I am showing it using innerHTML. So, the button events are not working here. If I coding like {{item}} then it shows [object object] text. You can also see in console the component array data. The main Question is, How can I active the button events on it (e.g. if i click close or refresh button then it will call the related functions)?
I would create structural directive like:
view.directive.ts
import { ViewRef, Directive, Input, ViewContainerRef } from '#angular/core';
#Directive({
selector: '[view]'
})
export class ViewDirective {
constructor(private vcRef: ViewContainerRef) {}
#Input()
set view(view: ViewRef) {
this.vcRef.clear();
this.vcRef.insert(view);
}
ngOnDestroy() {
this.vcRef.clear()
}
}
then
app.component.ts
private elements: Array<{ view: ViewRef, component: WidgetComponent}> = [];
...
addComponent(widget: string ): void{
let component = this.dynamicComponent.addComponent(this.WidgetClasses[widget]);
let view: ViewRef = this.dynamicComponent.container.detach(0);
this.elements.push({view,component});
this.dynamicComponent.resetContainer();
}
and
app.component.html
<widget *ngFor="let item of elements">
<ng-container *view="item.view"></ng-container>
</widget>
So i have just moved view from dynamic component container to desired place.
Plunker Example
On my parent page I have a link here:
<a (click)="showPermissionsRates(5757);">Link</a>
The function sets it:
showPermissionsRates(item) {
this.currentEventPoolId = item;
}
With a child component on the parent page here:
<app-event-pools-permissions-rates [eventPoolId]="currentEventPoolId "></app-event-pools-permissions-rates>
And then in my child component TS file I use:
inputs: ['eventPoolId']
But how do I get that value of '5757' in the child component? Such as using alert?
You should be able to just use #Input() on the child property.
I've put this together showing a VERY basic example, but without more to go on regarding your issues, it's hard to know what you need:
https://plnkr.co/edit/y9clOla1WrPFmhMJoz7o?p=preview
The gist is to use #Input() to mark your inputs in the child component, and map those in the template of the parent.
import {Component} from '#angular/core'
import {BrowserModule} from '#angular/platform-browser'
import { ChildComponent } from 'child.component.ts';
#Component({
selector: 'my-app',
template: `
<div>
<button (click)="changeProperty('ABC 123')">Click Me!</button>
<child-component [childProperty]="parentProperty"></child-component>
</div>
`,
})
export class App {
public parentProperty: string = "parentProp";
public changeProperty(newProperty: string) : void {
this.parentProperty = newProperty;
}
}
Then, in the child:
import {Component, Input} from '#angular/core'
#Component({
selector: 'child-component',
template: `
<div>Hello World: {{ childProperty }}</div>
`,
})
export class ChildComponent {
#Input()
childProperty:string;
constructor() {
this.childProperty = 'childProp'
}
}
I think you are setting value to at input variable in a click event, then you have to listen for it in the child component constructor using ngonchanges
ngOnChanges(changes: SimpleChanges) {
if(changes['eventpoolid'] && changes['eventpoolid'].currentValue) {
// you get updated value here
}
}
How to change the value of a variable or use a method in a parent component from a child component without using input and output
I try something like this but not working.
#Component({
selector: 'child',
template: `
<div>
<h2>{{name}}</h2>
<button (click) = "rename()" > Rename Parent </button>
</div>
`,
})
export class Child {
name:string;
constructor() {
this.name = 'child'
}
rename() {
App.name = 'Rename';
}
}
#Component({
selector: 'my-app',
template: `
<div>
<h2>Hello {{name}}</h2>
</div>
<child> </child>
`,
})
export class App {
name:string;
constructor() {
this.name = 'Angular2'
}
}
example here
plunker example
Input and Output are just made for this. It is, according to the Angular2 Documentation, made for communication between parent and child components.
#Component({
selector: 'my-app',
template: `
<div>
<h2>Hello {{name}}</h2>
</div>
<child [name]="this.name" (nameChanged)="this.name = $event"> </child>
`,
})
export class App {
name:string;
constructor() {
this.name = 'Angular2'
}
}
#Component({
selector: 'child',
template: `
<div>
<h2>{{name}}</h2>
<button (click) = "rename()" > Rename Parent </button>
</div>
`,
})
export class Child {
#Input() name:string;
#Output() nameChanged: EventEmitter<string> = new EventEmitter<string>();
constructor() {
}
rename() {
this.nameChanged.emit('Renamed');
}
}
Alternatively you could inject a service into both parent and child component, which has some values that both parent and child can access and modify. But make sure to add that service to either only the parent component or only the AppModule, otherwise you would get 2 instances of your service.
That is #Output and #Input in Angular 2.
Docs: https://angular.io/docs/ts/latest/cookbook/component-communication.html
Another way that is Observable, Subject you can also inject data from a child component to root component or from a component to another component : See this video: https://www.youtube.com/watch?v=U2qJxfi7370&list=PLFaW_8zE4amNEdKZOJD3P_GeV3Hgva7RD&index=10