DEV Community

Connie Leung
Connie Leung

Posted on

7

Dynamic component supports bindings and directives

Angular 20's createComponent function will support bindings and directives.

The feature is in 20.0.0-next.1; therefore, it can be tested after updating the Angular dependencies to the next version.

ng update @angular/cli --next
ng update @angular/core --next
Enter fullscreen mode Exit fullscreen mode

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 inputs, an output, and a host directive that needs to be set during creation.

Define the AppStarWarCharacterComponent Compnent

@Component({
 selector: 'app-star-war-character',
 template: `
   <div class="border">
     @if(person(); as person) {
       <p><span>Id:</span> {{ 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)="alertStarWars.emit(person.name)">Alert parent</button>
     } @else {
       <p>No info</p>
     }
   </div>
 `,
 changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppStarWarCharacterComponent { 
 id = input(1);
 isSith = input(false);

 alertStarWars = output<string>();

 getPersonFn = getPerson();

 person = toSignal(toObservable(this.id)
   .pipe(
     switchMap((id) => this.getPersonFn(id)),
   )
 );
}
Enter fullscreen mode Exit fullscreen mode

The AppStarWarCharacterComponent component displays the details of a Star Wars character. The component has two signal inputs, id and isSith, which hold the value of the ID and whether or not the character is a Sith fighter. It also has an alertStarWars output that notifies the parent component to show an alert box. Finally, the component is dynamically bound to a host directive, AppLabelColorDirective, that changes the text color of the span elements.

Implement of the Host Directive

.blue span {
   color: blue;
}

.rebeccapurple span {
   color: rebeccapurple;
}

.red span {
   color: red;
}
Enter fullscreen mode Exit fullscreen mode

The global stylesheet has some classes that override the text color of the span elements.

@Directive({
   selector: '[appLabelColorDirective]',
   host: {
       '[class]': 'spanClass()'
   }
})
export class AppLabelColorDirective {
   spanClass = input('red');
}
Enter fullscreen mode Exit fullscreen mode

The AppLabelColorDirective is a host directive that sets the host class. The default value of the spanClass input is 'red', which changes the text color of the span elements to red. When the input value is 'blue', the span elements display blue text color. Finally, the texts are in rebeccapurple color when the input is 'rebeccapurple'.

Create AppStarWarCharacterComponent Dynamically

@Component({
 selector: 'app-root',
 imports: [FormsModule, NgTemplateOutlet],
 template: `
   <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>
 `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppComponent {
  vcr = viewChild.required('vcr', { read: ViewContainerRef });
  componentRefs = [] as ComponentRef<any>[];

  jediId = signal(1);

  jediFighters = signal([
    { id: 1, name: 'Luke' },
    { id: 10, name: 'Obi Wan Kenobe' },
  ]);

  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),
         outputBinding<string>('alertStarWars', (name) => alert(`${name} alerts the parent component`)),
       ],
       directives: [
         {
           type: AppLabelColorDirective,
           bindings: [
             inputBinding('spanClass', () => isSith ? 'red' : 'rebeccapurple')
           ]
         }
       ]
     }
   );
   this.componentRefs.push(componentRef);
 }
}
Enter fullscreen mode Exit fullscreen mode

The inline template has a <ng-container #vcr > with a vcr template variable. It also displays a drop-down list containing names of the Jedi fighters.

vcr = viewChild.required('vcr', { read: ViewContainerRef });
Enter fullscreen mode Exit fullscreen mode

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.

const componentRef = this.vcr()
.createComponent(AppStarWarCharacterComponent,
     {
       bindings: [
         inputBinding('id', () => id),
         inputBinding('isSith', () => isSith),
         outputBinding<string>('alertStarWars', (name) => alert(`${name} alerts the parent component`)),
       ],
       directives: [
         {
           type: AppLabelColorDirective,
           bindings: [
             inputBinding('spanClass', () => isSith ? 'red' : 'rebeccapurple')
           ]
         }
       ]
     }
);
Enter fullscreen mode Exit fullscreen mode

The ViewContainerRef class has a createComponent method; therefore, it is executed to create an instance of AppStarWarCharacterComponent and set the bindings and host directive. The idcparameter is bound to the id signal input and isSith parameter is bound to the isSith signal input. The callback function listens to the alertStarWars output to open an alert box displaying the character name. The directive array applies the AppLabelColorDirective directive to the component. When the isSith parameter is true, the spanClass signal input receives 'red'. When the parameter is false, the spanClass signal input receives 'rebeccaPurple'.

The createComponent method returns a ComponentRef that is 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 leak.

Creating a dynamic component is easier in v20 when it supports custom events by output binding and changing DOM behavior through host directive.

References:

Heroku

Deploy with ease. Manage efficiently. Scale faster.

Leave the infrastructure headaches to us, while you focus on pushing boundaries, realizing your vision, and making a lasting impression on your users.

Get Started

Top comments (0)

Cloudinary image

Video API: manage, encode, and optimize for any device, channel or network condition. Deliver branded video experiences in minutes and get deep engagement insights.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay