Reusable AngularJS form components

Whenever you build software, you want to create reusable and independent components. In the world of user interfaces, those components are often called widgets. Here’s an example how we can achieve this with AngularJS.

An example

Let’s say we have an online shop where a customer can make an order. To do so, the customer must provide some data, in particular an invoice address and a delivery address:

Both, invoice address and delivery address, look exactly the same. They are an example for UI elements which can be bundled in an reusable component.

An AngularJS UI component

With version 1.5 AngularJS introduced the concept of a component. AngularJS’ components are a syntactic short-cut for common directives. So if you use an AngularJS version prior to 1.5, you can achieve the very same behaviour with directives – just with a little bit more code.

An AngularJS component for our address example could look like this:

The my.address.html template would look like this:

If we got this two pieces of code, we can use our brand new component:

Pitfalls

Although our example is still very simple, there are already some pitfalls you need to be aware off.

  • While we have named our component myAddress (camelCase) in our JavaScript code, we need to name it my-address in our HTML code. Otherwise the component will not be recognized.
  • We used the controller-as syntax to name our OrderController just controller. However, inside the template of our component, we must refer to the controller as $ctrl by default.
  • We bind our data to a property which we called address. We can name this property totally as we like, but we need to make sure that we refer to this property from inside of our template: ng-model="$ctrl.address.city"
  • We have used a bi-directional binding using the = operator. So if the model changes inside our component (because the user has entered some text in one of the input fields) it will affect the same object inside our parent controller. Otherwise we wouldn’t be able to access the data.

Accessing the data

Now that we have successfully added our component to our page, we also want to use the data of the delivery and invoice address. Doing this in our parent controller (OrderController) is quite easy:

The result would look like this:

Note that invoiceAddress and deliveryAddress is nested in an object called data because we set-up the component like that:

Form validation

Another pitfall which might be a little bit tricky is form validation. Let’s start with an example again. First, we add form validation to our initial example where we don’t use any components:

This is an example of a very simple form validation build with AngularJS build-in functionality. If the one of the input fields (firstName or lastName) is invalid, a certain CSS class will be applied to mark the field as visually for the user. If we do the very same for our component, we will come to a problem: In order to apply the CSS class with ng-class we need a reference to the form:

Since we want to make an independent component, we don’t want the component to know something about the form in which it is included. This would break encapsulation and we couldn’t reuse the component wherever we want. But we cannot put the component in its own form, because HTML doesn’t allow to nest form. So if we would package our component into its own little form, the browser would ignore and remove it:

bildschirmfoto-2016-10-31-um-09-15-07

The solution is to use ng-form instead of HTML’s form. It will enable AngularJS’ form validation, but will not be touched by the browser. Our component will look like this:

Plunker

You can see the complete working example here:

https://plnkr.co/6UyqVn

More

Best regards,
Thomas

Booting an AngularJS application

Every back-end application has a boot process. Services must be instantiated, connections must be established and configurations must be loaded before the application is ready. How can we do something similar for an AngularJS application?

ng-app

The typical way to start an AngularJS application is to use the ng-app directive on an HTML element (mostly body). Using ng-app Angular will do the bootstrapping of your application automatically. This is perfectly fine for most cases. But if we need more controller of the bootstrap process of Angular, we can do thins manually.

Manual start-up

Instead of using ng-app we can write a piece of JavaScript code to start our application:

Now we are able to start our application manually and to do initialization tasks!

Task 1: Loading data from the backend

A typical start-up task would be to load some initial data (for example a configuration) from the back-end. The code below will make a GET-call to the back-end in order to retrieve some configuration. After the GET-call has finished the application will be started. This makes sure that we have all data up-front.

Task 2: Going to an initial state

Most AngularJS applications have different states. E.g. a view of the user profile (/app/user), a view of the private messages (/app/messages) or simply a home screen (/app/home). Before we start our application, we want to navigate to the correct state. While there are many ways to achieve this, here is an approach how to do it by using an bootstrap script:

The method angular.bootstrap() will return the injector of our application. We can use this injector to access all services of our application (including the routing). In the example below, we assume that we save every state in the session storage of the browser. So we can always look in the session storage to finde the last state where the user has been. If the user now reloads the page in his browser, the AngularJS application will start again. The application will look in the session storage, find the LAST_SAVED_STATE and go to it. Note that we could also use another type of storage (maybe cookies) to persist the state.

Where to put the start-up script to

We usually create one start-script per application, e.g. my.app.bootstrap.js. We code shown above is wrapped into an IIFE and included as the last dependency into our HTML code:

Best regards,
Thomas

More