Working on an emergency application (with live data) using .net and angular. The backend is .net with SQL server and SignalR on Azure. This application is sales driven product, we came out first with desktop version and then mobile friendly version due to way the market and 90-80% of users are on desktop view.
This article describes how we architect our application do be responsive. Basically, going from an already existing desktop web application to responsive application. When referring to word “mobile” in article, it is not a mobile native application or Progressive Web App, it is referring to responsive application on different tables and mobile devices.
Architecture and Setup
We started by in our angular application under root folder “src”. We created a folder called “mobile”. We have the “app” folder for main desktop view, it can be renamed to “desktop”. This gave us a whole new place to have our mobile code and not been in the same folders with all other code, it is more organized, clean and separated from all other code.
In app router module “AppRoutingModule”, when application get loaded. We load an array of all paths of the application, from “app” and “mobile” folders. When application knows the size of window “window.innerWidth” it will modify the path array and rests the route configuration by calling "this.route.resetConfig()". Here we also listen to “window:resize” in the main component of the application for window resizing, and the router path gets modified and then router configuration gets reloaded. So, user can go from desktop to mobile view and vice and versa.
const desktopPreloadModules = [
{
path: 'dashboard', loadChildren: () =>
import('app/modules/dashboard/dashboard.module')
.then(mod => mod.DashboardModule)
},
{
path: 'debug', loadChildren: () =>
import('app/modules/debug/debug.module')
.then(mod => mod.DebugModule)
}
];
const mobilePreloadModules = [
{
path: 'dashboard', loadChildren: () =>
import('mobile/modules/dashboard/dashboard.mobile.module')
.then(mod => mod.DashboardMobileModule)
},
{
path: 'debug', loadChildren: () =>
import('mobile/modules/debug/debug.mobile.module')
.then(mod => mod.DebugMobileModule)
}
];
const appRoutes: Routes = [
{ path: 'login', component: LoginComponent },
{
path: 'main',
component: MainComponent,
canActivate: [AuthGuard],
resolve: [AppDataResolver],
children: [...desktopPreloadModules, ...mobilePreloadModules]
},
{ path: '', redirectTo: '/main', pathMatch: 'full' },
{ path: '**', component: LoginComponent }
];
@NgModule({
imports: [RouterModule.forRoot(appRoutes, { preloadingStrategy: SelectiveStrategy, enableTracing: false, scrollPositionRestoration: 'enabled' })],
providers: [SelectiveStrategy],
exports: [RouterModule]
})
export class AppRoutingModule {
constructor(…) { … }
}
There is also CSS files that styling changes depending on the window size, we used media query for that, by calling:
@media only screen and (max-width: ####px)
In our application we also have some dynamic dialog boxes, we also override those components by reloading them either mobile view or desktop view.
After route is change the all components (that is in the current view) needs to be reloaded. Here, we solve this by simply navigating to and empty path this basically reloads all current component in the DOM (it is not refresh from server.)
Mobile Implementation
In the mobile for with similar path tree as desktop “app” folder, by using OOP inheritance, polymorphism and overriding methods. We could access all of the functionalities from desktop services and components and reused the code, by extending from same components. Following the open/closed principle. For example:
@Component({
selector: 'my-mobile-component',
templateUrl: './my.mobile.component.html'
})
export class MyMobileComponent extends MyDesktopComponent { }
In case mobile functionality is different than desktop, we override the desktop method in the mobile folder.
In our application which is medium to advanced application complexity. There is only one place that it is checking if application is mobile or desktop and that is in the “AppRoutingModule” there no “if (mobile) do this else do that” outside the router. We follow and stick with this pattern due to the fact that the application could be very messy. If we have a mobile specific component or function with extending the class from desktop and override or add mobile specific functions or view. Following the single responsibility principle.
Pros
- Clean and organized code.
- Easy to implement the mobile view application.
- Reuse of the code, this saves us lots of time to impalement and test. The desktop code is already in production and been used by users, we know if works. So, we are confident that code works.
- Follows the single responsibility principle
- Follows the open/closed principle
Cons
- If we want to override a desktop behavior or functionality we will need to extend it in mobile. This can give a little complexity to some deeper functionalities. However, doable by refactoring and using SOLID principles.
- Can easily get a messy code if we breaking the pattern if someone writes “if (mobile) do this else do that” way or some other way.
Top comments (9)
Hi, I'm creating something similar, only we already have mobile ready application and are expanding it with different views for Desktop with completely different design and almost identical functionality.
What I did is (same like you) define different set of routes with the same paths but they are loading different page modules and our Desktop page components are inheriting mobile ones so we don't have to duplicate code for business logic, AND THIS WORKS!
My problem is all of our pages are lazy loaded and use feature modules and I can't access in desktop page something loaded in the mobile page module. For example pipes!
Translate pipes don't work inside new pages. Tried importing translate module in the new Desktop page module, tried importing mobile page module in it too. Nothing works.
Can you please give more info about how you are managing that?
I have pipes and these sort of objects/components in shared or core module and import them in.
Core is for anything that is singletons and rest are in share module. Hope that helps, sorry for late reply.
Hey Anoush,
You've chosen a really nice topic with a real problem, thanks in advance for that. Taking all the details you've shared, the most crucial is the one where you override the path, whether it's mobile or desktop routes:
When application knows the size of window “window.innerWidth” it will modify the path array
but basically you haven't specified the code which does that :)I would aprreciate if you share the implementation.
Cheers.
Sorry for the late responds to your question.
Basically, after the window Size has been determined you modify the route array and then you have to call this.router.resetConfig(newModifiedRoutes).
example (hope this will help clarifying it):
This Idea is good. But I did'nt understand Properly. Where you will do the window width function for call Dektopmodule and Mobilemodules
We listen to “window:resize” in the main component of the application for window resizing, and the router path gets modified and then router configuration gets reloaded. So, user can go from desktop to mobile view and vice and versa.
The article explains it, hope that helps.
1 when app is loading depends on window size
2 when user chnaging size of window
Media query
And listening to window:resize
Hey Anoush @anoushnet ,
Is there any chance the desktop specific components are loading onto mobile and vice-versa in server-side rendering ?
No, we have never has any issues with that. This code went live 6 month ago.