How to Make your Angular Responsive Web App Design?

By Saurabh Barot   |   23 April, 2021
How to Make your Angular Responsive Web App Design?

As a front-end developer when you get a website project these days, the client requirements differ from what they used to be when people were fairly new to the concept of making their websites. There has been significant development in websites and tools for creating a better online presence for companies, individuals and everyone else. Earlier making a website was lesser of a hassle as your clients’ expectation would only be to have a functional website that works well on their desktops and laptops. However, with the latest Javascript technologies like Angular, React Vue etc, the demand for front-end becomes more precise in terms of performance as well as userfriendliness. In today’s time we have more devices that are capable of showing web pages. Smartphones and tablets are the most accessible piece of technology that people use on the go almost throughout the day.

Here in this article, we will talk about How to get all device compatible Responsive Angular website that didn’t lag in performance.

Since there are so many different screens and devices capable of showing web pages now, there are better chances of you getting the exposure for your business that helps you grow substantially. However, the hassle of ensuring that your Angular website loads perfectly and fits different screen sizes with aesthetic and functional layouts can be a bit daunting. The perfect solution for this issue is a Angular Responsive Design that all Angular developers have adapted for making smarter and more efficient websites and web apps while providing far more functionality and consistency across various devices.

The solution – Angular Responsive Design

As we discussed above, earlier website solutions were made with the computer screen in mind. All media were big and the buttons were small. Such design looks aesthetic and is easy to click on a bigger screen with a cursor. However, imagine the same layout on a mobile screen and it’s the perfect recipe for disaster. Smaller buttons are difficult to press on such a limited screen size, and accidental touches leading to unwanted page loading ruins the user experience. Further, this would affect users’ interest in exploring your client’s website. To solve this issue, Angular Responsive Design emerged as the need of the hour solution.

To learn how to make Angular Responsive Layout, you first need to understand the Responsive Media Query utilized in CSS.
Responsive Media Query was introduced in CSS3 that allowed content rendering for adapting to different conditions such as different screen resolutions. This helped developers majorly for defining different styles for different devices.

Have a look at the following CSS snippet

header nav {
  display: block;
}

@media screen and (max-width: 768px) {
  header nav {
    display: none;
  }
}
@media screen and (orientation: landscape) {
  header nav {
    position: fixed;
    left: 0;
    width: 20vw;
  }
}

If you run this code, header navigation would be rendered in a block. There is also a condition that dictates if the screen resolution is lesser than 769 pixels (mobile resolution), the navigation bar would be hidden. This is unless the screen is being used in landscape mode, in which case the navigation would remain fixed on the left and take 20 percent of the viewport’s width.

This is the capability of media queries for targeting several parameters like width and height with both min and max prefixes, screen, print, reader, orientation, aspect ratio, availability of JavaScript parser and more.

Hire Angular Developer

The shift to Mobile-First Design

With changing times, computer browsers and websites also started becoming more capable. Now we have the capabilities of making websites that are closer in terms of visuals and performance to that of desktop applications on the browser. The entire philosophy of designing for desktop first and then changing bits and pieces to accommodate the site for mobile screens didn’t cut the slack anymore. Developers realized that they need to redesign from scratch for smaller screens.

This is when the mobile-first approach became a thing. Compared to the desktop-first approach where we would simplify desktop elements to accommodate the mobile view, the philosophy changed to creating web applications that were designed as per the mobile view first and later enriched for the desktop view. Doing this helps Angular Developers to pre-empt small screen problems early on.

There are different challenges and limitations of screen size and accessibility across different devices, due to which it is possible that the desktop and mobile view of the same website can be considerably different. To execute such large differences consistently and efficiently takes a huge toll on DOM to be visually modified or even kept hidden.

A sample for mobile and desktop view

nav {
  display: block;
}
nav ul {
  float: right;
}
nav ul li {
  display: inline-block;
}
@media screen and (max-width: 768px) {
  nav {
    display: flex;
    position: fixed;
    z-index: 999;
    width: 73vw;
    height: 100%;
    background: #fff;
    top: 0;
    left: -100vw;
    float: none;
    transition: .25s ease-in-out;
  }
  nav ul {
    float: none;
    width: 100%;
  }
  nav ul li {
    display: block;
  }
  nav.opened {
    left: 0;
  }
}

As you can see with this code the desktop version (widescreen) navigation is set to horizontal with items inline aligned to the right. In contrast, in mobile view, there are a bunch of styles that position navigation as fixed, with a fixed size and initially out of the screen. The inner list is vertically oriented. If you use the ‘opened’ modifier class, the navigation will slide from the left side.

In the code above, we have two menus: main-nav and secondary-nav. Both the menus have the same navigation items. The page makes use of a gird position to place the menus and items. This is a prime example of the mobile-first approach. If you notice all the items are hidden in the main menu but are visible in the secondary navigation.

.main-header {
  display: grid;
}
.nav-item-1,
.nav-item-2,
.nav-item-3,
.nav-item-4,
.nav-item-5,
.nav-item-6,
.nav-item-7,
.nav-item-8,
.nav-item-9,
.nav-item-10,
.nav-item-11 {
  display: none;
}
.main-nav {
  /* some grid relevant styles */
}
.secondary-nav {
  display: none;
}
.show-secondary .secondary-nav {
  display: flex;
}
.secondary-nav .nav-item-1,
.secondary-nav .nav-item-2,
.secondary-nav .nav-item-3,
.secondary-nav .nav-item-4,
.secondary-nav .nav-item-5,
.secondary-nav .nav-item-6,
.secondary-nav .nav-item-7,
.secondary-nav .nav-item-8,
.secondary-nav .nav-item-9,
.secondary-nav .nav-item-10,
.secondary-nav .nav-item-11 {
  display: flex;
}
@media (min-width: 800px) {
  .nav-item-1,
  .nav-item-2,
  .nav-item-3,
  .nav-item-4,
  .nav-item-5 {
    display: flex;
  }
  .main-nav {
    /* some grid relevant styles */
  }
  .show-secondary .secondary-nav {
    display: block;
  }
  .secondary-nav .nav-item-1,
  .secondary-nav .nav-item-2,
  .secondary-nav .nav-item-3,
  .secondary-nav .nav-item-4,
  .secondary-nav .nav-item-5 {
    display: none;
  }
}

However, once we exceed the 800 pixels’ limit, the first five items in the main menu become visible and the first five items in the secondary menu stay hidden. This technique of splitting menus gives websites more room to experiment with shapes and positions. This allows them to achieve more than the previous example where we were only changing the rendering of menu items which takes a toll on DOM by creating and keeping duplicates.

Understanding the Performance First Mentality

With all the modern abilities and features we can add to our websites, additional costs also start creeping up. The price of keeping elements in the DOM that are visible on some resolutions/screens only can be too expensive. Generally, images and media on desktop require full-quality high-res images but smartphone images can be resized to smaller pixels as they require lesser pixels. Similarly, videos on a desktop website is a great idea as it creates engaging visitor experience but the same won’t perform well on a mobile screen as –

  1. There are good chances your video will not be visible aesthetically.
  2. It would impact the bandwidth and battery considerably.
  3. Page scrolling on mobile phones can lead to accidental or unwanted touches on the video

which can be disruptive for the user experience. The idea is not just to change the aesthetics and UI only but also make the UX more accessible on smaller devices. Other than this you should also focus on removing some heavy elements that aren’t needed or would affect the mobile website’s performance negatively. Desktop websites are opened on fairly stable networks whereas mobile websites are accessed using net services which can fluctuate or be weak at times. Hence you want to focus on delivering performance first and give your users fast experience on mobile to match the speed and responsiveness they are used to with your desktop site. For achieving this it is important to be able to add/remove DOM elements depending on the device.

Get familiar with MediaQuery and matchmedia

Media queries are not only limited to CSS, they are supported in All popular Javascript frameworks. The Window object establishes a function matchMedia that returns a response of type MediaQueryList. MediaQueryList extends EventTarget which means it can have listeners set up and receive events as well.

interface MediaQueryList extends EventTarget {
  matches: boolean; // => true if document matches the passed media query, false if not
  media: string; // => the media query used for the matching
}

A basic example of this can look like

const query = '(orientation: portrait)';
const mediaQueryList = window.matchMedia(query);

// check the match
if (mediaQueryList.matches) {
  /* we are in the portrait mode */
} else {
  /* viewport is in the landscape mode */
}

Enhance this code with listeners

const query = '(orientation: portrait)';
const mediaQueryList = window.matchMedia(query);

// define the callback function for our event listener
function listener(mql: MediaQueryList) {
  if (mql.matches) {
    /* we are in the portrait mode */
  } else {
    /* viewport is in the landscape mode */
  }
}

// run check once
listener(mediaQueryList);

// run check on every subsequent change
mediaQueryList.addEventListener('change', listener);

The Media Service Solution

All even listeners create a stream of events. This allows developers to wrap up the information as an Observable using this service. The consumer who wants these services can subscribe to the stream of media changes and react to them. At the core of such services is ReplaySubject. We pass the values to this core service from MatchMediafunction.

class MediaService {
  private matches = new ReplaySubject<boolean>(1);
  public match$ = this.matches.asObservable();

  constructor(public readonly query: string) {
    // we need to make sure we are in browser
    if (window) {
      const mediaQueryList = window.matchMedia(this.query);
      // here we pass value to our ReplaySubject
      const listener = event => this.matches.next(event.matches);
      // run once and then add listener
      listener(mediaQueryList);
      mediaQueryList.addEventListener('change', listener);
    }
  }
}

This service is now usable for our components. It would help us control the visibility of parts of templates. Whenever media query match changes, the property is Desktop will be changed and hence influence the rendering of the template.

@Component({
  selector: 'foo-bar',
  template: `
    <div *ngIf='isDesktop; else isMobile'>I am visible only on desktop</div>
    <ng-template #isMobile>
      <div>I am visible only on mobile</div>
    </ng-template>
  `
})
class FooBarComponent implements OnInit {
  isDesktop: boolean;
  private mediaService = new MediaService('(min-width: 768px)');

  ngOnInit() {
    this.mediaService.match$.subscribe(value => this.isDesktop = value);
  }
}

In Angular Responsive Design, you can find many uses of MatchServicesuch as

  • Calculations based on media or complex business logic
  • Fetching various resources from the backend

However, if your requirement is just to manipulate the template, the best thing to do is use a dedicated component or directive implementation.

Hire Angular Developers

The Media Component Solution

Another way if you don’t want to subscribe to media changes is to list services directly in the media component.

@Component({
  selector: 'use-media',
  template: '<ng-content *ngIf="isMatch"></ng-content>'
})
class MediaComponent {
  @Input() set query(value: string) {
    // cleanup old listener
    if (this.removeListener) {
      this.removeListener();
    }
    this.setListener(value);
  }
  isMatch = false;
  private removeListener: () => void;

  private setListener(query: string) {
    const mediaQueryList = window.matchMedia(query);
    const listener = event => this.isMatch = event.matches;
    // run once and then add listener
    listener(mediaQueryList);
    mediaQueryList.addEventListener('change', listener);
    // add cleanup listener
    this.removeListener = () => this.removeEventListener('change', listener);
  }
}

If you notice, the first noticeable difference between component and service is removeListener. When the service has the query set as ‘read-only’ the component is able to change the value of the query in runtime which creates a new match media listener. It is best to avoid having more than 2 listeners in a race condition. For this, we ensure that all the previous listeners have been cleared up.
The component would be used to control the template the same way a service does. Now you witness the magic in the template:

@Component({
  selector: 'foo-bar',
  template: `
    <use-media query="(min-width: 768px)">
      I am visible only on desktop
    </use-media>
    <use-media query="(max-width: 767px)">
      I am visible only on mobile
    </use-media>
  `
})
class FooBarComponent { }

Now for better readability and reusability we can extract two media queries – min width:768 px and max-width: 767 px to constants and apply them across our application. Using this method exposes clear intent, however, we still end up with two extra use-media DOM elements dedicated to controlling visibility. Furthermore, the inner content gets processed first before ngIF, since we use media projection.

@Component({ selector: 'child-component' })
class ChildComponent implements OnInit {
  @Input() value: string;

  ngOnInit() {
    console.log(`From child: ${value}`);
  }
}

@Component({
  selector: 'foo-bar',
  template: `
    <use-media query="(min-width: 768px)">
      <child-component value="Desktop"></child-component>
    </use-media>
    <use-media query="(max-width: 767px)">
      <child-component value="Mobile"></child-component>
    </use-media>
  `
})
class FooBarComponent implements OnInit {
  ngOnInit() {
    console.log(`From FooBar`);
  }
}

The Media Directive Solution

We saw the limitations of both Media Services and Media Component. A way to solve these problems is to build a structural directive on top of the same logic. With Media Directives you solve the issues of –

  • The requirement of an extra DOM element
  • Content rendering with all values, and not only on receiving positive values.
@Directive({ selector: '[media]' })
class MediaDirective {
  @Input() set media(query: string) {
    // cleanup old listener
    if (this.removeListener) {
      this.removeListener();
    }
    this.setListener(value);
  }
  private hasView = false;
  private removeListener: () => void;

  constructor(
    private readonly viewContainer: ViewContainerRef,
    private readonly template: TemplateRef<any>
  ) { }

  private setListener(query: string) {
    const mediaQueryList = window.matchMedia(query);
    const listener = event => {
      // create view if true and not created already
      if (event.matches && !this.hasView) {
        this.hasView = true;
        this.viewContainer.createEmbeddedView(this.template);
      }
      // destroy view if false and created
      if (!event.matches && this.hasView) {
        this.hasView = false;
        this.viewContainer.clear();
      }
    };
    // run once and then add listener
    listener(mediaQueryList);
    mediaQueryList.addEventListener('change', listener);
    // add cleanup listener
    this.removeListener = () => this.removeEventListener('change', listener);
  }
}

If we actually see, the only difference between media directive and component is in how the listener callback functions. In the component, we were setting public property is Match, the directive, it is creating or clearing the view as per the value.

@Component({
  selector: 'foo-bar',
  template: `
    <div *media="'(min-width: 768px)'">I am visible only on desktop</div>
    <div *media="'(max-width: 767px)'">I am visible only on mobile</div>
  `
})
class FooBarComponent { }

Wrapping it up!

This blog helped you understand the advantages and features of a utilizing Responsive DOM. You can achieve Responsive in Angular using matchMedia and services, directives and components. Always remember to delete a listener whenever you create a listener in a service/component/directive after that instance has been destroyed. I hope this blog helps you create more responsive websites and app solutions using Angular Resposive.

Saurabh Barot

Saurabh Barot is co-founder and Experienced Chief Technology Officer at Aglowid IT Solutions. His deep industry experience in Angular, React, Ruby on Rails, PHP, Mean Stack and just to name few, has helped the company reach to a new level. He believes that technology is all about exploring new possibilities. Whatever a mind can think (exception can't be ruled out), technology brings it into reality.

Related Posts