DEV Community

Cover image for Take Your WordPress Site Farther With Angular
Stephen Whitmore
Stephen Whitmore

Posted on • Updated on

Take Your WordPress Site Farther With Angular

TL;DR

You can create a micro frontend with Angular and dress it up like a WordPress plugin. Here's a working example of what I'm talking about - https://github.com/stevewhitmore/angular-wordpress-integration-starter

Background

At the risk of sounding like I'm recycling an old post of mine, I wanted to give a quick tutorial on how you can use Angular to add custom business logic to the frontend of your WordPress app.

There was a time when Angular bundles were large. Too large to consider doing something like inserting an Angular app into a WordPress site. Back when I wrote about doing this with Vue.js I wasn't aware of just how small these bundles had become at that point, in large part due to the new(ish) Ivy compilation engine the Angular team rolled out with version 9.

As of this writing the latest stable version of Angular is 14.2.7. The bundle size for a freshly generated Angular app with no routing included or any other kind of extra optimization is roughly 180kb. For WordPress installations this is a small drop in the bucket, and well worth the benefits available to you.

So, if you have need of adding highly interactive, complicated business logic to your WordPress app then read on.

Step 1: Make the Angular app

Assuming you have npm installed already, install the Angular CLI



npm i -g @angular/cli


Enter fullscreen mode Exit fullscreen mode

Once that's done create the Angular project.



ng new my-awesome-new-app


Enter fullscreen mode Exit fullscreen mode

Follow the prompts to get your app generated. You wont likely need routing if you're embedding it into a page of your WordPress app. Using Sass or SCSS isn't a bad idea but isn't strictly necessary. Using either wont meaningfully impact the size of your bundle.

Image description

Now you can work on the application outside of WordPress. It should not be tightly coupled to your WordPress app and therefore should work the same independently.

Step 2: Folder Restructuring

The Angular CLI will generate some files that are not really required. You can safely remove the following: .browserlistrc, .editorconfig, package-lock.json, .vscode.

Create a new folder named app from the root of your newly created Angular project. Put everything from the root of your project into the app folder.

Next, make a new folder called plugin and another folder with the same name of whatever your new WordPress plugin will be called. In my case it's my-awesome-new-app.

Finally, move the .git, .gitignore, and README.md files from your newly created app folder to the root of your project.

Your folder structure will look something like this:



|-- app
    |-- [+] node_modules
    |-- [+] src
    |-- angular.json
    |-- karma.conf.js
    |-- package.json
    |-- tsconfig.app.json
    |-- tsconfig.json
    |-- tsconfig.spec.json
|-- plugin
    |-- my-awesome-new-app
|-- [+] .git
|-- .gitignore
|-- README.md


Enter fullscreen mode Exit fullscreen mode

Step 3: Create the Plugin File

Create a new php file in your /plugin/my-awesome-new-app folder. You'll want the php file name to be the same as the folder name so WordPress knows where to find the plugin file. At this point your folder structure should look like this:



|-- app
    |-- [+] node_modules
    |-- [+] src
    |-- angular.json
    |-- karma.conf.js
    |-- package.json
    |-- tsconfig.app.json
    |-- tsconfig.json
    |-- tsconfig.spec.json
|-- plugin
    |-- my-awesome-new-app
        |-- my-aweseome-new-app.php
|-- [+] .git
|-- .gitignore
|-- README.md


Enter fullscreen mode Exit fullscreen mode

Now you'll want to fill the new php file with code that

  • provides metadata about the plugin through the comments at the top of the file
  • registers and enqueues the styles and minified javascript for the Angular app
  • creates a shortcode to insert into your WordPress site

It'll look something like this:



<?php
/**
 * Plugin Name:         My Awesome New App
 * Plugin URI:          https://yourdomain/wherever-you-keep-the-app-readme-file
 * Description:         Some great app that I made and it's so great it'll make your life great!
 * Version:             1.0.0
 * Author:              Steve Whitmore
 * Author URI:          https://stevewhitmore.dev
 * 
 * Be sure to rename the folder this file is in and this file to match what your plugin will be called. The names must be the same so WordPress knows where to look.
 */

function load_ng_scripts() {
    wp_enqueue_style( 'ng_styles', plugin_dir_url( __FILE__ ) . 'dist/my-awesome-new-app/styles.somehash.css' );
    wp_register_script( 'ng_main', plugin_dir_url( __FILE__ ) . 'dist/my-awesome-new-app/main.somehash.js', true );
    wp_register_script( 'ng_polyfills', plugin_dir_url( __FILE__ ) . 'dist/my-awesome-new-app/polyfills.somehash.js', true );
    wp_register_script( 'ng_runtime', plugin_dir_url( __FILE__ ) . 'dist/my-awesome-new-app/runtime.somehash.js', true );
}

add_action( 'wp_enqueue_scripts', 'load_ng_scripts' );

function attach_ng() {
    wp_enqueue_script( 'ng_main' );
    wp_enqueue_script( 'ng_polyfills' );
    wp_enqueue_script( 'ng_runtime' );

    return "<app-root></app-root>";
}

add_shortcode( 'ng_wp', 'attach_ng' );

// Add the shortcode [ng_wp] to any page or post.
// The shorcode can be whatever. [ng_wp] is just an example.
?>


Enter fullscreen mode Exit fullscreen mode

Step 4: Change the NPM Build Script

You're nearly there. We do need to account for a couple things.

  1. Your php file needs to be able to find the minified js and css files Angular outputs when it builds.
  2. Angular likes to throw a hash in the js and css file names to help with caching issues.

The first issue is easy enough to deal with.

The plugin will need to have a copy of the executables for this app so we'll update the build script in our package.json file to do the extra step of copying the dist folder to our new plugin folder.

That means



"build": "ng build"


Enter fullscreen mode Exit fullscreen mode

becomes



"build": "ng build && cp -r ./dist ../plugin/my-awesome-new-app"


Enter fullscreen mode Exit fullscreen mode

Easy peasy, right? Unfortunately we're not quite there yet. Those files in the php example above with "somehash" in their name was not a typo. I put that placeholder there because every time changes are made to your Angular app, the CLI will generate new names for them to prevent browser caching. They all start the same but have some jumbled mess (a hash) between the prefix and their file types. So dist/my-awesome-new-app/main.somehash.js really looks something like dist/my-awesome-new-app/main.14089c95b00767cf.js.

We'll need to create a small script that will copy the names of the minified js and css files to the php file. Luckily I went ahead and did this for you to save some time:

build.js



const { execSync } = require('child_process');
const fs = require('fs');
const pluginName = 'my-awesome-new-app';
const pluginFileName = `${pluginName}.php`;
const destination = `../plugin/${pluginName}`;
const pluginFilePath = `${destination}/${pluginName}.php`

// Remove the dist folder in the plugin file if its present. Doesn't care if it's not.
fs.rmSync(`${destination}/dist`, { recursive: true, force: true });

// Run the build command
execSync('ng build --configuration production', { encoding: 'utf-8', stdio: 'inherit' });

// Move the bundle from the `/app` folder to the plugin's folder
execSync(`mv ./dist ${destination}`)

// copy the js and css file names to an array
distFilenames = fs.readdirSync(`${destination}/dist/my-awesome-new-app`);
scriptsAndStyleFiles = distFilenames.filter(file => file.endsWith('.js') || file.endsWith('.css'));

// replace the js and css file names in the php file contents
const pluginFileContents = fs.readFileSync(`${pluginFilePath}`, 'utf8');

const updateLine = (line, name) => {
    const matchedLinePart = line.match(/(?<=app\/).*?(?=\')/gs).toString();
    const matchedFileName = scriptsAndStyleFiles.find(file => file.includes(name));
    return line.replace(matchedLinePart, matchedFileName);
}

const updatedFileContentArray = pluginFileContents.split(/\r?\n/).map(line => {
    switch (true) {
        case line.includes('wp_enqueue_style( \'ng_styles'):
            return updateLine(line, 'styles');
        case line.includes('wp_register_script( \'ng_main'):
            return updateLine(line, 'main');
        case line.includes('wp_register_script( \'ng_polyfills'):
            return updateLine(line, 'polyfills');
        case line.includes('wp_register_script( \'ng_runtime'):
            return updateLine(line, 'runtime');
        default:
            return line;
    }
});
const updatedFileContents = updatedFileContentArray.join('\n');

// write the new names to the php file
fs.writeFileSync(`${pluginFilePath}`, updatedFileContents);
console.log(`*************** ${pluginFileName} updated! ***************`)


Enter fullscreen mode Exit fullscreen mode

Go back to your /app/package.json file and change that build script to look like this:



"build": "node ./build.js"


Enter fullscreen mode Exit fullscreen mode

Run npm run build from your /app folder.

The final folder structure should look like this:



|-- app
    |-- [+] node_modules
    |-- [+] src
    |-- angular.json
    |-- build.js
    |-- karma.conf.js
    |-- package.json
    |-- tsconfig.app.json
    |-- tsconfig.json
    |-- tsconfig.spec.json
|-- plugin
    |-- my-awesome-new-app
        |-- [+] dist
        |-- my-aweseome-new-app.php
|-- [+] .git
|-- .gitignore
|-- README.md


Enter fullscreen mode Exit fullscreen mode

Step 5: Upload and Activate

Upload the contents of /plugin/ to your WordPress installation's /wp-content/plugins folder. Log into your WordPress admin panel and navigate to the Plugins section. You should see your new plugin waiting to be activated.

plugin activate

Step 6: Insert the Shortcode

Once activated you can either insert the shortcode through template code or by adding it to a page



<?php
/**
 *
 * Template name: Some Template
 *
 */
get_header(); ?>

<section id="main">
    <h2>Some Page About Something Or Other</h2>
    <?php echo do_shortcode("[ng_wp]"); ?>
</section>

<?php get_footer(); ?>


Enter fullscreen mode Exit fullscreen mode

or

shortcode page

Step 7: Celebrate

Woohoo! We did it! Let me know if you run into any issues or have any questions. You can find an example project that followed all of these steps already here. Happy coding!

Top comments (11)

Collapse
 
wordpressure profile image
TricaExMachina

This is brilliant, thank you.

Collapse
 
stephenwhitmore profile image
Stephen Whitmore

Glad you liked it! Hopefully you get some use out of it.

Collapse
 
wordpressure profile image
TricaExMachina

While a little removed from the point of your post, this did inspire me to rebuild my personal site, which was straight wordpress/PHP, into a headless wp site with an angular frontend. Its been a good workout for practicing Angular, so thanks for that.

Collapse
 
leonardofabian profile image
Leonardo Fabian

Excellent, I just have one question, why didn't you put the dist folder and the plugin file inside the plugin folder? So a user would only have to copy the plugin folder and that's it.

Collapse
 
stephenwhitmore profile image
Stephen Whitmore

I think that may be a typo in the example after folder structure. I just ran the build in the example project and it looks like it does in fact put the dist folder in the plugin folder with the plugin file. Thanks for catching that!

Image description

Collapse
 
leomayer profile image
leomayer

Thx a lot for providing a full example. This made my day. With eating the appetite arise and more questions come.

First of all I want to note that the build script has a small bug since your plugin name is hard coded at one place. You'll recognize when changing the name of it. I managed to correct it without problems. The other thing which took me a while to understand was that the structure is not the default Angular one. Which I figured out after some time. Angular won't have an additional app-folder but your script relies on this. Plus the plugin.php has finally a wrong folder structure. Tiny bits but worth to mention.

The other thing: is there a way to include within the sample a configuration interface? One which is ONLY for the Settings within WordPress. With your approach I can only imagine two separate plugins but might be wrong. Perhaps you have a better idea.

Collapse
 
icolumbro profile image
Ivan Columbro

Hello, using this approach to run an Angular app inside a WordPress page, how is it possible to manage the various routes foreseen by an application? I mean in the typical way that for each route a component is used, for example if I go to /my-first-route, MyFirstComponent is rendered.

Collapse
 
stephenwhitmore profile image
Stephen Whitmore

Do you mean how would the Angular app see the URL of the WordPress page? If that's the case I want to say you simply can't because the app is embedded and is therefore isolated from the rest of the page; making it unaware. I can't say with absolute certainty.

I may not be understanding you 100% though. Could you elaborate more?

Collapse
 
lewismorgans profile image
Lewis

This doesn't seem to be working for me :(. When I upload the plugin as soon as I activate it, I can see the contents of the php file printed out at the top of the plugin page? Following from that when also trying to create a new page and using the [ng_wp] once again it simply outputs the text from the php file. Any help available?

Collapse
 
merp02 profile image
Mer • Edited

Thanks for sharing this, it's great!
I have a question: what if I would like to add 2 plugins to the same wordpress app?
I create two, I change name of function and variables, but I have problem with display it. Only one is added per site even I add 2 shortcode. If I use it on separate sites - everything is ok.