Reputation: 180
PrimeNG changed the icon mechanics of the ui components, I am updating Angular for that as well from 15 to 17. I want my custom Checkbox Icon.
Touching the html or the logic in the ts file where the p-checkbox is used, is not an option, but maybe using a directive instead.
I want to insert the icon template of the checkbox via directive
I am trying something like this
<p-checkbox checkboxIcon>
<!-- this is not an OPTION
<ng-template pTemplate="icon">
<custom-icon></custom-icon>
</ng-template>
-->
</p-checkbox>
I somehow want to create the TemplateRef in the directive and insert it.
@Directive({
selector: '[checkboxIcon]',
})
export class IconDirective implements OnInit {
constructor(
private element: ElementRef,
@Self() private checkbox: Checkbox
) {}
@HostListener('click', ['$event']) onModelChange(event): void {
this.appendIconTemplate();
}
appendIconTemplate(): void {
const iconTemplate = document.createElement('ng-template');
iconTemplate.setAttribute('pTemplate', 'icon');
iconTemplate.innerHTML = '<custom-icon></custom-icon>';
this.element.nativeElement.appendChild(iconTemplate);
}
}
I have a minimal reproduction of the issue here
Found something with @Self()
and ComponentFactoryResolver
. But never worked with that, can anyone help?
I think those questions here and here want to achieve something similar.
Upvotes: 3
Views: 444
Reputation: 1191
So, there's a few things to consider for your needs, firstly, there is no easy and clean way to add a child component using directives, we can use the TemplateRef
and ViewContainerRef
but the component will be added as a sibling.
We also can't make use of the HostListener in our case, as this will not have the expected behaviour when using it on a structural directive.
So we must take a different approach, as the StackBlitz you shared is in Angular 17, I've made use of signals.
First, we need a new Component
to encapsulate the custom icon.
@Component({
selector: 'appCustomIcon',
template: `
<custom-icon></custom-icon>
`,
})
export class CustomIconContainer {}
Then we can modify the directive to be able to create our CustomIconContainer
inside of its embedded view :
@Directive({
selector: '[checkboxIcon]',
})
export class IconDirective {
// We will now us an input to know if the icon must be shown
checkboxIcon = input<boolean>(false);
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef
) {
// This effect will listen on the changes in our input.
effect(() => {
// Using the signal will mark it to be used by the effect.
const showIcon = this.checkboxIcon();
// We run the rest of the logic in the untracked function to avoid
// side effects
untracked(() => {
if (showIcon) this.appendIconTemplate();
else this.resetView();
});
});
}
appendIconTemplate(): void {
this.viewContainer.clear();
const embeddedView = this.viewContainer.createEmbeddedView(
this.templateRef
);
const component = this.viewContainer.createComponent(CustomIconContainer);
// This is the "hack" that allows us to create the component
// as a child of the element
embeddedView.rootNodes[0].appendChild(component.location.nativeElement);
}
resetView(): void {
this.viewContainer.clear();
this.viewContainer.createEmbeddedView(this.templateRef);
}
}
To avoid having to rely on a HostListener
, we will instead listen on clicks on the checkbox itself, here's the new ChboxDemo
class :
export class ChboxDemo {
protected showCustomIcon = false;
// The checked boolean is used to keep the state of the checkbox
// between the view container refreshes
protected checked = false;
protected onClick() {
this.showCustomIcon = !this.showCustomIcon;
}
}
<div class="card">
<p-checkbox
binary="true"
label="label"
(click)="onClick()"
*checkboxIcon="showCustomIcon"
[(ngModel)]="checked"
>
</p-checkbox>
</div>
There was a last small problem where the icon would not show, to fix it, I simpled added a width to the svg element :
<svg style="width: 20px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
d="M14.83,12,18,8.82A1,1,0,0,0,18,7.4L16.6,6a1,1,0,0,0-1.42,0L12,9.17,8.82,6A1,1,0,0,0,7.4,6L6,7.4A1,1,0,0,0,6,8.82L9.17,12,6,15.18A1,1,0,0,0,6,16.6L7.4,18a1,1,0,0,0,1.42,0L12,14.83,15.18,18a1,1,0,0,0,1.42,0L18,16.6a1,1,0,0,0,0-1.42Z"
/>
</svg>
Here's your forked StackBlitz with the working solution :
https://stackblitz.com/edit/btpn8a-as1ppw?file=src%2Fapp%2Fdemo%2Fchbox-demo.html
I hope this helps and I'm available for any further questions.
Upvotes: 1