I suppose, most of you, who found this article, have already built a project, using Spring Boot and Angular. Today, I will show you how we can use the new Angular Elements library (@angular/elements), which is available with the release of Angular 6.
So, have you ever tried to embed a custom Angular component in external, non-Angular application, by providing only one .js and one .css file? If not, I’ll gladly introduce you to some useful tips, if yes, you can give me some feedback and advice by leaving a comment below or contacting me personally. I will be happy to read all you have to say.
Now to the point, recently, our team in Dreamix, a bespoke software company, where we develop software using Java with Spring Boot and Angular, had a similar situation. After long discussions, thorough research and several experiments, we came to the solution, which I am going to share with you in the next minutes.
Starting with the practical part, we are going to play around with the configuration. During the implementation of the project I used the most recent versions of Spring Boot version 2.0.6 and Angular version 7.0.2. I created this demo project , to help you visualize the process during our brief journey. I’ll also try to explain in detail as many of the things as I can.
I. SETUP OF SPRING BOOT APPLICATION
Alright, let’s start with the first step, that is the creation of our Maven project with Spring Initializr. In brief, the Spring Initializr is a web application that can generate Spring Boot project structure. It will give you a basic project structure and either Maven or Gradle build specifications to build your code with. It doesn’t generate any application code, so your programming skills will come in action.
In the screenshot above, we just say “I want you to generate the structure of an Maven project with Java and Spring Boot version 2.0.6”. In the dependencies input field we can choose which packages we need. In our case, the only thing that we need in our project is the “Web” dependency. By choosing this one, we add spring-boot-starter-web package that is the starter for building Web, including RESTful, applications using Spring MVC. This packages also includes Tomcat by adding spring-boot-starter-tomcat package which one is used as default servlet container. By typing “Generate project” that will download us a zip file, containing our application.
Okey, lets extract it and open it. I use IntelliJ IDEA as IDE and here is a screenshot of the current basic structure of the project when you open it.
Okay, now we have to create the folder where we going to put our Angular Application. By following Spring MVC Application structure and as we are making a web application that we would like later to build as WAR file and deploy it, we should create under the “main” directory, a folder with the name “webapp”.
Afterwards this WAR file is read by the maven-war-plugin when compiling a Java webapp. The embedded tomcat used to run the application is using exactly the src/main/webapp folder to look for static resources and serve them.
Just for the test we can make a simple index.html containing some text in the newly created directory and run the application.
Now, with the running application, if we go to https://localhost:8080/ in the browser, we should see rendered the content that we added in our index.html.
II. SETUP OF ANGULAR APPLICATION
Good, now the next step is to generate our Angular 7 application. For this purpose we will use the Angular CLI. Angular CLI is a command line interface for creating Angular apps that basically install and configure all the required dependencies and wire everything together, providing you with boilerplates and therefore, saving your time. In order to use it we need Node.js and npm which is installed with the Node.js on our machine. In my case, the version that I used was v8.12.0. To install Angular CLI you can check the detailed description on how you can do it.
Alright, if we have everything set up, we can continue and generate our front-end part. But before that, lets delete our test webapp and just start with clear structure. Now let’s open a terminal in src/main directory and type “ng new webapp”.
Before the generation of the files, a dialog will appear to ask you about some options like:
Would you like to add Angular routing? (y/N)
Which stylesheet format would you like to use?
For simplicity of our project I choose “no” on the first option and “css” for the second one. When you make your final choice the build of the project is fired up and you’ll see the generation of the
files.
Great, we have generated our Angular 7 application and now we have to make some changes in the structure of our project. First, lets remove the generated node_modules and second, we need to move the config files from webapp to the root directory. We do this, because in the webapp we want to leave only the public files that are going to be served. To make it more clear I made a screenshot of the target structure, also you can check it here.
Alright, now comes the tricky part. By moving the files we have to make some changes in the angular.json and to the .conf files. You can learn more by checking angular.json, tsconfig.app.json, tsconfig.e2e.json, tsconfig.json, tsconfig.spec.json, protractor.conf.js, karma.conf.js to see the changes needed to be applied.
Next step is to make npm install to add the node_modules in the right directory, where package.json is now.
To test if everything is working properly, let’s type ng serve and see if our Angular application will run smoothly.
Now we have to add the following configuration in plugins section of the blog-demo/pom.xml to build the Angular 7 project through Maven.
<plugin> <groupId>com.github.eirslett</groupId> <artifactId>frontend-maven-plugin</artifactId> <version>1.6</version> <configuration> <nodeVersion>v8.9.0</nodeVersion> <npmVersion>6.4.1</npmVersion> </configuration> <executions> <execution> <id>install node and npm</id> <goals> <goal>install-node-and-npm</goal> </goals> </execution> <execution> <id>npm install</id> <goals> <goal>npm</goal> </goals> </execution> <execution> <id>npm run build</id> <goals> <goal>npm</goal> </goals> <phase>generate-resources</phase> <configuration> <arguments>run build:prod</arguments> </configuration> </execution> </executions> </plugin>
In these lines of code, we have specified the node and npm versions and the working directory in the configuration section. In the first execution section, we are telling Maven to install node and npm. This will install within the project which gives us many advantages.
In the next execution sections, we tell Maven to run the ‘npm install’ and ‘npm run build’ commands. ‘npm install’ will install all modules listed as dependencies in package.json in the local node_modules. ‘npm run build’ command will execute the build task described in package.json. By default, angular-cli will write the files in the src/main/web/dist directory, but if you remember we changed the dist directory with target/webapp.
You will also notice, that in the execution of the npm run build task, as argument I pass build:prod which refers to the created new task in package.json – “build:prod”: “ng build –prod –build-optimizer
Let’s run mvn clean install from the project root directory. This will generate a WAR file in the root/target directory. After executing mvn clean install command you should receive BUILD SUCCESS message.
III. EMBEDDING
To get started with this last step, we need to install the following packages:
a. Npm install gulp
b. Npm install gulp-concat
c. Npm install gulp-rename
Now, lets create in the root directory gulpfile.js and paste the following code:
const gulp = require('gulp'); const concat = require('gulp-concat'); const rename = require("gulp-rename"); gulp.task('prepare-js', function () { return gulp.src(['target/webapp/*.js']) .pipe(concat('angular.js')) .pipe(gulp.dest('src/main/webapp/dist')); }); gulp.task('prepare-css', function () { return gulp.src(['target/webapp/styles.*.css']) .pipe(rename('styles.css')) .pipe(gulp.dest('src/main/webapp/dist')); }); gulp.task('build', gulp.parallel('prepare-js', 'prepare-css'));
Here, with the first task prepare-js we say “take all the javascript files from target/webapp, which are created after the production build of our Angular application, concatenate them into one js file with a given name and put it in src/main/webapp/dist directory”.
For the second task prepare-css the logic is the same, but instead of taking the javascript files, we take the only css from target/webapp, renaming it and put it in src/main/webapp/dist directory.”
The next step is to add the execution of this gulpfile.js in the pom.xml. Luckily, the frontend-maven-plugin offers this possibility and the only thing that we should do is add the following execution:
<execution> <id>gulp build</id> <goals> <goal>gulp</goal> </goals> <phase>generate-resources</phase> <configuration> <arguments>build</arguments> </configuration> </execution>
When we run mvn clean install or mvn spring-boot:run we can see in the console log that, after all of the rest configured executions, the gulp execution will also run.To verify that everything is okay, let’s navigate to src/main/webapp/dist and check if the needed files are generated.
We are almost ready, the only thing left to do is to create our embeddable components with Angular Elements.
First, let me say a few words about Angular Elements. We know that not all of the applications on the web are Angular based. Maybe, one day, we would need to reuse our amazing Angular component by embedding it in any Vanilla JavaScript, React, Vue, etc application. Here come, the Angular Elements.
With two words, Angular Elements are normal Angular Components that are packages as Custom Elements. Other great news is that the Angular itself takes care of the binding, the attributes, events and lifecycle hooks between the component to that custom element. The components are self bootstrapping, we can say that they host the Angular Component inside a Custom Element and they export Web Components, which allows us to use them anywhere.
In brief, Angular Elements are Angular Components packaged as Custom Elements, a web standard for defining new HTML elements in a framework-agnostic way, which allows us to create reusable Angular components & widgets usable in any web application independently of the used framework.
Alright, already caught up with Angular Elements, we can continue with the build of our Angular component as a custom element, but first we need to install some packages. The first one is @angular/elements package which exports a createCustomElement() API that provides a bridge from Angular’s component interface and change detection functionality to the built-in DOM API.
npm install @angular/elements --save
We will also need to add a polyfill because Custom Elements v1 aren’t yet fully supported by the browsers.The following command will automatically set up our project with the correct polyfill.
ng add @angular/elements --name=webapp
Okay, now lets create an ordinary Angular component. I created in blog-demo/src/main/webapp/app a component named ‘dashboard’ with its own module. Now, we have to register the created component in the declarations and entryComponents arrays of the DashboardModule and import it in AppModule.
As we mentioned earlier, Angular Elements are self-bootstrapping which means that they will automatically start when they are added to the DOM and automatically destroyed when they are removed from the DOM. In our case, because our embeddable component will bootstraps by itself, we don’t need to register a component to bootstrap in AppModule, that’s why we don’t need more than a manual invoking of ngDoBootstrap. We also can leave only the app.module.ts and delete the rest of the app component files.
I should mention here to evade a problem later, that we also have to import zone.js in main.ts by adding the following line of code:
import 'zone.js';
The brief explanation why we need to do this, is because we are loading a custom element into a Non-Angular-application. By default Angular is using zone.js for change tracking, so you have to make sure that it is loaded into the browser too. When the target app is an angular application, zone.js is there, but if it’s not, we have to provide it manually.
We are almost done, let’s first test if everything is working properly and the application is building and running without any problems. We type ng serve and see what happens.
If everything is okay, let’s build the whole project with mvn clean install. This will fire up the configured by us Angular production build and will generate as output js and css files and pass them through the gulp task that we’ve already created. If everything is working properly, angular.js and styles.css files should be generated in the blog-demo/src/main/webapp/dist directory.
The only thing left to be done is the embedding itself. For this purpose, let’s create a test web portal, where we are going to embed our custom component. Go to the Spring Initializr for the last time and generate one more application, the same as the one before. Now, if we run the two projects we will face the issue that the two application processes are listening on port 8080. So, let’s go in the web-portal application in the web-portal/src/main/resources/application.properties file and insert the following line of code:
server.port = 8081
This will change the port on which the web-portal application is running. Now, the last step is to create an index.html in web-portal/src/main/resources/static where we can add our custom element by its custom selector and by providing the angular.js and the styles.css.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" type="text/css" href="https://localhost:8080/dist/styles.css"> </head> <body> <app-dashboard></app-dashboard> <script src="https://localhost:8080/dist/angular.js"></script> </body> </html>
Good job, finally we should have a working embedded custom component in non-angular application by providing an single js file. Hope you enjoyed this article and also learned something that will come in handy And just to remind you, if you have suggestions or better ideas, please comment below, I’ll be grateful.