circuit

Create an Employee Satisfaction Survey Using Angular and Store Results in a MongoDB Collection

A step-by-step tutorial to building an employee satisfaction survey using Angular and SurveyJS, a free, open-source survey library.


image

Introduction

Employee satisfaction surveys can provide a number of benefits for both employees and employers.

For employees, satisfaction surveys can help them feel heard and valued by their employer, which can increase morale and job satisfaction.

For employers, satisfaction surveys can provide valuable insights into what is working well and what areas need improvement within the organization. This can help employers make informed decisions about how to support their employees and improve the overall work environment.

Satisfied employees are more productive and engaged in their work, which can lead to increased business value generated. Conversely, if you have unhappy people in your organization, you'll tank profitability. It's always beneficial to keep a finger on this pulse, and internal surveys like this can help.

In this article, we'll build such a survey but before we proceed further, let's take a look at the technologies that we'll use:

  • Angular: An application-design framework and development platform for creating single-page applications.
  • SurveyJS: A free and open-source JavaScript form builder (Github link), which makes creating self-hosted forms much simple and easier.
  • MongoDB: A document-oriented database. Classified as a No SQL database. MongoDB uses JSON-like documents with optional schemas.

One might argue that Angular while being the enterprise framework of choice, does not provide a great developer experience for building surveys that scale well with more than a handful of questions. But in this article, you will find an elegant way to design an enterprise survey using Angular --- without a ngIf and ng-template pair for each and every question you want shown conditionally, or a bunch of bindings.

This is where SurveyJS comes in. Using this open-source library, we can facilitate form creation and manipulation in a dynamic manner. We'll explore this in more detail further down the line. For now, let's see which questions we're going to put in our survey.

The questions in this survey are detailed in the table below:

Our employee satisfaction survey will cover a variety of questions to determine whether employees are satisfied in their roles and if not why.

In the next section, we'll see why we're going to use SurveyJS as the tool of choice for building this survey.

Why SurveyJS?

The following reasons show why we should use SurveyJS in conjunction with Angular for our task, instead of designing our forms/surveys by hand with Angular primitives.

  • Create self-hosted surveys. In this way, we can secure our data and keep it stored in our database of choice, rather than using other online services like Google Forms that store our results in Google Sheets files.
  • Easily create complicated surveys which include conditional logic and branching, and make our forms more explicit and adaptable to many cases without having to embed additional JavaScript.
  • Create dynamic forms that can be updated easily. SurveyJS forms are based on a data-first model, so what we would do is just update the JSON form model.
  • Include a variety of input components with validation like multiline inputs, checkboxes, drop downs, matrix questionnaires, file uploads and more, that give us more flexibility in designing forms and surveys.
  • Create multilingual surveys. In this way, we can just create one survey rather than creating many surveys for each language.

In addition to all those benefits, SurveyJS gives us the possibility to use either their beautiful form components, or we can customize everything with CSS, and make these components more personalized and fit with our branding theme.

Now, it's time to start building. Let's get into it.

Building the Survey

Step 1: Set up the Angular Project

First, we should ensure that we have installed the latest version of Angular CLI.

npm install -g @angular/cli

We can learn more about Angular CLI and find more details about the installation here.

After installing Angular CLI, we can create our Angular app using the ng command as shown below:

ng new employee-satisfaction-survey-app

This command will generate our app with the following structure:

Step 2: Install and Configure SurveyJS

Now that we have created our app, let's install the SurveyJS library (for more details about the installation you can refer to the official documentation) with the code below:

npm install survey-angular-ui --- save

Once SurveyJS is installed, we'll start the configuration:

Step 2.1: Reference SurveyJS style sheets UI in angular.json

SurveyJS come with 2 different style sheets Modern and DefaultV2 illustrated in the picture below:

SurveyJS UI themes

{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"employee-satisfaction-survey-app": {
"projectType": "application",
"schematics": {},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
// ...
"styles": [
"src/styles.css",
"node_modules/survey-core/defaultV2.min.css",
// Modern theme
"node_modules/survey-core/modern.min.css"
],
"scripts": []
},
"configurations": {
// ...
},
"defaultConfiguration": "production"
},
"serve": {
// ...
},
"extract-i18n": {
// ...
},
"test": {
// ...
}

Step 2.2: Apply the referenced theme in your app component

For that, we will use StyleManager.applyTheme(theme-name); method and pass the theme name as parameter ("modern" or "defaultV2").

import { StylesManager } from "survey-core";

StylesManager.applyTheme("modern");

Step 2.3 Create the survey model

According to SurveyJS documentation:

A model describes the layout and contents of your survey. The simplest survey model contains one or several questions without layout modifications.

A model consists of a JSON object that makes a declaration of our questions and their types. This JSON object is called a schema.

For example, this simple schema contains one question declared in the elements array.

const surveyJson = {
  elements: [
    // every element contains a question
    {
      name: "Name", // field name
      title: "Enter your full name:", // field title
      type: "text", // field type
    },
  ],
};

Then let's instantiate a model by passing schema to the Model constructor as shown in the code below:

import { Component, OnInit } from "@angular/core";
import { Model, StylesManager } from "survey-core";

StylesManager.applyTheme("modern");

const surveyJson = {
  elements: [
    {
      name: "Name",
      title: "Enter your full name:",
      type: "text",
    },
  ],
};

@Component({
  selector: "app-root",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"],
})
export class AppComponent implements OnInit {
  title = "employee-satisfaction-survey-app";
  surveyModel: Model | undefined;

  ngOnInit() {
    const survey = new Model(surveyJson);
    this.surveyModel = survey;
  }
}

Step 2.4: Add SurveyJS module to app.module.ts

In order to render our survey, we need to import the SurveyJS Form Library module in app.module.ts.

import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { AppRoutingModule } from "./app-routing.module";
import { AppComponent } from "./app.component";
import { SurveyModule } from "survey-angular-ui";

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, AppRoutingModule, SurveyModule],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

Then, we will just add a <survey> element to our component template and pass the model instance we created above as model attribute:

<survey [model]="surveyModel"></survey>

Step 2.5: Run the app

Finally, let's run our app:

npm run start

If you've followed these steps, you will have the following output:

Step 3: Create the Employee Satisfaction Survey

Now let's concentrate on the survey schema, and see how to use different component types according to every question of our satisfaction survey.

If we take look at our satisfaction survey, we see that we need 4 types of components:

  • Simple text field: text
  • Simple text area field: comment
  • Radio group field: radiogroup
  • Matrix of radio groups: matrix

For more details about the different components provided by SurveyJS, you can take a look at the Form Library API.

After fixing what components we need, let's update our JSON schema:

const surveyJson = {
  title: "Your opinion counts!",
  description: "Please express what you think about your job?",
  pages: [
    {
      elements: [
        {
          name: "Name",
          title: "Enter your full name:",
          type: "text",
        },
        {
          name: "OverallSatisfaction",
          title:
            "How would you describe your overall level of job satisfaction?",
          type: "radiogroup",
          isRequired: true,
          choices: [
            "Very satisfied",
            "Satisfied",
            "Neutral",
            "Dissatisfied",
            "Very dissatisfied",
          ],
        },
        {
          name: "Quality",
          title: "How would you rate the following?",
          type: "matrix",
          isRequired: true,
          columns: [
            {
              value: 1,
              text: "Very poor",
            },
            {
              value: 2,
              text: "Poor",
            },
            {
              value: 3,
              text: "Average",
            },
            {
              value: 4,
              text: "Good",
            },
            {
              value: 5,
              text: "Excellent",
            },
          ],
          rows: [
            {
              value: "Salary",
              text: "Salary",
            },
            {
              value: "OverallBenefits",
              text: "Overall benefits",
            },
            {
              value: "HealthBenefits",
              text: "Health benefits",
            },
            {
              value: "PhysicalWorkEnvironment",
              text: "Physical work environment",
            },
            {
              value: "TrainingOpportunities",
              text: "Training opportunities",
            },
            {
              value: "WorkingTimeFlexibility",
              text: "Working time flexibility",
            },
          ],
        },
        {
          name: "ValuedAtWork",
          title: "Do you feel valued at work?",
          type: "radiogroup",
          isRequired: true,
          choices: ["Yes", "No"],
        },
        {
          name: "Explanation",
          title: "If no please explain",

          visibleIf: "{ValuedAtWork}='No'",
          type: "comment",
        },
        {
          name: "Feedback",
          title: "Please Provide Any Additional Feedback",
          type: "comment",
        },
      ],
    },
  ],
};

Note:

  • To mark a question as required just add isRequired property.
  • To set the type of the question we use type property.
  • To show some fields when the condition is satisfied, we use visibleIf property.

Then, save and refresh our app to see the modification in the browser:

In order to handle the survey completion after a user completes it, we should use the onComplete event handler. For example, let's just log (print) the results in the browser console.

Add the following code to your app.component.ts:

import { Component, OnInit } from "@angular/core";
import { Model, StylesManager } from "survey-core";

// const SURVEY_ID = 1;

StylesManager.applyTheme("modern");

const surveyJson = {
  // ...
};

@Component({
  selector: "app-root",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"],
})
export class AppComponent implements OnInit {
  title = "My First Survey";
  surveyModel: Model;
  logSurveyResults(sender) {
    console.log(sender.data);
  }
  ngOnInit() {
    const survey = new Model(surveyJson);
    survey.onComplete.add(this.logSurveyResults);
    this.surveyModel = survey;
  }
}

Save, refresh, and open the browser console to see the results displayed there after completing the survey:

Now that we've built our survey, we will next see how to configure a database to store the survey data.

Step 4: Set up the MongoDB Database

In real-world applications, we don't log results in the console but we will use these results in other web services. In fact, we should send the results to a server where they will be stored in a database and processed by the system.

To make our example more complete, we will create a simple API using Express and MongoDB. This API will receive the results of our satisfaction survey and store them in the database.

Let's start by ensuring that we have installed node, npm and MongoDB (you can download it from here).

$ node -v
$ npm -v
$ mongo -v

Then, we create our Node.js application and install the required packages. For that, we will use:

  • Express: A minimal Node.js web application framework that provides a set of features for web and mobile applications.
  • Nodemon: A tool that helps develop Node.js-based applications by automatically restarting the node application when file changes in the directory are detected.
  • Mongoose: A MongoDB object modelling for Node.js.
$ mkdir employee-satisfaction-survey-api
$ cd employee-satisfaction-survey-api
$ npm init
$ npm install express nodemon mongoose --- save

After that, we will create the following application structure:

API project structure

  • package.json:
{
  "name": "employee-satisfaction-survey-api",
  "version": "1.0.0",
  "description": "Employee satisfaction survey api",
  "main": "index.js",
  "scripts": {
    "start": "nodemon index.js"
  },
  "author": "Sihem BOUHENNICHE",
  "license": "ISC",
  "dependencies": {
    "express": "⁴.18.2",
    "mongoose": "⁶.7.4",
    "nodemon": "².0.20"
  }
}
  • index.js: The entry point of our API, it contains the Express app and routes declaration:
// Import dependencies
const express = require("express");
const database = require("./initdb.utils");

// Create app instance
const app = express();

// Define JSON as return type
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Configure headers
app.use((req, res, next) => {
  res.setHeader("Access-Control-Allow-Origin", "*");
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
  res.setHeader("Access-Control-Allow-Headers", "Content-Type");
  res.setHeader("Access-Control-Allow-Credentials", true);
  next();
});

// DEFINE ROUTES
app.use("/hello", (req, res) => {
  res.status(200).json({ message: "Hello world !" });
});
app.use("/surveys", require("./survey.routes"));

// This route will handle all the requests that are
// not handled by any other route handler. In
// this handler we will redirect the user to
// an error page with NOT FOUND message and status
// code as 404 (HTTP status code for NOT found)
app.all("*", (req, res) => {
  res.status(404).json({ error: "End point not found" });
});

// Handle database error
database.on("error", (error) => {
  console.log(
    "Connection error: --- --- --- --- --- --- --- --- --- --- --- --- --- "
  );
  console.log(error);
  console.log(
    " --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- "
  );
});

// Start app after connecting to Database
database.once("connected", () => {
  console.log("Database Connected");

  const PORT = 3001;
  app.listen(PORT, () => console.log("Server ready at 3001"));
});
  • initdb.js: Contains connection configuration with our database
const mongoose = require("mongoose");

const MONGO_HOST = "localhost";
const MONGO_PORT = 27017;
const MONGO_DB_NAME = "surveydb";
const MONGO_URL = `mongodb://${MONGO_HOST}:${MONGO_PORT}/${MONGO_DB_NAME}`;
const connectOptions = {
  useNewUrlParser: true,
};

mongoose.connect(MONGO_URL, connectOptions);
const database = mongoose.connection;

module.exports = database;
  • survey.model.js: Contains our survey model schema
const mongoose = require("mongoose");

const SurveySchema = mongoose.Schema(
  {
    Name: String,
    OverallSatisfaction: String,
    Quality: Array,
    ValuedAtWork: String,
    Explanation: String,
    Feedback: String,
  },
  {
    timestamps: true,
    strict: false,
  }
);

module.exports = mongoose.model("Survey", SurveySchema);
  • survey.controller.js: Contains our web services, in our example, we will create only two services one for saving the results of the survey and another for retrieving results.
const Survey = require("./survey.model");

// Create and Save a new Survey
exports.create = (req, res) => {
  const surveyData = req.body;

  // Create a Post
  const survey = new Survey(surveyData);

  // Save Post in the database
  survey
    .save()
    .then((data) => {
      res.send(data);
    })
    .catch((err) => {
      res.status(500).send({
        message:
          err.message || "Some error occurred while creating the survey.",
      });
    });
};

// Find all surveys
exports.findAll = (req, res) => {
  Survey.find({})
    .then((data) => {
      res.send(data);
    })
    .catch((err) => {
      res.status(500).send({
        message: err.message || "Some error occurred while retrieving data.",
      });
    });
};
  • survey.routes.js: Contains the routes of our API
const surveyController = require("./survey.controller");
const router = require("express").Router();

//CRUD
router.post("/", surveyController.create).get("/", surveyController.findAll);

module.exports = router;

Now let's start our API by running the following command:

npm run start

If you go to http://localhost:3001/hello you will see the message "hello world!"

If you try http://localhost:3001/surveys you will get an empty response because there is no survey saved yet.

In the next section, we will save the results by using our API.

Step 5: Store Results in MongoDB Collection

Now that we have our API, let's update the app.component.ts file, and instead of just logging the survey results, we will send them by an HTTP request to our API.

import { Component, OnInit } from "@angular/core";
import { Model, StylesManager } from "survey-core";

// const SURVEY_ID = 1;

StylesManager.applyTheme("modern");

const surveyJson = {
  // ...
};

@Component({
  selector: "app-root",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"],
})
export class AppComponent implements OnInit {
  title = "My First Survey";
  surveyModel: Model;
  logSurveyResults(sender) {
    console.log(sender.data);
  }
  saveSurveyResults(sender) {
    const request = new XMLHttpRequest();
    const url = "<http://localhost:3001/surveys>";
    request.open("POST", url);
    request.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
    request.send(JSON.stringify(sender.data));
  }
  ngOnInit() {
    const survey = new Model(surveyJson);
    survey.onComplete.add(this.saveSurveyResults);
    this.surveyModel = survey;
  }
}

After that and after filling all the fields if we click on complete the survey button, data will be sent to the API:

And, if you try now to go to http://localhost:3001/surveys, you will find the results:

Conclusion

With that, we come to the end of the tutorial. You can do a lot of other complex things using the SurveyJS Form Library. It's completely free and open source so feel free to take it for a spin.

And for more features besides the free Form Library, check out the pricing section.

The complete code for this project can be found here:

  • Link to Angular app GitHub repository:

GitHub - EmployeeSatisfactionSurvey/employee-satisfaction-survey-app

  • Link to Express API GitHub repository:

GitHub - EmployeeSatisfactionSurvey/employee-satisfaction-survey-api




Continue Learning