NX - Dev Log - Refactoring for AdFrame.io
Refactoring a more monolithic angular application into smaller features made it more manageable to maintain.
Maybe I'm a glutton for punishment, but I wanted to simplify how features were organized within https://adframe.io. On the surface, it's appears simple. There exists only a feature libraries. We purchased a template with a number of different dumb and smart components that I want to separate for reuse in other applications.
My poor design from current state to my future ambition of separating these modules revealed more coupling than I expected, whoops! I guess that happens when you start something only to find better standards and practices later. You learn, grow, and frustrate yourself when things don't work as expected.
Before I decided to refactor, I required guidelines where certain libraries for features, shared components, api libraries, and my ui design library (which was purchased as an Angular Template) would organize in the monorepo.
This being my first crack at a monorepository, before reading the Angular Enterprise Monorepo Patterns and Manfred Steyer's Enterprise Angular, my libraries existed unorganized at the app level.
In the present state, I followed a patterns closely related to Architecting an Angular Application with NgRx. Essentially, I created several monolithic applications inside of the apps
folder and shared code from the libs
for other apps to consume.
Love Web Development?
Angular, Google Cloud, C#, Node, NestJs?
Breaking Down Modules
First order of business, understand how I wanted to break down the features of AdFrame. The feature modules should be easy enough to break down as I'd be able to follow the lazy-loaded routes from the app module level.
I have five different features that I needed to cover.
- Marketing Module
- Landing Page
- Subscription Page
- Privacy
- Google Ads Module
- SKAG Builder
- List of Campaigns
- OAuth Configuration Module
- Configuration OAuth for Facebook, Google OAuth Config
- User Module
- Login
- Payment Module
- Payment Page
While these modules handle smart componentry with specific routes, the template and design language for the site also needed to be broken out. Considering I purchased this as a template, quite a bit of change needed to accomplish this task.
After breaking this out into it's own design module, referencing all of the SASS files proved to be a bit annoying. I researched the solution below, but I haven't committed to it yet.
In the end, I just made relative references where needed. If this were a dependency inside of a node module, I'd be able to make reference in the SASS file for example import '~@ngserveio/scss/ngserve'
.
More important, I needed a base of the domain models and services. So I created a few libraries to support these libraries, simply named domain-ui
and domain-services
.
Free Your Developers
Nx Monorepo Starter alleviates developers from re-inventing deployments on popular Google Cloud Services, standardizes on libraries, and saves time for scaffolding projects.
View on Gumroaddomain-ui
This library represents any domain specific ui models, injection keys shared between services, and any constants values.
Some of the shared models/interfaces included error-dialog.interface.ts
, app-configuration.interface.ts
, and billing-form.interface.ts
.
The constants included items such as a list for the states.ts
used in the billing form. I could move these to a JSON file and request a list of states, but good for the purposes of this refactor.
domain-services
A number of the features I separated out required the same route guards to protect or load certain data as it pertained to the user. Due to certain subscription levels and being dependent on Google Services, I moved services and guards to this library.
In the payment form, users must use their Gmail supported account to connect with their Google Ads Account. Before they're able to choose their subscription, the user must login.
To ease the pain of logging in at the default route as the user enters the app, the guard and subscription level checks if the current user is authorized. This checks at different levels of the routes. Initially this happens when the user enters the marketing pages to redirect the user to the tooling section of the site. This also happens at the tooling level to make sure the user possesses the correct subscription level.
feature-fuse
Previously mentioned, a purchased template provided the branding for the site. It included components and services supporting features for navigation, menus, forms, and different layouts.
Each of the feature modules consume the components, animations, services, and modules.
feature-fuse-layout
Depending on which feature, layout differences exist between the marketing pages and the admin section. The purchased template packaged a few modules to handle horizontal and vertical layouts. Since the purchased template existed as it's own Angular application, it felt appropriate to move these into their own feature library.
Conclusion
Refactoring the application had some loose guidelines that centered mostly around the lazily loaded routes and making them their own feature modules. Sharing the branding componentry posed some issues in regard to references to certain SASS
files.
In the end, this should be more manageable to maintain should our team decide to take on more developers. It will allow them to understand the organization of the code as parts of the application break down into features supporting the tooling, payments, and user services.