Angular 20's createComponent function will support two-way binding in addition to input and output bindings and directives.
The feature is in 20.0.0-next.3; therefore, it can be tested after updating the Angular dependencies to the next version.
ng update @angular/cli --next
ng update @angular/core --next
This demo will show how to invoke the createComponent
method to create a dynamic component displaying the information of Star Wars characters. The dynamic component has a model input that will be bound to the signal of the App
component. When users click the dynamic component's button, the click
event updates the model input and synchronizes the value of the signal. Finally, the App component displays the latest value of the model input.
Define the AppStarWarCharacterComponent Compnent
@Component({
selector: 'app-star-war-character',
template: `
template: `
@if(person(); as person) {
<p><span>Id:</span> {{ person.id }} </p>
@if (isSith()) {
<p>A Sith, he is evil.</p>
}
<p><span>Name: </span>{{ person.name }}</p>
<p><span>Height: </span>{{ person.height }}</p>
<button (click)="voted()">Voted for {{ person.name }}</button>
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppStarWarCharacterComponent {
id = input(1);
isSith = input(false);
lastClicked = model('')
voted() {
const name = this.person()?.name || 'NA';
const currentTime = new Date(Date.now()).toISOString();
this.lastClicked.set(`You voted for ${name} at ${currentTime}`);
}
getPersonFn = getPerson();
person = toSignal(toObservable(this.id)
.pipe(switchMap((id) => this.getPersonFn(id)))
);
}
The AppStarWarCharacterComponent
component is responsible for displaying the details of a Star Wars character. The component has a lastClicked
model input that will be bound to the parent component's signal. It also has a voted
method that sets the lastClicked model input after the button click.
Create AppStarWarCharacterComponent Dynamically with the CreateComponent Function
<div class="container">
<ng-container #vcr />
</div>
<ng-container [ngTemplateOutlet]="starwars"
[ngTemplateOutletContext]="{ items: jediFighters(), isSith: false }" />
<ng-template let-items="items" let-isSith="isSith" #starwars>
<select [ngModel]="items[0].id" #id="ngModel">
@for (item of items; track item.id) {
<option [ngValue]="item.id">{{ item.name }}</option>
}
</select>
@let text = isSith ? 'Add a Sith' : 'Add a Jedi';
<button (click)="addAJedi(id.value, isSith)">{{ text }}</button>
</ng-template>
The inline template has a <ng-container #vcr>
with vcr
template variable. It also displays a dropdown list containing the names of the Jedi fighters.
vcr = viewChild.required('vcr', { read: ViewContainerRef });
In the component class, the viewChild
function queries the VierContainerRef
and assigns to the vcr field.
When users select a Jedi fighter and click the "Add a Jedi" button, the addAJedi
method is triggered. The method imports the AppStarWarCharacterComponent
class to create the component dynamically.
lastClicked = signal('');
async addAJedi(id: number, isSith = false) {
const { AppStarWarCharacterComponent } = await import ('./star-war/star-war-character.component');
const componentRef = this.vcr()
.createComponent(AppStarWarCharacterComponent,
{
bindings: [
inputBinding('id', () => id),
inputBinding('isSith', () => isSith),
twoWayBinding('lastClicked', this.lastClicked),
]
}
);
this.componentRefs.push(componentRef);
}
The ViewContainerRef
class has a createComponent
method; therefore, it is executed to create an instance of AppStarWarCharacterComponent
and set the bindings. The twoWayBinding
function binds the lastClicked
model input to the lastClicked
signal.
<div>
<span>Two-way Bindings: {{ lastClicked() }}</span>
</div>
The template displays the lastClicked
signal, which is the timestamp of the most recent button click.
ngOnDestroy(): void {
if (this.componentRefs) {
for (const ref of this.componentRefs) {
ref.destroy();
}
}
}
The createComponent
method returns a ComponentRef
inserted to the ViewContainerRef
. The ComponentRef
is tracked by the componentRefs
array. When the App
component is destroyed, the ngOnDestroy
lifecycle hook method also runs to destroy the ComponentRef
to release the memory to avoid memory leaks.
Creating dynamic components is easier in v20 when it supports two-way bindings, input and output bindings, and directives.
References:
- PR: https://github.com/angular/angular/pull/60342
- Github Repo: https://github.com/railsstudent/angular20-new-features/blob/main/projects/two-way-binding-dynamic-component-demo/src/app/app.component.ts
- Github Page: https://railsstudent.github.io/angular20-new-features/two-way-binding-dynamic-component-demo/
Top comments (0)