GrabDuck

Angular 2 Master-Detail Interface – Philippe Martin – Medium

:

A Master-Detail Interface is a very common piece of user interface nowadays. We will see in this article how to create such an interface. We will use a lot of tools provided with Angular 2: routing, lazy loading, guards, resolvers, reactive forms. The source code described in this article is available in the github project feloy/ng2-master-detail and git tags are placed at every important milestone, indicated during the article. Live demos are avaiable for each milestone:
- initial angular-cli project 
- material interface
- empty module 
- routes in place
- resolvers in place
- data displayed
- edition

Initial Project

We start with an angular-cli new project (see git tag ’step0').

Next, we create a simple page layout with a sidenav, a toolbar and a working zone. Thanks to Material, this can be done in a couple of minutes (see git tag ‘step1-material’).

Routing

If you look at the menu entry in this interface, you guess that we will build a master-detail interface to list and view details of dragons. Let’s define the different states of our desired app and the associated routes.

First, when we click on the menu entry, we want to get an interface listing the dragons. This application state will be navigated to with the URL ‘/dragons’.

Then, clicking on a dragon from the list, we want to get the details of a specific dragon and be able to edit these details. In this state, we also want that the list of all the dragons to be still visible, so you can quickly navigate to another dragon. The URL to navigate to this state will be ‘/dragons/:id’, where ‘:id’ is the dragon identifier.

At the end, we want an interface where we can add a new dragon. We still want to view the list of dragons in this state, to help us not creating a dragon twice. This state will be navigated to with the URL ‘/dragons/new’.

To sum up, the three routes we want to define are:

  • ‘/dragons’ to list all the dragons,
  • ‘/dragons/:id’ to view and edit the details of a dragon, still listing all the dragons,
  • ‘/dragons/new’ displaying a form to add a new dragon, still listing all the dragons.

For these states, we will create three components: the first one will be the ‘Dragons Center Component’ which will contain the two others: the ‘Dragons List Component’ and the ‘Dragon Details Component’.

The Center Component needs to be instatiated as soon as the URL begins with ‘/dragons’. The List Component should always be displayed in this Center Component, and the Details Component will be displayed as soon as the URL is completed with an ‘:id’ or ‘new’.

We will create a module containing these three components and the router configuration related to these states, so you can benefit from the lazy loading provided by the router.

Create a module and lazy-load it

  • In the app template, we add a router link to the anchor to navigate to ‘/dragons’ when the menu entry is clicked,
  • in the app template, we also add a router outlet which will embed the component instantiated by the router,
  • we create a configuration for the router with two possibles routes: 1. the empty route which will represent the ‘Home’ state and display a simple Home Component in the router outlet, 2. the ‘/dragons’ route which will load the Dragons Module.
  • we create a Dragons Module, empty for the moment.

At this point (git tag ‘step2-empty-dragons-module’), you can navigate to ‘/dragons’ clicking the menu entry and see that you browser is loading a ‘0.chunk.js’ file. Lazy load works!

Create different routes in Dragons Module

  • We make the Home Component display some welcome info,
  • we add a Dragons Center Component, instantiated every time we navigate to ‘/dragons’ URL or sub-URL,
  • we add two router-outlets in the Dragons Center Component, the primary one for the Dragons List Component, and the ‘details’ named one for the Dragons Details Component.
  • we write the router configuration to instantiate these components when necessary,
  • we implement empty Dragons List Component and Dragons Details Component.

At this point (git tag ‘step3-dragons-routes’), we can see that our three different states are working:

  • enter ‘http://localhost:4200/dragons’ in your browser navigation bar or click the ‘Thirsty Dragons’ in the sidenav: the Dragons Center should be displayed, with the Dragons List Component displaying its sample data.
  • enter ‘http://localhost:4200/dragons/new’ in the navigation bar (we still don’t have created routerLinks to access this URL from the app): you should see the Details Component and the List Component displayed inside the Center Component.
  • enter ‘http://localhost:4200/dragons/1’ and you will see the same things as before.

Resolve and display data

The next step is to configure Resolvers which will load data before the instantiation of the different components. The Center Component does not need data. It is only a placeholder where the list and the details are placed. On the contrary, the List Component needs to know the list of dragons, and the Details Component needs to know the details of a particular dragon, when used with an id (it does not need any data when instantiated with ‘new’).

For this, we will create two resolvers in the Dragon module: the DragonsListResolve and DragonsDetailsResolve, and we tell the router to use these resolvers when necessary.

We begin with two resolvers returning no data but logging info in the console, so we can verify they are called when we load the corresponding components (see git tag ‘step4-resolvers-silent’).

The next step is to create a Dragons Service which will return the list of dragons and return information about a particular dragon. This service will then be used by the resolvers. After this, List and Details components can get data from their resolvers and display the information.

At this point (git tag ‘step5-data-displayed’) we get a read-only master-detail interface. The list of dragons is displayed and when you click on a dragon from the list, the details are displayed and the selected dragon is highlighted in the list.

Editing data

There is still some work to do to edit the data: create a form for editing dragons information or create a new one, and using a CanDeactivate guard to control if one can leave the edition of the form.

For this, we use a reactive form with one control, name, and some buttons to cancel the edition, reset the form, delete a dragon and save or add the edited dragon.

In the router configuration, we define a canDeactivate service which will be responsible of telling the router if the user can leave the form without loosing data. Precisely, the router will call the ‘canDeactivate’ method of this service, passing it the component the user want to deactivate. We create here a generic CanDeactivate service which will call a ‘canDeactivate’ method of the component itself.

When saving or adding a dragon, we want to go back to the list and highlight the just edited or created dragon. To do this, we navigate to the ‘/dragons’ URL passing a matrix parameter with the syntax navigate([‘dragons’, { editid: this.id } ]) which will navigate to an URL like ‘/dragons;editid=1’. In the List Component, wa can get this ‘editid’ value from the router ‘params’ attribute.