Build an Enterprise Scalable Dashboard using Angular — Part 2

Landon Buttars
ITNEXT
Published in
9 min readJul 30, 2019

--

In part 1 we went over how to take advantage of dynamic component rendering to separate the dashboards content from the dashboard itself. In this part, we will go over how to add/remove items from the tracks, how to make services calls for individual cards on the dashboard, and how to implement drag and drop while maintaining state.

Adding and Removing Items

Before we begin

Best practice in Angular is to move shared state into services that can be consumed across multiple components in your application. Because we declared our tracks in the dashboard.component.ts it is inaccessible to any other component directly, including the dashboard content. To be able to add or remove a card from anywhere other than the dashboard component itself, we will need to move the tracks variable into a service.

Before we continue it is important that you understand the concept of observable services. Observable services are regular services that return data, usually state, in the form of observables. If you are new to the concept of observable services and the advantages/disadvantages they come with, I recommend you read this article that covers the topic more in depth. For now, this will allow us to share the dashboard state which will be useful soon.

First, generate the dashboard service. Then copy the tracks array from the dashboard.component.ts into your service. I also renamed the variable to defaultState.

dashboard.service.ts

Second, create a subject that we can give data. A subject is like an EventEmitter with the ability to have multiple observers. In our case, we want to use a BehaviorSubject. A BehaviorSubject will emit the last given value to new listeners. We can create an observable from our subject by calling this.subject.asObservable(), I named that variable tracks$.

dashboard.service.ts

Finally, retrieve the tracks from the service in the dashboard component.

dashboard.component.ts

Now we can begin to add and remove tracks from the dashboard.

Removing Items

To remove items create a method on the dashboard service, I named it removeItem. Removed item will take an item as a parameter and attempt to remove the item from the tracks state and push the changes to the subject.

To get the state from a subject call subject.getValue(). This will give us a snapshot of the subject at that point in time. Now that we have the snapshot we can begin to manipulate it.

dashboard.service.ts

Remove the item by looping over each track in the state array. Then for each track loop over the items in the track. If the item in the loop matches the item provided as a parameter we then want to splice it out of the track. Once that is complete we want to pass the new state to the subject again by calling this.subject.next().

dashboard.service.ts

Adding Items

Create a method called addItem which will take an item and add it to one of the tracks. I chose to push the new item to the track with the least amount of items. Optional: you may want to validate that the item does not exist on either of the tracks before adding the item to prevent duplicates.

dashboard.service.ts

Before we can use the addItem/removeItem methods, we must go over how to make individual services calls from dashboard contents which is covered in the next section.

Calling Services and Passing State Into Dashboard Content

Decoupling content state from the dashboard is extremely powerful. It allows us to independently create dashboard content, represented as cards, without having any content specific logic in the dashboard component. And if you are working in an enterprise environment, decoupling the dashboard from the contents’ state allows multiple orgs to create, manage, and maintain dashboard content they are responsible for without needing to touch the dashboard component itself. For example, the finance department might want to create a finance card to show financy things. When they do, they will not have to touch anything but the dashboard-cards.enum.ts and the dashboard-cards.ts.

For this example, go ahead and create another service called HelloService. This service is going to hold a list of names and which will be retrievable by the dashboard content. Also, add an Input() name on the HelloWorldComponent and display it in the component template.

hello-world.service.ts

hello-world.component.ts

hello-world.component.html

If you are following along with the tutorial, you will have created a HelloWorldContainer component in the first part. The container should call the service, retrieve the info it needs, and pass it to the content through the child component’s Input() properties.

Due to the way we are mapping containers to track items, there has to be a container for each unique item on the dashboard. This is because containers have to call services to retrieve data and depending on how you structure your components, this may limit the reusability of any particular container.

To retrieve different data, I’m going to generate a second container hello-world-two.container.ts and add it to the entryComponents which is the same process we used for hello-world.container.ts.

Each container is going to retrieve a name from the HelloService and pass it into the HelloWorldComponent. The only difference is the name each container retrieves.

hello-world.container.ts and hello-world-two.container.ts

Now go into dashboard-cards.ts and dashboard-cards.enum.ts and add another entry, HELLO_WORLD_TWO.

dashboard-cards.ts

dashboards-cards.enum.ts

In the dashboard.service.ts change the second item’s component to ‘HELLO_WORLD_TWO’.

Save the changes, and you can see that there are now two cards with different content on the dashboard.

Hurray!!! Now we’ve built a decoupled static dashboard. In the next section, we will make the content draggable.

Implementing Drag and Drop

Drag and drop is generally a quite difficult yet mundane task. Luckily ng2-dragula makes this extremely easy. Go ahead and install it. yarn add ng2-dragula. There is a list of install instructions you will need to follow in the dragula repo. Dragula will also need to be imported into the dashboard module.

To implement drag and drop all we have to do is add the dragula and the dragulaModel directives along with the dragulaModelChanged event. The dragula directive simply identifies which group the given track should belong to. In our case, they both belong to the dashboard group. The dragulaModel directive specifies which items the dragula track should contain. This will be synced with our tracks data. There is also some css we will need to ensure that when the tracks are empty, the track does not shrink to 0 size, so we can drag stuff back.

dashboard.component.html

dashboard.component.scss

With these additions, the cards are now draggable. Awesome! Now off to store some state.

Storing State

To persist changes when dragging and dropping we have to do a few things.

First, we must take the changed state, emitted by the dragulaModelChanged event, and set it in the service. We will need a method to take the whole state of the tracks and push them into the subject. Add a method named setState onto the DashboardService. setState requires an array of tracks and sets the state accordingly.

dashboard.service.ts

We will call this in the dashboard component when the tracks have changed.

The dragulaModelChanged will emit an event that returns the array passed into dragulaModel with the appropriate changes after a drag and drop. The catch is that it only returns the individual items of the particular track not the array of tracks. That means we will have to know which track’s items are being changed. To do that we can easily add a reference to the index on the tracks ngFor loop in the dashboard template. That will give us a reference to which track the dragulaModelChanged is referring to.

Create a method called changed on the dashboard component. The changed method will accept a track and a trackIndex. Once changed is called it will retrieve the current state and apply the changes given to us from the event. Once you have a reference to the modified state, pass it to setState to update the service.

dashboard.component.ts

We can now add the dragulaModelChanged event to our div and call changed.

dashboard.component.html

Now here is where things get weird. Because we are updating the object that dictates what needs to be rendered, we must tell the change detection to check for changes and only then do we reload the content. If you do it in the wrong order the loadContents method will not have the correct set of ng-templates to render the components on. The best place to add the detectChanges call is in the tracks subscription. This subscription is called whenever the subject in the dashboard service gets a new value, which in our case will be when cards get dragged and dropped.

dashboard.component.ts

Yay, the state is consistent while we drag!! All there is left to do is save the state.

Saving Dashboard State

Saving dashboard state is extremely simple. All we have to do is store the dashboard tracks in local storage (or anywhere else you want it) and load it when we startup. We will also implement a default configuration in case it is the users first time visiting the dashboard.

Create a method on the dashboard service called loadFromLocalStorage. This will pull the dashboard state and update the service.

dashboard.service.ts

Create another method; saveTracksToStorage. This does as the name says and saves the tracks to localstorage as a string.

dashboard.service.ts

To save future updates to the tracks subscribe to tracks$ in the dashboard service constructor. Before that happens we need to make sure and call this.loadTracksFromStorage so we get the most recently saved state.

dashboard.service.ts

And boom!

You have a dynamic draggable dashboard that persists it’s state completely independent from the content!

Source Code: https://stackblitz.com/github/buttars/enterprise-dynamic-dashboard/tree/part-2

I hope you enjoyed this two part series. If you did I would really appreciate it if you gave it a few claps. (50 is the max if you are feeling extra generous).

I will be answering as many questions as I can in the comments below so please feel free to ask away.

--

--

Full-Stack Developer specializing in Angular, Node.js, AWS, and Nx. Passionate about creating efficient, scalable, and user-friendly web applications.