Build Modern Joomla! Module with Angular Frontend
What it's all about
Create new project
-
client
-
module
-
composer.json
-
.gitignore
Loading & installing Module Blueprint
composer setup
- ...
-
module
-
language
-
media
-
services
-
sql
-
mod_modulename.xml
- ...

Clean up blueprint files
Remove unnecessary templates
Empty SQL file
Create Angular App template file
-
module
-
tmpl
-
aungular-ui
-
aungular-ui
Configure Composer.json in the root directory
{
"name": "nxd/module-angular-ex",
"description": "Angular Example Module for Joomla! 4.x / 5.x",
"require": {
"php": "^8.3"
},
"scripts": {
"install-client": [
"cd client && npm install"
],
"build-client": [
"cd client && npm run build-safe"
],
"post-install-deploy": [
"rm -rf vendor",
"composer clear-cache",
"composer install --no-dev --optimize-autoloader"
],
"deploy-zip": [
"@post-install-deploy",
"@build-client",
"mkdir -p dist",
"cd module && zip -r ../dist/mod_ex-angular-module.zip . -x '*.DS_Store' -x '*.git*' -x 'Thumbs.db' -x 'node_modules/*' -x '*/vendor' -x 'vendor/*' -x '*/vendor/*' -x 'vendor/bin/*' -x '.idea' -x '.idea/*' -x '*/.idea' -x '*/.idea/*' '*/.gitignore' -x 'dist/*' -x 'composer.*' -x '*.zip'"
]
},
"config": {
"vendor-dir": "vendor",
"allow-plugins": {
"php-http/discovery": true
}
}
}
Initiate Angular project
The "client" directory serves as the development area for our Angular frontend. I won't go into the details of how to install Angular CLI on your system here. You can find out how to do this, for example, in the official documentation at https://angular.dev/tools/cli.
In the root directory of our project, we open a new terminal session and create a new Angular project in the "client" directory. We use the "skip-git" flag here as we will later activate Git in the root directory so that client & module are versioned. I use less as the style option - but this is more of a personal preference. We execute the following terminal / command line command in the root directory:
ng new angular-ui --directory=client --style=less --skip-git
This now generates an Angular app in the client directory - we can now start it:
cd client & ng serve
Now check whether the Angular app is running and then stop the application again.
Customise Angular build process
To ensure that our Angular client is also placed in the correct directory (module tmpl), we must next adapt the Angular build process. To do this, we open the "angular.json" file in the client directory. Under projects > [project name] >architect > build > options we find the relevant points that we need to create or change:
We change the outputPath from the string dist/angular-ui to an object:
...
"outputPath": {
"base": "../module/tmpl/angular-ui",
"browser": ""
},
...
We also set the outputHashing to none. We find / define this option under projects > [project name] >architect > build > configurations >production OutputHashing "none" ensures that our CSS / JS files do not receive a random suffix. Otherwise the files would receive a different file name each time, which we would have to adapt accordingly in our Joomla! / PHP code. In the client/package.json file (the package file for our client), we also create the following lines in the scripts area:
{
"name": "angular-ui",
"version": "0.0.0",
"scripts": {
...
"preserve-php": "cp ../module/tmpl/angular-ui/angular-ui.php ../module/tmpl/angular-ui.php.backup",
"restore-php": "mv ../module/tmpl/angular-ui.php.backup ../module/tmpl/angular-ui/angular-ui.php",
"build-safe": "npm run preserve-php && ng build; npm run restore-php"
},
...
}
We will then effectively use the build-safe script. This copies our angular-ui.php file from our template directory, executes our Angular build process and then places the php file back in its place. This is necessary because the Angular build process completely deletes the folder content before a build. Another alternative would be not to let Angular "build" directly into the template and to move the created Angular app into the template directory after the build. If we now create a test build by executing the following command in the client directory:
npm run build-safe
Our Angular app should now be published in our tmpl/angular-ui directory.
Set up Angular App
Now we have to modify our Angular app a little bit. This guide is for Angular version 19. Basically, we create a CustomElement for our application and load it later in our PHP file.
Create Angular app as CustomElement
The first step is to edit the main.ts file in the client/src directory. To do this, we delete the current content and write instead:
import {createCustomElement} from "@angular/elements";
import {ApplicationConfig} from "@angular/core";
import {createApplication} from "@angular/platform-browser";
import { ExposerComponent } from './app/exposer.component';
import {provideHttpClient} from "@angular/common/http";
const config: ApplicationConfig = {
providers: [
provideHttpClient()
]
};
// Async IIFE (Immediately Invoked Function Expression)
(async () => {
// Application erstellen
const app = await createApplication({
providers: config.providers
});
// Injector aus der Application holen
const injector = app.injector;
// Web Component registrieren
const appElement = createCustomElement(AppComponent, {
injector
});
customElements.define('app-root', appElement);
})();
Of course you can also use a different name instead of app-root. However, please note that you will then also use this name in the template (PHP file).
@angular/elements may need to be installed in the project - your IDE should display a corresponding message.
Clean up app.component.html
In the next step, we delete the entire content of the app.component.html file in the client/src/app directory. This component is the main entry point for our frontend. For now, you can enter a placeholder here that will later be displayed in Joomla!
Hello from the Angular app
Edit app.component.ts
Next, we prepare the app.component.ts to receive variables. For later use, it is actually sufficient to transfer the module ID to the Angular application. We will continue working with this later.
Since we receive the module ID as an attribute of the host element, we have to import "ElementRef" from the Angular core and use it first in the constructor:
constructor(private elementRef:ElementRef) {}
moduleId is defined as a variable in the component / class:
export class AppComponent {
moduleId: string | null = null;
…
}
As we can only access the attributes after the page has been loaded, we must also use the corresponding hook:
ngAfterViewInit() {
// Read from DOM attribute
const hostElement = this.elementRef.nativeElement;
if (!this.moduleId) {
this.moduleId = hostElement.getAttribute('moduleId');
}
console.log(this.moduleId)
}
If the router was active when the Angular app was created and you are not using it, remove the router import in the Angular app and also remove it from the Decorator:
Component({
selector: 'app-root',
imports: [], // <— No Imports
templateUrl: './app.component.html',
styleUrl: './app.component.less'
})
Run first build
Now it's time for our first build. We call our build script in the client directory:
npm run build-safe
We remember: the build-safe script copies our current angular-ui.php from the template folder "angular-ui" - and thus "saves" its content. The Angular app is then built and the angular-ui.php is copied back into the directory. Simple and elegant.
After the build process has been run, we find the following files in the "module/tmpl/angular-ui" folder:
-
main.js
-
polyfills.js
-
styles.js
-
index.html
-
angular-ui.php
- ... other angular files
Default template should now implement Angular template
defined('_JEXEC') or die;
use Joomla\CMS\Layout\FileLayout;
$fileLayout = new FileLayout('angular-ui', __DIR__ . '/angular-ui');
echo $fileLayout->render(array('moduleId' => $module->id));
Link template PHP file with Angular app
use Joomla\CMS\Factory;
defined('_JEXEC') or die;
$wa = Factory::getApplication()->getDocument()->getWebAssetManager();
$wa->registerAndUseScript('ex-angular-polyfills', 'modules/mod_angularexample/tmpl/angular-ui/polyfills.js', [], ["type" => "module"]);
$wa->registerAndUseScript('ex-angular', 'modules/mod_angularexample/tmpl/angular-ui/main.js', [], ["type" => "module"]);
$wa->registerAndUseStyle('ex-angular-styles', 'modules/mod_angularexample/tmpl/angular-ui/styles.css');
?>
<app-root moduleId="42"></app-root>;
$moduleId = $displayData['moduleId'];
...
<app-root moduleId="<?php echo $moduleId;?>"></app-root>;
Building and installing the module
composer deploy-zip