Tennisclub7 wanted a registration form for new members on their WordPress website. HandiHow has created an Angular Elements widget that can be used on the WordPress site.

Hold on ... Angular Elements? This is a new concept that allows you to build Angular components into other applications, which are made with a completely different technology. Basically you make a small package of javascript that you can use anywhere on an HTML page. For example, you can build this into a WordPress page!

The code for this project can be found under this link.

Are you also interested in a custom widget for your WordPress site? Please contact HandiHow. For developers, below is the story of how we made this. Pay attention! It had quite a few feet in the ground….

The new Angular project

It all started with the command line. A number of dependencies are added to use Angular Elements (the last three statements).

ng new lake7elements
cd lake7elements  
npm i @angular/elements --save 
npm i @webcomponents/custom-elements --save
npm install --save document-register-element

We are also adding Firebase as a backend for the mini application. We also use Bootstrap for styling the application.

npm install firebase @angular/fire --save 
npm install --save bootstrap jquery 

We install the great SurveyJS to complete the registration form:

npm install survey-angular --save
npm install --save survey-knockout
npm install --save surveyjs-widgets

We only make 1 component, so we just do this in the app.component that is automatically already in the project. Important to add the “encapsulation”.

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
  encapsulation: ViewEncapsulation.Emulated
})

In the app module we import “Injector” and “createCustomElement”. We also add AppComponent to the entryComponents. We add an ngDoBootstrap method in which we define the custom element.

...
import { NgModule, Injector } from '@angular/core';

import { createCustomElement } from '@angular/elements';
...
entryComponents: [
  	AppComponent
  ]
...
export class AppModule { 
	constructor(private injector: Injector){}

	ngDoBootstrap(){
		
		const el = createCustomElement(AppComponent, { injector: this.injector });

		customElements.define('signup-form', el);
	}
}

In the styles.scss file we import the CSS stylesheets from Bootstrap and SurveyJS. Pay attention! If you try to do this in the angular.json file, your component will not get any styles ... Learning curve ... this took me a long time.

@import 'survey-angular/survey.css';
@import 'bootstrap/dist/css/bootstrap.min.css';

You change the following things to the angular.json file. You add a javascript file to the scripts. This ensures that the element is properly registered. You also set the “outputHashing” to “none”. This ensures that the compiled output later gets a predictable file name.

"scripts": [
              "node_modules/jquery/dist/jquery.min.js",
              "node_modules/bootstrap/dist/js/bootstrap.min.js",
              {
                  "input": "node_modules/document-register-element/build/document-register-element.js"
              }
            ]
....
"outputHashing": "none"

Now we can create the component. We create the form with SurveyJS Builder. Check the repo's code for more information on how to build SurveyJS into Angular. We recommend using Bootstrap for the application, as SurveyJS already has support for Bootstrap (there is a theme with Bootstrap). Incidentally, HandiHow has also used Bulma with SurveyJS before, this is not a problem but you will then be styling the forms longer.

Send emails

We are now going to create a function that will send the emails after the registration form has been created. We do this with Firebase Cloud Functions and SendGrid. In SendGrid we first create two templates (1 for the person who registers and 1 for the member administration).

firebase init
cd functions 
npm install --save @sendgrid/mail
npm install dateformat
firebase functions:config:set sendgrid.key="SENDGRID_API_KEY" sendgrid.id="SENDGRID"

Now we create the function and upload it to Firebase.

import * as functions from 'firebase-functions';
// import * as admin from 'firebase-admin';
const sgMail = require('@sendgrid/mail');
const dateFormat = require('dateformat');


export const sendEmails = functions.firestore.document('registrations/{id}').onWrite((change, context) => {
	const newValue = change.after.data();
	const previousValue = change.before.data();
	if(newValue && previousValue && newValue.isFinished && !previousValue.isFinished){
		//set the api key for sendgrid
		sgMail.setApiKey(functions.config().sendgrid.key);
		const data = newValue.data;
		const dynamicData = {
			  email: data.email,
	          firstName: data.firstName,
	          lastName: data.lastName,
	          gender: data.gender,
	          birthDate: dateFormat(data.birthDate, "d-m-yyyy"),
	          address: data.address,
	          postalCode: data.postalCode,
	          city: data.city,
	          mobilePhoneNumber: data.mobilePhoneNumber,
	          membershipType: data.membershipType,
	          date: dateFormat(data.date, "d-m-yyyy"),
	          imageUrl: data.image && data.image[0] && data.image[0].content ? data.image[0].content : 'https://cdn.pixabay.com/photo/2016/08/08/09/17/avatar-1577909_1280.png',
	          signedBy: data.signedBy,
	          placeSigned: data.placeSigned,
	          dateSigned: dateFormat(data.dateSigned, "d-m-yyyy"),
	          currentRatingSingles: data.currentRatingSingles ? data.currentRatingSingles : '-',
	          currentRatingDoubles: data.currentRatingDoubles ? data.currentRatingDoubles : '-'
        };
		const msgToNewMember = {
			// to: data.email,
			to: data.email,
			from: 'no-reply@tennisclub7.nl',
			fromname: 'Ledenadministratie Tennisclub7',
			templateId: 'd-06de23332fd04c43a1258a1c99f090bb',
			dynamic_template_data: dynamicData
		};
		const msgToAdministration = {
			to: 'ledenadministratie@tennisclub7.nl',
			from: 'no-reply@tennisclub7.nl',
			fromname: 'Ledenadministratie Tennisclub7',
			templateId: 'd-08a21deec3c9430ab552406395ed0c61',
			dynamic_template_data: dynamicData
		}
	    return sgMail.send(msgToAdministration)
		    .then(() => {
		    	return sgMail.send(msgToNewMember)
			    .then(() => {
			    	console.log('emails verstuurd');
			    	return true;
			    })
			    .catch((error:any) => {
			    	console.error(error.toString());
					return false;
			    })
		    })
		    .catch((error:any) => {
		    	console.error(error.toString());
				return false;
		    })
	} else {
		console.log('not sending email, the user is not yet finished');
		return false;
	}
	

})

As you can see, we first check whether the emails should be sent… Because if the form is saved in the meantime, this should not yet be done. Then we create the dynamic data for the templates. With the handy library “dateFormat” we can easily get the date in a Dutch readable format.

Merge the output

Angular normally creates a number of files with the “ng build –prod” command. We would like only 1 javascript file, so that it can be easily loaded as a widget. For this we install FS-extra and concat.

npm install fs-extra concat --save-dev

We create a new file “build-elements.js” in the root of the project.

const fs = require('fs-extra');
const concat = require('concat');
(async function build() {
  const files = [
    './dist/lake7elements/runtime-es5.js',
    './dist/lake7elements/polyfills-es5.js',
    './dist/lake7elements/scripts.js',
    './dist/lake7elements/main-es5.js',
    './dist/lake7elements/5-es5.js'
  ]
  await fs.ensureDir('elements')
  await concat(files, 'elements/signup-form.js');
  await fs.copyFile('./dist/lake7elements/styles.css', 'elements/styles.css')

})()

In package.json we add a new line to the scripts.

"build:elements": "ng build --prod && node build-elements.js"

Now we can call this command via the command line with:

npm run build:elements  

We will create an index.html file in the folder “elements” that has just been created.




	Testing elements
	
	


	

We test this by going to the folder “elements” and then http-server command and voila! The component works!