Reputation: 219
I thought I would post this example to show how I migrated some older code to Angular 13 that replaced HTML elements with Angular Components. My code originally used ComponentFactoryResolver
which is now deprecated. If there is a better solution out there, I would be happy to update this!
Context: My use case was to add some customization to the display of parsed markdown by extending ngx-markdown. I picked directives as it fit well and was easy enough to implement. Specifically I was wanting to use Angular components to customize code blocks, images, and videos in a web app. This example shows what I did to replace video elements with my own, custom video player.
Here's the original way that I implemented my directive this using the ComponentFactoryResolver
with pre-Angular 13.
import { DOCUMENT } from '@angular/common';
import { ApplicationRef, ComponentFactoryResolver, Directive, ElementRef, HostListener, Inject, Injector} from '@angular/core';
import { VideoPlayerComponent } from '../components/video-player/video-player.component';
@Directive({
selector: 'markdown,[markdown]'
})
export class VideoReplacementDirective {
constructor(
@Inject(DOCUMENT) private document: Document,
private injector: Injector,
private applicationRef: ApplicationRef,
private componentFactoryResolver: ComponentFactoryResolver,
private element: ElementRef<HTMLElement>
) {}
@HostListener('ready')
public processVideos() {
// find our videos to replace
const els = this.element.nativeElement.querySelectorAll<HTMLVideoElement>('video');
// process each element
for (let i = 0; i < els.length; i++) {
const v = els[i];
// get the parent of our video and make sure it exists to make TS happy below
const parent = v.parentElement;
if (parent) {
// create container element
const container = this.document.createElement('div');
// make our component
const component = this.componentFactoryResolver
.resolveComponentFactory(VideoPlayerComponent)
.create(this.injector, [], container);
this.applicationRef.attachView(component.hostView);
// set the source for the video
component.instance.src = v.getElementsByTagName('source')[0].src;
// replace our element
parent.replaceChild(container, v);
}
}
}
}
Upvotes: 3
Views: 2811
Reputation: 219
With Angular 13, the docs and deprecated comments mention that you should use ViewContainerRef
but don't tell you all the details to replicate what I was doing before.
Essentially I:
ViewContainerRef
to make my Angular componentElementRef
as a public propertyHere's the updated directive code which is a lot easier for me to understand and follow:
import { Directive, ElementRef, HostListener, ViewContainerRef } from '@angular/core';
import { VideoPlayerComponent } from '../components/video-player/video-player.component';
@Directive({
selector: 'markdown,[markdown]'
})
export class VideoReplacementDirective {
constructor(private view: ViewContainerRef, private element: ElementRef<HTMLElement>) {}
@HostListener('ready')
public processVideos() {
// find our videos to replace
const els = this.element.nativeElement.querySelectorAll<HTMLVideoElement>('video');
// process each element
for (let i = 0; i < els.length; i++) {
const v = els[i];
// get the parent of our video and make sure it exists to make TS happy below
const parent = v.parentElement;
if (parent) {
// make our component
const component = this.view.createComponent(VideoPlayerComponent);
// set the source for the new video
component.instance.src = v.getElementsByTagName('source')[0].src;
// replace our element
parent.replaceChild(component.instance.elementRef.nativeElement, v);
}
}
}
}
And here is my updated component to show with the added ElementRef
in the constructor to make this work.
import { Component, ElementRef, Input } from '@angular/core';
@Component({
selector: 'vis-ngx-markdown-video-player',
templateUrl: './video-player.component.html',
styleUrls: ['./video-player.component.scss']
})
export class VideoPlayerComponent {
/** URL for the video to play */
@Input() public src!: string;
// add elementRef as public property
constructor(public elementRef: ElementRef<HTMLElement>) {}
}
Upvotes: 3