1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.
  2. Anuncie Aqui
    Anuncie aqui você Também: fdantas@4each.com.br

How do I properly use an Angular Web Component inside a JSF application?

Discussão em 'Angular' iniciado por codrut, Junho 4, 2025.

  1. codrut

    codrut Guest

    I am trying to test out using an Angular web component inside a JSF application, with the aim of having an incremental refactor of some areas before a full rewrite. To test out this approach I used a simple component in Angular 19 (the app was initialised as zoneless):

    My component to be exported (the additional logic is for testing purposes):

    import {
    Component,
    input,
    output,
    booleanAttribute,
    computed,
    signal,
    ChangeDetectionStrategy,
    } from '@angular/core';

    @Component({
    selector: 'app-like-button', // Angular component selector
    standalone: true,
    template: `
    <button type="button" (click)="toggleLike($event)">
    <svg
    xmlns="http://www.w3.org/2000/svg"
    width="24"
    height="24"
    viewBox="0 0 48 48"
    >
    <path
    d="M24,44.9l-17.7-17.7c-4.3-4.3-4.3-11.3,0-15.6c2.1-2.1,4.9-3.3,7.9-3.3c3,0,5.8,1.2,7.9,3.3L24,13.5l1.9-1.9
    c2.1-2.1,4.9-3.3,7.9-3.3c3,0,5.8,1.2,7.9,3.3c4.3,4.3,4.3,11.3,0,15.6L24,44.9z"
    fill="none"
    stroke="#E57373"
    stroke-width="5"
    />
    <path
    d="M24,44.9l-17.7-17.7c-4.3-4.3-4.3-11.3,0-15.6c2.1-2.1,4.9-3.3,7.9-3.3c3,0,5.8,1.2,7.9,3.3L24,13.5l1.9-1.9
    c2.1-2.1,4.9-3.3,7.9-3.3c3,0,5.8,1.2,7.9,3.3c4.3,4.3,4.3,11.3,0,15.6L24,44.9z"
    [attr.fill]="safeLikedState ? '#E57373' : 'transparent'"
    />
    </svg>
    </button>
    `,
    styles: [
    `
    button {
    border: none;
    background: transparent;
    cursor: pointer;
    padding: 0; /* Adjusted padding */
    width: 24px; /* Explicitly set */
    height: 24px; /* Explicitly set */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    vertical-align: middle;
    }

    button:hover {
    background-color: #e573732e;
    border-radius: 50%;
    }

    svg {
    width: 100%;
    height: 100%;
    }
    `,
    ],
    changeDetection: ChangeDetectionStrategy.OnPush, // Recommended for signal-based components
    })
    export class LikeButtonComponent {
    liked = input(false, { transform: booleanAttribute });
    likedChange = output<boolean>({ alias: 'likedchange' });

    private internalLiked = signal<boolean | null>(null);

    likedState = computed(() => {
    // Add defensive checks here too, though less likely to fail if toggleLike is called
    if (
    !this ||
    typeof this.internalLiked !== 'function' ||
    typeof this.liked !== 'function'
    ) {
    console.error(
    '[LikeButton] CRITICAL: `this` or signals not available in `likedState` computed.'
    );
    return false; // Return a default state to prevent template error
    }
    const override = this.internalLiked();
    return override !== null ? override : this.liked();
    });

    get safeLikedState(): boolean {
    try {
    return this.likedState?.() ?? false;
    } catch {
    return false;
    }
    }


    main.ts:

    import { createCustomElement } from '@angular/elements';
    import { createApplication } from '@angular/platform-browser';
    import { appConfig } from './app/app.config';
    import { LikeButtonComponent } from './app/like-button/like-button.component';

    createApplication(appConfig)
    .then((app) => {
    const LikeButton = createCustomElement(LikeButtonComponent, {
    injector: app.injector,
    });
    customElements.define('like-button', LikeButton);
    })
    .catch((err) => console.error(err));


    And for the JSF:

    <ui:composition xmlns:h="http://xmlns.jcp.org/jsf/html"
    xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
    xmlns:f="http://xmlns.jcp.org/jsf/core"
    xmlns:p="http://primefaces.org/ui"
    template="../../layouts/user.xhtml"
    xmlns="http://www.w3.org/1999/xhtml">

    <f:metadata>
    <f:viewAction action="#{viewDashboardAgentMB.init}"/>
    <ui:param name="currentMB" value="#{viewDashboardAgentMB}"/>
    </f:metadata>

    <ui:define name="title">Title</ui:define>

    <ui:define name="head">
    <script type="text/javascript">
    window.jQuery = window.jQuery || {};
    window.$ = window.jQuery;
    </script>
    <h:eek:utputScript name="angular/main.js" target="head"/>
    </ui:define>

    <ui:define name="content">
    <div class="#{sessionStorageMB.darkTheme ? 'dashboardContent-dark' : 'dashboardContent-light'}">
    <div id="likeMount"></div>

    <h:form id="dashboard">
    <!-- Content -->
    </h:form>
    </div><!-- End Custom Dash -->

    <h:eek:utputStylesheet name="dashboards.css" library="css"/>

    <script>
    window.addEventListener('DOMContentLoaded', () => {
    customElements.whenDefined('like-button').then(() => {
    const mount = document.getElementById('likeMount');
    if (mount) {
    mount.innerHTML = '<like-button liked="false"></like-button>';
    } else {
    console.warn('likeMount not found in DOM');
    }
    });
    });
    </script>

    </ui:define>
    </ui:composition>


    I have put the script in resources/angular/

    I have tried to use the tag in a separate html page and it works as expected

    Then, I put the file in the resources folder in JSF as shown above;

    The encountered error is:
    Cannot read properties of undefined (reading 'safeLikedState')

    What am I missing?

    Continue reading...

Compartilhe esta Página