What technology stack to choose for drag and drop page builder

2k Views Asked by At

We have different templates built in angular 8 which contain sliders, product listing, and some blocks with images, text etc. I need to create a drag n drop page builder, where user can select a template, add or remove any element and edit elements (styles) e.g product box with image, title, price, button etc. I don't know how to proceed with technology stack which will finally save templates to be served into angular. mostly all components show dynamic data loaded from API

  • canvas is no option
  • do I need to create a json object for components' / elements' position on page ?
  • dynamically creating components with 'ComponentFactoryResolver' can help ? but how to bind data while live editing and after saving when it's served with angular build
  • when a component is created in dropable area, how I would detect that it contains an image or text and I have to open editable options with respect to the element chosen

Any help will be much much appreciated and I'm open for any discussion

1

There are 1 best solutions below

1
On

From your description it sounds like you are trying to build a dashboard system or HTML WYSIWYG editor. this is a fairly broad architectural question, and the approaches will very depending on your project's requirements/timeframe etc... My first recommendation would be to do a search to see if there are any tools out there that already do what you want. i.e. does Ck Editor with customization give you want you need? ...

failing to find one, if you need to roll your own, here are a few lessons learned from my experience with this.

  • either create a custom directive that uses native HTML drag and drop, or use one of the angular libraries out there that provides you with drag and drop (i.e. material drag & drop etc...)
  • Define what your page model will look like.
  • Make use of Observables to ensure individual components remain isolated from each other. yet can use shared services/observables to communicate between each other
  • group as many common 'properties' together and make them part of your base class.

Define what your page model will look like

  • how will you store the representation of what the user does? in HTML, JSON?
  • we had a debate between using HTML over JSON internally, but we ended up with JSON as the team felt it was better suited for them. but you could just as well use HTML as your model. for example:

export interface Widget {
  name: string;
  type: string;
  style: CSSProperties;
  widgets?: Widget[];
}

export interface Text extends Widget {
  textContent: string;
}

export interface Image extends Widget {
  url: string;
}

export interface Table extends Widget {
  source: string;
}

you can always augment your interfaces, but at least spend sufficient time understanding what structure will look like. if done property, adding new components with their own interfaces becomes a breeze. in some ways, this is where using HTML as the model may make more sense.

but with this model in place, you "TextComponent" can expect a data to be provided to it that conforms to the "Text" interface. same for image, table etc...

with this you can tag your component's html with the specific property you wish to use. i.e. in the case of a TextComponent, you would simply use "{{textContent}} in your HTML since it is a property of the interface for this Component.

Drag and Drop

  • Understand if you need to support nested dragging and dropping?
  • depending on the requirements of your project, find a library that has the drop and drop functionality you can use (material drag and drop etc...)
  • OR create an directive that implements that native Drag and Drop functionality of HTML 5

whichever approach you take, all drag and drop have a 'drop' event. this event is triggered when the user stops dragging. most drag and drops tools also allow you to pass data during the 'drag' even so that the 'drop' event handler can process it.

Once you have defined your models, you could pass the model for the current component in the drag event. and on "Drop" you can check the widget.type of the data passed. this will allow you to determine what type of component you are dealing with.

Make use of Observables to ensure individual components remain isolated from each other

  • it will be tempting at the start, when you have only a handful of components, to allow your various components to interact with each other directly. potentially even modifying each other's states and/or data.
  • However, as your list of component grows, and you start to have the need for component interactions, you will start finding problems very quickly. i.e. choosing the date range in Component A needs to also update the data displayed in Component B

we ended up going with a state management solution (NgRX), to help separate state from layout. Since we did it midway through, it was a pain initially, but once done, adding new components became much faster. FYI: You don't need to use NgRx, a service or shared observable that will keep track of your various component's data will do just as well. Using RxJS BehaviorSubject to allow late subscribers to get the current value for example

Component to DataStore example

this allowed for handling scenarios such as

  • Component B has a dependency on Component A
  • When a change is made to Component A, rather than Component A looping through all component to find dependencies, Component B subscribes to the store and changes itself a property of Component A Changes
  • this allowed us to separate the layout logic from the business logic in the application. i.e. whenever the store is updated by Company A, if there is business logic to be applied, then it is handled outside the display component. this helped as our list of components continued to grow.
  • this allowed us to handle multi component event. i.e. if a user holds shift and selects more then one component, and wants to change the background color on all selected components. the selected Components css property for background color are updated in the store (NgRX), and the component (through subscriptions and change detection) automatically get updated on the screen.

ultimately this approach allowed us to decouple the presentation logic of our components from the data logic.

this can get very complicated depending on how complex you need it to be. if this is a project that you simply need to get something up quick and dirty, then of course do what you must. however, if you have the time to dedicate to this, I would suggest spending some quality time with understanding your model as well as what the component interaction will be.

I hope some of this was helpful. best of luck.