Issue
I want to switch between a desktop and mobile template depending on the screen width to ensure my application is responsive. I am trying to do the following:
@Component({
selector: 'app-root',
templateUrl: "./" + (window.innerWidth < 768) ? "app.component.html" : "app.component.mobile.html",
styleUrls: ['./app.component.css']
})
However, this is not working. Instead of the template loading, the string, "app.component.html"
, appears on the screen instead.
What is even more interesting is that, if I use the following:
@Component({
selector: 'app-root',
templateUrl: "./" + (false) ? "app.component.html" : "app.component.mobile.html",
styleUrls: ['./app.component.css']
})
The page still only shows the string "app.component.html"
.
Is there no support for using conditional statements as the value for the templateUrl
field in the @Component
decorator?
If not, what is an alternative solution I could use to achieve this level of responsiveness that is still modular and follows best practices?
Update: I got this to work by using ng serve --aot
instead of just ng serve
. However, I decided to not go through with this idea because it does not switch templates as the window resizes.
Solution
your approach here is off. Templates are assigned to components at build time, not runtime and Angular will build all of it’s components, well before any window exists with a width to write a conditional off of. You need to set up logic to tell your app when to show which component.
The “best practice” (IMO) here, if you can’t do responsive design (note: it’s important to understand your audience and how the app will be used. I always try to opt for a mobile first responsive design, but also recognize this isn’t always appropriate), is to have 2 components, and use route guards to enforce the correct component.
@Injectable()
export class MobileGuard implements CanActivate {
constructor(
private router: Router
) { }
canActivate() {
if (window.innerWidth >= 768) {
this.router.navigate(['/']);
return false;
}
return true;
}
}
@Injectable()
export class DesktopGuard implements CanActivate {
constructor(
private router: Router
) { }
canActivate() {
if (window.innerWidth < 768) {
this.router.navigate(['/m/']);
return false;
}
return true;
}
}
then define your routing structure somewhat like this:
const routes: Routes = [
{
path: '',
component: AppComponent,
canActivate: [DesktopGuard],
children: [... desktop routes ...]
},
{
path: 'm',
component: MobileAppComponent,
canActivate: [MobileGuard],
children: [... mobile routes ...]
}
]
as for the components themselves, your mobile component just extends your non mobile component and has a different template / styles associated.
an alternative approach here is to do something like this:
export class WrapperAppComponent {
isMobile: boolean;
constructor() {
this.isMobile = window.innerWidth < 768;
}
}
with template like:
<desktop-app *ngIf="!isMobile"></desktop-app>
<mobile-app *ngIf="isMobile>></mobile-app>
but this isn’t a very scalable approach and has the same “duplication” of components.