Skip to main content

Build Modern Joomla! Module with Angular Frontend

What it's all about

This guide will help you to "marry" a modern Joomla! 5.x / 6.x module with an Angular frontend. The communication between the module frontend / backend, i.e. Angular and PHP, takes place via AJAX calls. This guide shows you the basic steps on how to build such a communication.

Create new project

I work with JetBrains PHP Storm, but in general you can use an IDE of your choice as we will not cover IDE specific topics. Firstly, create a new (empty) project without a template. In this project, we will now create the following folders and files:
The folder client will represent our Angular development environment, the folder module contains our Joomla! module code.
Note
We will also use Composer in this project, which will make some things easier for us, especially in deployment. However, you can also work without Composer, it will simply mean more manual work for you.
Create the following Folders / Files:
  • client
  • module
  • composer.json
  • .gitignore

Loading & installing Module Blueprint

I mentioned at the beginning that we are creating a project without a template. However, this only applied to the root directory. To save us some work, we will first use a Joomla! module blueprint to get the basic module framework. You can find such a blueprint module here, for example: https://github.com/marcorensch/Joomla-Blueprint-Example-Module. Of course, you can also use your own or work with an existing module. Simply copy the files into the module directory.
If you use the blueprint linked above, you can now execute the Composer script setup in the module folder to set all project variables. The corresponding readme (now also available in the module directory) will show you what the script does. In addition, the manual steps are also explained to you if you cannot / do not want to work with Composer. The Composer script will guide you through the steps:
composer setup
Place the Blueprint Files in the module directory:
  • ...
  • module
  • language
  • media
  • services
  • sql
  • mod_modulename.xml
  • ...
Composer Setup Process

Clean up blueprint files

As it is a blueprint module it also comes with some features that we don't need, most of them we can simply ignore but a few things we have to adapt / remove.

Remove unnecessary templates

We should "tidy up" the tmpl folder a little. The folders "grid", "table" and the associated PHP files for these templates can be deleted.

Empty SQL file

The contents of the SQL file are not required in this tutorial, so these can also be removed. Remove the contents of all files under module/sql/.

Create Angular App template file

After we have deleted the old files / directories, we create a new template in which we will later deploy our Angular app. Now create the folder angular-ui in the folder module/tmpl and a PHP file with the name angular-ui.php.
Later we will adapt the content of this file so that the Angular app is loaded here.
Generate a new file named angular-ui.php in a new directory called angular-ui:
  • module
  • tmpl
  • aungular-ui
  • aungular-ui

Configure Composer.json in the root directory

So that we can "build" quickly and easily later, we create a composer.json file in the root directory if it does not already exist and populate it with the following content:
{
  "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
    }
  }
}
This allows us to simply generate a clean ZIP file of our module later using deploy-zip.

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);
})();
Note

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).

Attention

@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)
}
Note

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

In the next step, we need to refer to the Angular template in our default template module. To do this, we delete all content from default.php in the "module/tmpl" directory and replace it with the following code:
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

Now it's time to "marry" our angular-ui.php file, which is currently still empty, with the Angular app. To do this, we load the corresponding scripts & styles into our PHP file and create the container element for our Angular app. The content of the angular-ui.php now looks like this:
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>;
For the first test, the module ID was entered manually at this point. If everything works as expected, the number "42" will appear in the console when we install the module and publish and call it on a page.
As we have already transferred the module ID from the default template to our angular-ui template, you can already use this:
$moduleId = $displayData['moduleId'];
...
<app-root moduleId="<?php echo $moduleId;?>"></app-root>;

Building and installing the module

Once all the steps have been carried out, we can now enter the root directory of our module using:
composer deploy-zip
Build our module, in the directory "dist" we now have the finished ZIP file that we can install in Joomla!
That's it for the first part for now, stay tuned for the second part of this tutorial which will explain how Angular communicates with the Joomla! backend using AJAX calls.

Was this article helpful?

Help me to continue writing such articles. You can support my work by making a donation via PayPal or by purchasing a paid extension.