Creating a component dynamically, and programmatically in Angular
Tags: tutorial, webdev, angular
Published: 2023-05-15 17:38:56

In Angular 13, the ComponentFactoryResolver became deprecated. Some libraries that depended on it had to be rewritten using the new introduced function createComponent, that is more aligned with the standalone mantra. Let's dig into it and put it to good use.

An example use of a dynamically, programmatically created component in Angular is the infamous [Dialog](https://material.angular.io/components/dialog/overview). Today, let's go over the [documentation](https://angular.io/api/core/createComponent) of the `createComponent` and setup the bare minimum to make use of it later.

Follow me on [StackBlitz](https://stackblitz.com/edit/angular-program-component)

The bare minimum
-----------------

Let's first create the component that needs to be inserted programmatically, that has a simple **yes**, and **no** buttons. The following is the plan:

-   Create the component programmatically from another part of the app. Let's call her **Rose**.
-   Insert into a known HTML tag
-   Insert into the HTML body
-   Pass data to Rose
-   Interact with Rose

Once we cover these, we can aspire for more: we will insert a new component into Rose, let's call it **Peach**.

Rose
-----

Here is rose:

```ts
// src/components/rose.partial
@Component({
  template: `
   <button (click)="ok()">Yes</button>
   <button (click)="no()">Cancel</button>
   `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class RosePartialComponent {
  constructor() {
    //
    console.log('created');
  }

  ok(): void {
    console.log('ok');
  }
  no(): void {
    console.log('no');
  }
}
```

In our app component, the following could be anywhere, but for simplicity let's do that in the app root component.

```ts
// main.ts, or app.component

@Component({
  selector: 'my-app',
  standalone: true,
  imports: [CommonModule],
  template: `
  <button (click)="insertElement()" class="btn">Insert element</button>
	<!-- hosting Rosy -->
  <div id="hostmehere"></div>
  `,
})
export class App {
  insertElement(): void {
    // in here we shall create Rose and add it to hostmehere element
  }
}
// bootstrap app
bootstrapApplication(App);
```

According to the documentation and the example provided, we need to:

1.  Bootstrap an application (we already did that). To get the application reference though we can simply inject it in app component constructor.
2.  Locate a DOM node that would be used as a host. We used `hostmehere`
3.  Get an `EnvironmentInjector` instance from the `ApplicationRef` injected token
4.  We can now create a `ComponentRef` instance. And pass to it the host element and environment injector.
5.  Last step is to register the newly created ref using the `ApplicationRef` instance, to include the component view into change detection cycles. Let's put this off for a minute.

Let's adapt and implement all the above steps

```ts
// main.ts

// inject reference to appRef
constructor(private appRef: ApplicationRef) {}

insertElement(): void {
  // in here we shall create Rose and add it to hostmehere element
  const host = <Element>document.getElementById('hostmehere');
  const componentRef = createComponent(RosePartialComponent, {
    hostElement: host,
    // the environment injector instroduced for standalone
    environmentInjector: this.appRef.injector,
  });
}
```

This should add Rosy to the `hostmehere` element, and it receives clicks as expected.

### [](https://garage.sekrab.com/posts/creating-a-component-dynamically-and-programmatically-in-angular#Append-programmatically)Append programmatically

What if we want to **remove the host element**, and append to a programmatically created HTML element instead? Two ways

#### The simple way

The obvious way is to create a new HTML element and append it to `document.body`

```ts
// create a new element the simple way
// don't forget to use the right platform for SSR
const newHost = document.createElement('somenewelement');
document.body.append(newHost);

const componentRef = createComponent(RosePartialComponent, {
  hostElement: newHost,
  environmentInjector: this.appRef.injector,
});
```

#### The posh way

We can append the resulting **component reference **into the body as well. Console logging the `componentRef` we have access to `hostview` which contains `rootNodes` array, which apparently has our HTML element. So let's append that:

```ts
// main.ts
const componentRef = createComponent(RosePartialComponent, {
  environmentInjector: this.appRef.injector,
});

// append the rootNodes root after creation. That works too.
// don't forget to use the right platform for SSR
document.body.append((<any>componentRef.hostView).rootNodes[0]);
```

Note, I am casting everything to `any` because that's beyond the point. But if you insist, the right type is `EmbeddedViewRef`.

```ts
document.body.append((<EmbeddedViewRef<any>>componentRef.hostView).rootNodes[0]);
```

So far so good. Let's add template variables to Rose.

[](https://garage.sekrab.com/posts/creating-a-component-dynamically-and-programmatically-in-angular#Attaching-the-view)Attaching the view
-----------------------------------------------------------------------------------------------------------------------------------------

If we do the following in Rose's template:

``{{ something }}``

Upon creating the component, the variable will not reflect changes. To make it part of the change detection cycle, we need to `attachView` to the application reference.

```ts
// main.ts
const componentRef = createComponent(RosePartialComponent, {
  environmentInjector: this.appRef.injector,
});

// attach view to make it part of change detection cycle
this.appRef.attachView(componentRef.hostView);

document.body.append(
  (<EmbeddedViewRef<any>>componentRef.hostView).rootNodes[0]
);
```

Passing inputs
----------------

To set input after creation, all we need to do is use `setInput` of component reference:

```ts
// main.ts
// set inputs
componentRef.setInput('something', 'something else');

// rose component
// create input
@Input() something: string = 'somevalue';
```

We can also set the value of **public properties** through the `instance` property.

```ts
componentRef.instance.something = 'anything else';
```

We can also call public methods in our `instance` the same way. We're going to use that later to try and remove the component from within the component itself.

Detach and destroy.
--------------------

In order to move on to other routes, especially that we appended directly to the body, we need a way to destroy the component and detach it from the change detection cycle.

```ts
// maint.ts
// remove element using its component reference
this.appRef.detachView(componentRef.hostView);
componentRef.destroy();
```

Emit output
------------

If Rose has an output emitted, the `EventEmitter` in Angular is an extension of RxJS `Subject`, thus to listen to those events, we can subscribe to them.

```ts
// rose.component
// create output
@Output() onSomething: EventEmitter<string> = new EventEmitter<string>();

// in main.ts
// listen to output events
componentRef.instance.onSomething.subscribe((data: string) => {
  console.log(data);
});
```

### Put it in

Let me make the removal of the element, from inside the element itself. That should be handy. In Rose, let's add a remove button and attach it to an event.

```ts
// rose:
@Component({
  template: `
  // ... add remove button
    <button class="btn-fake" (click)="remove()">Remove</button>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class RosePartialComponent {
  // add output
  @Output() onRemove: EventEmitter<string> = new EventEmitter<string>();

  // emit on click
  remove(): void {
    this.onRemove.emit('remove element');
  }
}
```

In main component, let's listen to the remove button to remove element. And since we are subscribing, let's also unsubscribe.

```ts
// main.ts
insertElement(): void {
  // ...
  // listen to Output events
  const ref = componentRef.instance.onRemove.subscribe((data: string) => {
    // remove element, then unsubscribe
    this.appRef.detachView(componentRef.hostView);
    componentRef.destroy();
    ref.unsubscribe();
  });
}
```

With those basic ingredients, let's rewrite our [toast service](https://stackblitz.com/edit/angular-error-toast-v16) to get rid of the toast element, and make it programmatic.

Putting it to good use. The toast as an example.
------------------------------------------------

In our previous trip to error handling, we created a **toast service** that controls a single toast element, showing, and hiding it upon request. We can use this method to insert the component programmatically **upon initialization** of the Toast state service, and that will reduce some code lurking around, specifically the toast host element in the app root. This change is superficial and would not alter any behavior.

When initialized, the toast component injects the same service immediately, causing a **circular dependency issue**. To avoid that, we have a very simple solution, create the element upon first time `Show`.

We shall use a flag to detect the existence of the component.

```ts
// services/toast/toast.state
// rewrite

@Injectable({ providedIn: 'root' })
export class Toast extends StateService<IToast> {

  // v16 inject application reference
  constructor(private appRef: ApplicationRef) {
    // ...
  }

  // v16 use a flag
  private created: boolean;
  // add component programmatically
  private addComponent() {
    // v16 check component if it does not exist, create it
    if (this.created) {
      return;
    }
    const componentRef = createComponent(ToastPartialComponent, {
      environmentInjector: this.appRef.injector
    });

    this.appRef.attachView(componentRef.hostView);

    // append to body
    document.body.append((<EmbeddedViewRef<any>>componentRef.hostView).rootNodes[0]);

    this.created = true;
  }

  Show(code: string, options?: IToast) {
    // v16 add the component
    this.addComponent();
    // ... eveyrthing else stays the same
  }
}
```

Also remove any reference to `ToastPartialComponent`, and `gr-toast`. Now that we create the component programmatically, we don't even need the selector. But we do need to turn the component into `standalone`, and import its required modules:

```ts
// services/toast/toast.partial

// turn it into standalone, and drop the selector
@Component({
  standalone: true,
  imports: [CommonModule],
  // ...
})
export class ToastPartialComponent {
  constructor(public toastState: Toast) {}
}

```

Have a look at it on [StackBlitz](https://stackblitz.com/edit/angular-error-toast-v16).

## Dialog

Time to make a bigger use of it, let's adapt it to create a dialog service, where Peach is going to be displayed in a Rose component, upon request. Let's sleep on that till next Tuesday, or any day of the week.

Thanks for reading this far, did you miss me?

### RESOURCES

-   [Deprecated ComponentFactoryResolver](https://angular.io/api/core/ComponentFactoryResolver)
-   [Angular ViewContainerRef](https://angular.io/api/core/ViewContainerRef)
-   [StackBlitz project](https://stackblitz.com/edit/angular-program-component)
-   [Toast StackBlitz project](https://stackblitz.com/edit/angular-error-toast-v16)