Take Your WordPress Site Farther With Angular
Tags: wordpress, angular, typescript, php
Published: 2022-10-22 07:12:51

## 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](https://dev.to/stevewhitmore/take-your-wordpress-site-further-with-vue-js-179n), 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

```shell
npm i -g @angular/cli
```

Once that's done create the Angular project.

```shell
ng new my-awesome-new-app
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cawglbtm0k8ldpqklqcy.png)

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
```

## 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
```

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
<?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.
?>
```

## 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

```json
"build": "ng build"
```

becomes

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

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*

```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! ***************`)
```

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

```json
"build": "node ./build.js"
```

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
```

## 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2lry7uvlg5lvlzol35ft.png)


## Step 6: Insert the Shortcode

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

```php
<?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(); ?>
```

or

![shortcode page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/koyxx5nsau2r6mjams18.png)

## Step 7: Celebrate

<img width="100%" style="width:100%" src="https://media.giphy.com/media/6oMKugqovQnjW/giphy.gif">

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](https://github.com/stevewhitmore/angular-wordpress-integration-starter). Happy coding!