• About Morris Development
  • A Focus for Cloud Efficiency
    • Microsoft Azure
    • Amazon Web Services
  • What our Clients Say
  • Our Products
    • PreschoolDB
    • WebinarDB
  • Contact Us

Morris Development

Custom System Development and Integration

January 13, 2023

Responsive Views in Angular without CSS Media Settings

I admit it. I’m terrible at CSS. I’m especially bad at dealing with responsive issues. But I found a solution that I’m simply more comfortable with.

Angular CDK.

I’m not going to go on about installing things from scratch. Rather, I’m going to explain how it works and how I’ve found the best way to use it may be.

The primary tool is called a BreakpointObserver. This is simply an observable that tracks the browser size and orientation. It’s cool, but it’s also a pain in the ass. It doesn’t just return the answer, it “matches”. So, you give it a layout of height and width and it tells you if that height and width “matches”. Well, that’s cool, but do you want to run a bunch of javascript code every time you load a component? Do you want to try to guess every size and see “so… is this it? no, how about this? no, how about…..”. No, you don’t. Here’s how to do it.

You make a function that uses some built in display structures, try to match them from mobile to big browser, then return the one that fits in an observable.

breakpoint$:Observable<{size:string}>
 
constructor(private breakpointObserver: BreakpointObserver) { this.breakpoint$ = this.breakpoint(); }
 
private breakpoint() {
  console.log("BREAKPOINT INITIALIZED")
 
  let lrg = this.breakpointObserver.observe([Breakpoints.Web]).pipe(
    map(({ matches }) => { if (matches) { return { size: "web" } } else { return { size: "unknown" } } }),
    tap(obj => {
      if (obj.size == "web" && !environment.production) console.log('screen is sized for web')
    }));
 
  let sml = this.breakpointObserver.observe([Breakpoints.Tablet]).pipe(
    map(({ matches }) => { if (matches) { return { size: "tablet" } } else { return { size: "unknown" } } }),
    tap(obj => {
      if (obj.size == "tablet" && !environment.production) console.log(obj)
    })
  )
  let mobile = this.breakpointObserver.observe([Breakpoints.Handset]).pipe(
    map(({ matches }) => { if (matches) { return { size: "handset" } } else { return { size: "unknown" } } }),
    tap(obj => {
      if (obj.size == "handset" && !environment.production) console.log('handset')
    })
  )
 
  return of(lrg, sml, mobile).pipe(mergeAll(), filter(rets => rets.size != 'unknown'))
}


So, here’s what’s happening:
1. I define a public variable for an observable that is going to hold my screen size. (There’s some more data in there, so you can obviously adjust that.)
2. I pull an observable from the breakpointObserver’s “observe” function and do the match. It either returns the size or unknown. Some predefined settings in the Breakpoints object are Web, Tablet, and Handset. That covers the whole gamut of tiny to huge. There are more specific ones, but these are enough for me.
3. Now, each of these observables lrg, sml, and mobile return property “size” with values:”web, tablet, handset or unknown”. I can stick them all into one big observable using “of”, but then I’d have 3 observables inside an observable – that sucks, so I use “mergeAll()” to instead push them all into one observable with a common result set. So, now I have an observable with an array of 3 {size:something}. The result will actually be 2 “unknowns” and 1 value that’s either “web, tablet or handset”.

Then, in the constructor, I’m setting the public value breakpoint$ to this resulting observable.

Now, I put this all into a service:

import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
import { Injectable } from '@angular/core';
import { merge, Observable, of } from 'rxjs';
import { filter, map, mergeAll, tap } from 'rxjs/operators';
import { environment } from 'src/environments/environment';
  
@Injectable({
  providedIn: 'root'
})
export class BreakpointService {
 breakpoint$:Observable<{size:string}>
 
constructor(private breakpointObserver: BreakpointObserver) { this.breakpoint$ = this.breakpoint(); }
 
private breakpoint() {
  console.log("BREAKPOINT INITIALIZED")
 
  let lrg = this.breakpointObserver.observe([Breakpoints.Web]).pipe(
    map(({ matches }) => { if (matches) { return { size: "web" } } else { return { size: "unknown" } } }),
    tap(obj => {
      if (obj.size == "web" && !environment.production) console.log('screen is sized for web')
    }));
 
  let sml = this.breakpointObserver.observe([Breakpoints.Tablet]).pipe(
    map(({ matches }) => { if (matches) { return { size: "tablet" } } else { return { size: "unknown" } } }),
    tap(obj => {
      if (obj.size == "tablet" && !environment.production) console.log(obj)
    })
  )
  let mobile = this.breakpointObserver.observe([Breakpoints.Handset]).pipe(
    map(({ matches }) => { if (matches) { return { size: "handset" } } else { return { size: "unknown" } } }),
    tap(obj => {
      if (obj.size == "handset" && !environment.production) console.log('handset')
    })
  )
 
  return of(lrg, sml, mobile).pipe(mergeAll(), filter(rets => rets.size != 'unknown'))
}
 
}

So now I can add that service to the app.module’s proivders, then inject that service and it’s just a one-and-done. You can subscribe to it anywhere.

Now, on the component:

responsiveClass = 'web';
 
constructor(bps: BreakpointService, private router: Router) {
 
  bps.breakpoint$
    .pipe(
      map(retval =&gt; { return retval.size }),
    )
    .subscribe(retval=&gt;{
    this.responsiveClass = retval;
  });
}

See what I did there? I did a “map” and changed the result object to a simple string, because I really just need the string size. ( I “could” have just done an async call from the HTML, but I may want to play with that variable more, so I just made it a normal variable)

Now look what I can do in HTML:

<div [class]="responsiveClass">
   <h1>{{responsiveClass}}</h1>
</div>

And in CSS, I can do whatever the hell I want with those classes. I can make them custom for the page or use global things or use specific IDs. It’s full control.

.web{
height: 100px;width:100px;background-color: red;
}
.tablet{
height: 100px;width:100px;background-color: green;
}
.handset{
height: 100px;width:100px;background-color: blue;
}


So, on that page, I could have simply scripted something so if responsiveClass==’tablet’ then do something that only would be done on a tablet for just one particular div or something. Do you see how powerful that is?

Moreover, if you load it in a service, it’s one-and-done. Then you can subscribe to it wherever you want and it’s ready for you.

Obviously, some of you love writing media queries in CSS and doing tons of stuff there, but I find it super messy if things are specific to a page.

Some References: https://medium.com/@pjlamb12/angular-cdks-breakpointobserver-b75df04a1cc2

Article by MacGyver / Angular 2

About MacGyver

I've worked with database systems for over 20 years, and started my own company in 2000. Almost all my business consists of internal database systems, either ERP or CRM. My programming is primarily in Angular / Microsoft C# and MS SQL.

About This Site

Morris Development has been specializing in internal database system design and integration since 1999. We provide long-term management and support of secure data systems for many businesses as well as developing the more complex code structures for ERP systems like Intellievent, Apidas, and AVMS.

This site is primarily for our developers to keep track up various technologies and updates that are used by Morris Development.

Training

Integrating Angular Microsite with .Net

Private Data Caching with Google Storage

Continuous Deployment for Production Releases?

Azure Websites – the perfect Angular host

Angular 2

  • Angular 2 Authentication
  • Angular Command Line Interface
  • Material Design for Angular
  • Using Observables in Angular 2

Mentors

  • Ben Nadel
  • Dan Wahlin
  • Deborah Kurata
  • John Papa

Staff

  • Dan Morris

Training

  • Google Development Courses
  • Microsoft Virtual Academy
  • PluralSight
  • Test Deep Links

© 2025 · Morris Development