Compass Web App Project Gets Own Repository

Once I got Three.js and Magnetometer API up and running within the context of an Angular web app, I was satisfied this project is actually going to work. Justifying a move out from my collection of NodeJS tests and into its own repository. Another reason for moving into its own repository is that I wanted to make it easy to use angular-cli-ghpages to deploy my Angular app to GitHub pages. Where it will be served over HTTPS, a requirement for using sensor API from a web app.

Previously, I would execute such moves with a simple file copy, but that destroys my GitHub commit history. Not such a huge loss for small experiments like this one, but preserving history may be important in the future so I thought this is a good opportunity to practice. GitHub documentation has a page to address my scenario: Splitting a subfolder out into a new repository. It points us to a non-GitHub utility git-filter-repo which is a large Python script for manipulating git repositories in various ways, in this case isolating a particular directory and trimming the rest. I still had to manually move everything from a /compass/ subdirectory into the root, but that’s a minor change and git could recognize the files were renamed and not modified.

The move was seamless except for one detail: there is a conflict between local development and GitHub Pages deployment in its index.html. For GitHub Pages, I needed a line <base href="/compass/"> but for local development I needed <base href="/">. Otherwise the app fails to load because it is trying to load resources from the wrong paths resulting in HTTP404 Not Found errors. To make them consistent, I can tell my local development server to serve files under the compass subdirectory as well so I can use <base href="/compass/"> everywhere.

ng serve --serve-path compass

I don’t recall this being a problem in my “Tour of Heroes” tutorial. What did I miss? I believe using --serve-path is merely a workaround without understanding the root cause, but that’s good enough for today. It was more important that GitHub Pages is up and running and I could test across different browsers.


Source code for this project is publicly available on GitHub

Magnetometer Service as RxJS Practice

I’m still struggling to master CSS, but at least I got far enough to put everything I want on screen roughly where I want them, even after the user resizes their window. Spacing and proportion of my layout is still not ideal, but it’s good enough to proceed to the next step: piping data from W3C Generic Sensor API for Magnetometer into my Angular app. My initial experiment was a much simpler affair where I could freely use global variables and functions, but that approach would not scale to my future ambition to execute larger web app projects.

Installation

The Magnetometer API is exposed by the browser and not a code library, so I didn’t need to “npm install” any code. However, as it is not a part of core browser API, I do need to install W3C Generic Sensor API type definition for the TypeScript compiler. This information is only used at development time.

npm install --save-dev @types/w3c-generic-sensor

After this, TypeScript compiler still complains that it can’t find type information for Magnetometer. After a brief search I found I also need to edit tsconfig.app.json and add w3c-generic-sensor to “types” array under “compilerOptions”.

  "compilerOptions": {
    "types": [
      "w3c-generic-sensor",
    ]
  },

That handles what I had to do, but I’m ignorant as to why I had to do it. I didn’t have to do the same for @types/three when I installed Three.js, why was this different?

Practicing Both RxJS and Service Creation

I’ll need a mechanism to communicate magnetometer data when it is available. When the data is not available, I want to be able to differentiate between reasons why that data is not available. Either the software API is unable to connect to a hardware sensor, or the software API is not supported at all. The standard Angular architectural choice for such a role is to package it up as a service. Furthermore, magnetometer data is an ongoing stream of data, which makes it a perfect practice exercise for using RxJS in my magnetometer service. It will distribute magnetometer data as a Subject (multicast Observable) to all app components that subscribe to it.

Placeholder Data

Once I had it up and running, I realized everything is perfectly setup for me to generate placeholder data when real magnetometer data is not available. Client code for my magnetometer data service doesn’t have to change anything to receive placeholder data. In practice, this lets me test and polish the rest of my app without requiring that I run it on a phone with real magnetometer hardware.

State and Status Subjects

Happy with how this first use turned out, I converted more of my magnetometer service to use RxJS. The current state of the service (initialized, starting the API, connecting to magnetometer hardware, etc) was originally just a publicly accessible property, which is how I’ve always written such code in the past. But if any clients want to be notified as soon as the state changes, they either have to poll state or I have to write code to register & trigger callbacks which I rarely put in the effort to do. Now with RxJS in my toolbox, I can use a Behavior Subject to communicate changes in my service state, making it trivial to expose. And finally, I frequently send stuff to console.log() to communicate status messages, and I converted that to a Behavior Subject as well so I can put that data onscreen. This is extra valuable once my app is running on phone hardware, as I can’t (easily) see that debug console output.

RxJS Appreciation

After a very confused introduction to RxJS and a frustrating climb up the learning curve, I’m glad to finally see some payoff for my investment in study time. I’m not yet ready to call myself a fan of RxJS, but I feel I have enough confidence to wield this tool for solving problems. This story is to be continued!

With a successful Angular service distributing data via RxJS, I think this “rewrite magnetometer test in Angular” is actually going to happen. Good enough for it to move into its own code repository.


Source code for this project is publicly available on GitHub

Angular Component Dynamic Resizing

Learning to work within Angular framework, I had to figure out how to get my content onscreen at the location and size I wanted. (Close enough, at least.) But that’s just the start. What happens when the user resizes the window? That opens a separate can of worms. In my RGB332 color picker web app, the color picker is the only thing onscreen. This global scope meant I could listen to Window resize event, but listening to window-level event isn’t necessarily going to work for solving a local element-level problem.

So how does an Angular component listen to an event? I found several approaches.

It’s not clear what tradeoffs are involved using Renderer versus EventManager. In both cases, we can listen to events on an object that’s not necessarily our component. Perhaps some elements are valid for one API versus another? Perhaps there’s a prioritization I need to worry about? If I only care about listening to events that apply to my own specific component, things can be slightly easier:

  • @HostListener decorator allows us to attach a component method as the listener callback to an event on a component’s host object. It’s not as limited as it appears at first glance, as events like “window:resize” apparently propagates through the tree so our handler will fire even though it’s not on the Window object.

In all of the above cases, we’re listening on a global event (window.resize) to solve a local problem (react to my element’s change in size.) I was glad to see that web standards evolved to give us a local tool for solving this local problem:

  • ResizeObserver is not something supported by core Angular infrastructure. I could write the code to interact with it myself, but someone has written an Angular module for ResizeObserver. This is part of a larger “Web APIs for Angular” project with several other modules with similar goals: give an “Angular-y” way to leverage standardized APIs.

I tried this new shiny first, but my callback function didn’t fire when I resized the window. I’m not sure if the problem was the API, the Angular module, my usage of it, or that my scenario not lining up with the intended use case. With so many unknowns, I backed off for now. Maybe I’ll revisit this later.

Falling back to @HostListener, I could react to “window.resize” and that callback did fire when I resized the window. However, clientWidth/clientHeight size information is unreliable and my Three.js object is not the right size to fill its parent <DIV>. I deduced that when “window:resize” fired, we have yet to run through full page layout.

With that setback, I fell back to an even cruder method: upon every call to my animation frame callback, I check the <DIV> clientWidth/clientHeight and resize my Three.js renderer if it’s different from existing values. This feels inelegant but it’ll have to do until I have a better understanding of how ResizeObserver (or an alternative standardized local scope mechanism) works.

But that can wait, I have lots to learn with what I have on hand. Starting with RxJS and magnetometer.


Source code for this project is publicly available on GitHub

Angular Component Layout Sizing

For my first attempt at getting Three.js running inside an Angular web app, the target element’s width and height were hard coded to a number of pixels to make things easier. But if I want to make a more polished app, I need to make this element fit properly within an overall application layout. Layout is one of the things Angular defers to standard CSS. Translating layouts I have in my mind to CSS has been an ongoing challenge for me to master so this project will be another practice opportunity.

Right now, I’m using a <DIV> in my markup to host a canvas object generated by Three.js renderer. Once my Angular component has been laid out, I need to get the size of that <DIV> and communicate it to Three.js. A little experimentation with CSS-related properties indicated my <DIV> object’s clientWidth and clientHeight were best fit for the job.

Using clientWidth was straightforward, but clientHeight started out at zero. This is because during layout, the display engine looked at my <DIV> and saw it had no content. The canvas isn’t added until after initial layout in AfterViewInit hook of Angular component lifecycle. I have to create CSS to block out space for this <DIV> during layout despite lack of content at the time. My first effort was to declare a height using “vh” unit (Viewport Height) to stake my claim on a fraction of the screen, but that is not flexible for general layout. A better answer came later with Flexbox. By putting “display: flex;” on my <DIV> parent, and “flex-grow:1” on the <DIV> itself, I declared that this Three.js canvas should be given all available remaining space. That accomplished my goal and felt like a more generally applicable solution. A reference I found useful during this adventure was the Flexbox guide from CSS-Tricks.com.

It’s still not perfect, though. It is quite possible Flexbox was not the right tool for the job, but I needed this practice to learn a baseline from which I can compare with another tool such as CSS Grid. And of course, getting a layout up on screen is literally just the beginning: what happens when the user resizes their window? Dynamically reacting to resize is its own adventure.


Source code for this project is publicly available on GitHub

Angular + Three.js Hello World

I decided to rewrite my magnetometer test app as an Angular web app. Ideally, I would end up with something more polished. But that is a secondary goal. The primary goal is to use it as a practice exercise for building web apps with Angular. Because there are definitely some adjustments to make when I can’t just use global variables and functions for everything.

My first task is to learn how to use Three.js 3D rendering library from within an Angular web app. I know this is doable from others who have written up their experience, I only have to follow their lead.

Installation

The first step is obvious: install Three.js library itself into my project.

npm install --save three

Now my Angular app could import objects from Three, but it would fail TypeScript compilation because the compiler doesn’t have type information. For that, a separate library needs to be installed. This is only used at development time, so I save it as a development-only dependency.

npm install --save-dev @types/three

HTML DOM Access via @ViewChild

Once installed I could create an Angular component using Three.js. Inside that component I could use most of the code from Three.js introduction “Creating a Scene“. One line I could not use directly is:

document.body.appendChild( renderer.domElement );

Because now I can’t just jam something to the end of my HTML document’s <BODY> element. I need to stay within the bounds of my Angular component. To do so, I name an element in my component template HTML file where I want my Three.js canvas to reside.

[...Component template HTML...]

  <div #threejstarget></div>

[...Component template HTML...]

In the TypeScript code file, I can obtain a reference to this element with the @ViewChild decorator.

  @ViewChild('threejstarget') targetdiv!: ElementRef;

Why the “!” suffix? If we declared the variable “targetdiv” by itself, TypeScript compiler would complain that we risk using a variable that may be null or undefined instead of its declared type. This is because TypeScript compiler doesn’t know @ViewChild will handle that initialization. We use an exclamation mark (!) suffix to silence this specific check on this specific variable without having to turn on the (generally useful) null/undefined checks in TypeScript.

(On the “To-Do” list: come back later and better understand how @ViewChild relates to similar directives @ViewChildren, @ContentChild, and @ContentChildren.)

Wait until AfterViewInit

But there are limits to @ViewChild power. Our ElementRef still starts null when our component is initialized. @ViewChild could not give us a reference until the component template view has been created. So we have to wait until the AfterViewInit stage of Angular component lifecycle before adding Three.js render canvas into our component view tree.

  ngAfterViewInit() : void {
    this.targetdiv.nativeElement.appendChild( this.renderer.domElement );
  }

An alternative approach is to have <CANVAS> inside our component template, and attach our renderer to that canvas instead of appending a canvas created by renderer.domElement. I don’t yet understand the relevant tradeoffs between these two approaches.

Animation Callback and JavaScript ‘this

At this point I had a Three.js object onscreen, but it did not animate even though my requestAnimationFrame() callback function was being called. A bit of debugging pointed to my mistaken understanding of how JavaScript handles an object’s “this” reference. My animation callback was getting called in a context where it was missing a “this” reference back to my Angular component, and thus unable to advance the animation sequence.

requestAnimationFrame(this.renderNeedle);

One resolution to this issue (JavaScript is very flexible, there are many other ways) is to declare a callback that has an appropriate “this” reference saved within it for use.

requestAnimationFrame((timestamp)=>this.renderNeedle(timestamp));

That’s a fairly trivial problem and it was my own fault. There are lots more to learn about animating and Angular from others online, like this writeup about an animated counter.

Increase Size Budget

After that work I had a small Three.js animated object in my Angular application. When I run “ng build” at that point, I would see a warning:

Warning: bundle initial exceeded maximum budget. Budget 500.00 kB was not met by 167.17 kB with a total of 667.17 kB.

An empty Angular application already weighs in at over 200kB. Once we have Three.js in the deployment bundle, that figure ballooned by over 400kB and exceeded the default warning threshold of 500kB. This is a sizable increase, but thanks to optimization tools in the Angular build pipeline, this is actually far smaller than my simple test app. My test app itself may be tiny, but it downloaded the entire Three.js module from a CDN (content distribution network) and that file three.module.js is over a megabyte (~1171kB) in size. By that logic this is the better of two options, we just have to adjust the maximumWarning threshold in angular.json accordingly.

My first draft used a fixed-size <DIV> as my Three.js target, which is easy but wouldn’t be responsive to different browser situations. For that I need to learn how to use CSS layout for my target <DIV>


Source code for this project is publicly available on GitHub

Compass Web App for Angular Practice

I’ve wanted to learn web development for years and one of my problems was that I lacked the dedication and focus to build my skill before I got distracted by something else. This is a problem because web development world moves so fast that, by the time I returned, the landscape has drastically changed with something new and shiny to attract my attention. When I window-shopped Polymer/Lit, I was about to start the cycle again. But then I decided to back off for a few reasons.

First and most obvious is that I didn’t yet know enough to fully leverage the advantages of web components in general, and Polymer/Lit in particular. They enable small lightweight fast web apps but only if the developer knows how to create a build pipeline to actually make it happen. I have yet to learn how to build optimization stages like tree-shaking and minimization. Without these tools, my projects would end up downloading far larger files intended for developer readability (comments, meaningful variable names, etc.) and include components I don’t use. Doing so would defeat the intent of building something small lightweight.

That is closely related to the next factor: Angular framework has a ready setup of all of those things I have yet to master. Using Angular command line tools to build a new project boilerplate comes with a build pipeline that minimizes download size. I wasn’t terribly impressed by my first test run of this pipeline, but since I don’t yet know enough to setup my own, I definitely lack the skill to analyze why and certainly don’t yet know enough to do better.

And finally, I have already invested some time into learning Angular. There may be some “sunk cost fallacy” involved here but I’ve decided I should get basic proficiency with one framework just so I have a baseline to compare against other frameworks. If I redirect focus to Polymer/Lit, I really don’t know enough to understand its strengths and weaknesses against Angular or any other framework. How would I know if it lived up to “small lightweight fast” if I have nothing to compare it against?

Hence my next project is to redo my magnetometer web app using Angular framework. Such a simple app wouldn’t need all the power of Angular, but I wanted a simple practice run while things are still fresh in my mind. I thought about converting my AS7341 sensor web app into an Angular app, but those files must be hosted on an ESP32 which has limited space. (Part of the task would be converting to use ESPAsyncWebServer which supports GZip-compressed files.) In comparison, a magnetometer app would be hosted via GitHub pages (due to https:// requirement of sensor API) and would not have such a hard size constraint. Simpler deployment tipped the scales here, so I am going with a compass app starting with putting Three.js in an Angular boilerplate app.


Source code for this project is publicly available on GitHub

Angular “Tour of Heroes” Unit Tests For 100% Code Coverage

After learning all the ways an Angular web app can perform HTTP communication, I have a better understanding more of Angular’s “Tour of Heroes” tutorial app. And even better, I have a better idea of how I could write unit tests for it! That wasn’t something covered by the tutorial itself, so writing tests for the tutorial app is my self-assigned extracurricular activity. This follows my previous self-assigned homework, an aesthetic overhaul using Angular Material component library.

An Angular project created via “ng new” command sets up a unit testing pipeline and a few boilerplate tests. “Tour of Heroes” was created this way but it was never touched in the tutorial. Every time we call “ng create” to add a component, that boilerplate also added a simple test to verify that component could be created in the test framework. By the end of the tutorial, we have a handful of these simple creation tests. Most of which would fail, because those components have become more complex than the original boilerplate and the test hasn’t been updated to match.

Import Dependencies

First step was to import all the missing dependencies so a component can even be created in the test harness. I first did this mostly by searching for error messages and copying the answers, but it was an unsatisfying approach because I didn’t understand what I was doing. To address this, I paused my mindless copy/paste and started studying. I started with Angular Developer Guide to Testing which led to my reactive programming tangent with RxJS. Now I’m back, and I understand how to use import section of TestBed.configureTestingModule to bring in dependencies needed by component under test.

HttpClientModule to HttpClientTestingModule

Once components could be created, I was faced with a long list of HTTP failures. They all had the same cause: components that need to perform HTTP communication imported HttpClientModule, which tried to make actual HTTP calls to a server that isn’t online. For unit tests, we need to use HttpClientTestingModule to redirect component HTTP activity to behavior controlled by unit test infrastructure.

Mock Services and overrideComponent

Once tests were in place for services that used HTTP directly, errors came from components that indirectly used HTTP via those services. To keep tests small (the whole point of unit tests) I stubbed out those services with mockups that had just enough behavior to support specific unit tests and no more. Hooking them into the system required using overrideComponent to switch out real services for their simpler mockups.

For my first pass, I wrote mockups directly as TypeScript classes. This is a simpler and more direct first pass, but I think I’m supposed to use “Spy” mechanism provided by Jasmine testing framework for such mockups. Learning to use Spy is still on the to-do list.

Code Coverage Report

Running “ng test –code-coverage” activates code coverage report courtesy of Istanbul. A brief coverage summary is printed to console every time the test suite is executed, but I wanted more details to see where I need to focus my effort. The detailed report is in HTML format under the /coverage/ subdirectory. I’m supposed to go double-click on that file to bring it up in a web browser, but I’m running Angular inside a VSCode Dev Container and I can’t “just” go and double-click that file as it is not accessible to my local web browser.

Solution: quickly whip up a static file web server with the serve-static package, letting my local web browser access the report via a port opened on my dev container. I added that file along with a shortcut to my packages.json file so I can bring it up by running “npm run coverage.”

100% Is Not 100%

=============================== Coverage summary ===============================
Statements   : 100% ( 82/82 )
Branches     : 100% ( 7/7 )
Functions    : 100% ( 47/47 )
Lines        : 100% ( 75/75 )
================================================================================

These mechanisms helped me to write unit tests that exercised all code paths: 100% code coverage! But this metric is misleading, because there’s much more to an Angular app than its TypeScript code.

Many important pieces of functionality live within HTML templates and are not covered by code coverage statistics. That requires accessing ComponentFixture.nativeElement. From there we can navigate the DOM tree, query for elements, and verify they are expected.

There’s much more to Angular testing. I haven’t even gotten into other things like verifying Angular router functionality but I am getting tired of Tour of Heroes. Time for a change of pace.

Notes on Angular “HTTP Client” Guide

A working understanding of reactive programming with RxJS Observables for Angular was listed as a prerequisite to Angular guide for HTTP client. After my long and convoluted tangent to learn RxJS, I felt I was finally ready to dive in and learn how to apply RxJS to handle HTTP transactions in an Angular web application.

One of the most powerful aspects of RxJS is the declaration of an Observable pipeline is a template. It means we can set one up independently of using them. To use them we call subscribe(), and it is a template that we can reuse by calling subscribe() again. An unexpected downside of this power is that nothing actually happens until we call subscribe(), which can be counterintuitive when we use the Angular HTTP client for operations like DELETE. Outside of Angular/RxJS, such things are fire-and-forget but here we have to subscribe() to start things in motion even if we don’t really care about the results of that subscription. This is apparently a common mistake because it is called out multiple times on this page. I also remember seeing this in the “Tour of Heroes” tutorial, which was absolutely wild when I first saw it. I now understand why it behaves that way, but I still think it is weird.

Unrelated to RxJS, I thought “Requesting a typed response” section was interesting. It enables TypeScript compile-time checking, which helps keep our code straight, but we have to remember we’re still on the hook for runtime checking. Sanitizing data is important for security! This also explains why the example code would copy elements from the result and not blindly copy the whole object. Blind copy would leave our code vulnerable to a maliciously written object that comes with its own toString() or something that is likely to get called in the context (and authority!) of our application code.

With my current basic understanding of RxJS and HTTP, the first half of this page was fairly straightforward. Then things got weird with “interceptors”. I understand it is a very powerful concept that allows separation of concerns across different chunks of code, each handling an aspect of HTTP activity. But getting them to all play nicely together seems to be a challenging task with lots of room for error. A very powerful gun aimed at our feet, it seems.

Even just loading them up has pitfalls. There’s a recommendation to load them with a “barrel” but I haven’t understood the tradeoffs to use/not use them. I also understood that load order mattered, but it didn’t cover symptoms of getting the order wrong. There’s even a back channel for application code to control interceptors (passing metadata) via HttpContextToken object.

An application of interceptor that looked very impressive was the ability to return a cached value immediately while also issuing the HTTP request. Then the real updated live value from the server is given, all on the same Observable! This is something that would have been really complicated to do with plain JavaScript Promise (async/await) and the first real “A-ha” for me to see power of RxJS in action.

Interceptors are also how Angular performs client-side work to protect against cross-site request forgery (XSRF) adding an XSRF-TOKEN in the header. It’s only part of the solution, though. It needs to mesh with server-side code to set the token and verify the token. It is on my “To-Do” list to find an implementation example, probably with Express.

And finally, there’s a section on using HTTP client in unit tests. This is the section I can put to work immediately.

Notes on Angular “Observables & RxJS” Guide

I went on a reactive programming tangent thinking it would help me become an effective user of Angular web application framework. Because some very important parts of Angular made extensive use of a JavaScript reactive programming library called RxJS. I found a lot of general information about reactive programming out there, but I need to stay focused on Angular. Returning to Angular documentation, I picked another developer guide section to start reading: “HTTP client“. This page has a “Prerequisites” section at the top which linked to an “Observables & RxJS Guide“.

It was an introduction to reactive programming with the RxJS library, with examples that are relevant to Angular apps. I didn’t know this guide existed. If I had, I would have started here first! If I had, I still wouldn’t have understood RxJS immediately, but I would at least have a better focus as I ventured to other RxJS guides. Mostly thanks to Observables in Angular which showed the most common RxJS usage patterns.

I was mildly disappointed the Practical Usage page had only two examples, and one of them was the type-ahead autocomplete mechanism already covered in Tour of Heroes. At least that one had a breakdown of how the asynchronous processing pipeline worked to accomplish its goal. The other example “Exponential backoff” had no such explanation. Just an overly optimistic claim “With observables, it is very easy” before we were tossed a chunk of code with no comments nor a breakdown of what it did. Definitely room for improvement here.

RxJS Observables were compared & contrasted against JavaScript Promises in the “Observables compared to other techniques” section. I saw some unavoidable overlap with similar topics on other resources I recently read but still, it was a great way to help my brain think about how to make use of RxJS aligned with its strengths. But I know the best way to get comfortable with RxJS is to start working on code that use it.

Tiny Step on RxJS Learning Curve

I learned a lot from reading Angular’s Developer Guide to Testing, one of which is that I have a significant weak spot in my Angular understanding: how and when to use RxJS for asynchronous operations. Not just for testing, but also for writing Angular application code. On my first run through “Tour of Heroes” tutorial, RxJS was a black box I didn’t understand at all. On my second run, I understood the tutorial enough to understand specific uses of RxJS from context, but my understanding did not extend to the rest of that library. I certainly don’t know how to employ RxJS for my own tasks. It’s time to buckle down and read official RxJS documentation.

This was quite a chore. It appears RxJS official documentation was written by its development team for an audience that already knows what it does and why they would want it. It started with “RxJS is a library for composing asynchronous and event-based programs by using observable sequences” and jargon gets thicker from there. (Interspersed with words like “just” and “simply” which this beginner found ridiculous.) There was no “Getting Started” section. The next-best candidate section “Installation” is full of material telling existing users how to migrate breaking changes, not an introduction to new users.

The good news is that, as of this writing, RxJS is still under active development. Today’s stable release 7.8.0 has a timestamp only a few weeks old. (December 15th, 2022.) I was concerned about that, because RxJS was a solution for JavaScript asynchronous programming before JavaScript evolved to have its own asynchronous constructs like async/await. It doesn’t map cleanly to Promises and conversion is a bit of a mess. But RxJS still does things that standard JavaScript doesn’t do, so there are still scenarios where RxJS would be the right tool for the job. It’ll take more learning before I recognize those scenarios.


Observable: RxJS foundation class. One thing that demystified a lot was rewriting an analogy using plain JavaScript. Seeing something familiar and understandable (with my very recent JavaScript education) gave me a starting point to try understanding the rest of the page. Unhelpfully this was at the bottom of the page.

Observer: It took a while for me to understand observer where my code fits into the system and I can use as little or as much of it as I need for a scenario. It can be a single callback for receiving data (“next”) but I can also register optional callbacks to be notified of problems (“error”) or when there’s no more data (“complete”).

Operator: Documentation said this is the powerful part of RxJS, but didn’t really explain why or how we would leverage that power. This is also where marble diagrams were explained, something that bewildered me when Angular testing discussed marble testing. The overview of available operators is a disappointing list of just names. Each name is a link to their reference page without any additional context. I’d have to click the link and read each one. I would love a reference where each link has an 1-2 sentence summary, but no luck here.

Subject: These are Observable that multicasts to more than one Observer. Up until this point I hadn’t known a basic Observable only broadcast to a single Observer! This critical information should have been covered earlier.

Scheduler: No usage examples or scenarios were given so I have no idea when I should care about choosing a different RxJS scheduler. I see the “How to do X”, but not “Why you want to do X.”


I’m sure everything in official RxJS documentation is technically correct but the information lacks context. Or more precisely, I don’t think they were even trying to provide context for readers who are unaware. Perhaps there are resources to help RxJS beginners elsewhere on the internet?

Notes on Angular Developer Guide to Testing

Once my Angular development container could run tests in Chrome using headless mode, I could see actual test failures. Since “Tour of Heroes” tutorial did not touch test files at all, these failing tests were untouched boilerplate tests. For the most part they just test to see if a component instance could be created within the test harness, there were no further updates to test “Tour of Heroes” functionality. Components that couldn’t be created in a test harness obviously led to abysmal code coverage numbers.

=============================== Coverage summary ===============================
Statements   : 18.75% ( 15/80 )
Branches     : 0% ( 0/8 )
Functions    : 6.38% ( 3/47 )
Lines        : 16.21% ( 12/74 )
================================================================================

Since I am just getting started, there were a lot of (1) copy error message (2) search on that message (3) read hits on StackOverflow (4) repeat. Component creation failure mainly revolved around fixing up “imports” or declarations” sections to calling TestBed.configureTestingModule(). After I got component startup sorted out, code coverage numbers looked a lot better.

=============================== Coverage summary ===============================
Statements   : 61.25% ( 49/80 )
Branches     : 0% ( 0/8 )
Functions    : 53.19% ( 25/47 )
Lines        : 62.16% ( 46/74 )
================================================================================

But that was a lot of copy/pasting from StackOverflow with little understanding of what happened. And even though tests registered as passing, there were still error messages shown on console output. I need to learn more, so I started reading the “Testing” section of Angular Developer guides. It seems to assume I know Jasmine and Karma, which I don’t. I plan to skim the first pass for a partial understanding, then maybe dedicate some time to learning Jasmine/Karma, then come back again. Here are some notes from my first pass:

Valuable Live Examples

Just like the “Understanding Angular” section, pages in “Developer Guides” very frequently have a link to a project that we can load up and examine either by downloading code for our own machine or fire it up within our browser connected to a StackBlitz virtual machine. Having functional code is always great and mitigates many documentation issues. One code snippet in “Testing Services” documentation called a function I couldn’t find any information about, looking at the live code I figured out it was something strictly within the demo project. (async-observable-helpers.ts)

Scattered Older Content

I see occasional signs of outdated content. Some examples: (1) API TestBed.get() was deprecated from Angular 9 but still pops up here and there. (2) Occasional mentions of “Tour of Heroes” that doesn’t match the tutorial I just completed a second time. Either they were snippets from an older version of the tutorial, or the example was modified for illustration purposes. (3) Standard boilerplate code listed in “Basics of Testing Components” do not match what today’s tools generate.

Having illustrative code examples fall out of date reminded us to keep in mind that some of their associated information might be out of date as well.

Component Testing Scenarios

The longest page (by far) in this section is “Component Testing Scenarios.” I think it is the goldmine. A list of how we might want to test our Angular components and tools to go about them. It puts a lot of Angular testing tools (which have their own section) in context of problems they were created to solve. As a beginner I don’t quite understand why I might want to do these things, but here’s the reference if I want to do them.

Almost a quarter of this very long page is dedicated to “Component with async service”. (Which makes this one section longer than most other pages in “Angular Developer Guides – Testing”) RxJS is used by Angular a lot in the realm of asynchronous behavior, so I need to learn more about that. Not just to test, but to build Angular in general. I expect things will make sense after getting a better grasp of RxJS terminology like “marble testing“. I thought the best place to start would be RxJS official documentation. (I was wrong.)

Running Angular Unit Tests (ng test) in VSCode Dev Container

I knew web development frameworks like Angular proclaim to offer a full package, but it’s always enlightening to find out what “full” does and doesn’t mean in each context. I had expected Angular to have its own layout engine and was mildly surprised (but also delighted) to learn the official recommendation is to use standard CSS, migrating off Angular-specific interim solutions.

Another area I knew Angular offered is a packaged set of testing tools: a combination of generic JavaScript testing tools and Angular-specific components already set up to work together as a part of Angular application boilerplate. We can kick off a test pass by running “ng test” and when I did so, I saw the following error message:

✔ Browser application bundle generation complete.
28 02 2023 09:17:22.259:WARN [karma]: No captured browser, open http://localhost:9876/
28 02 2023 09:17:22.271:INFO [karma-server]: Karma v6.4.1 server started at http://localhost:9876/
28 02 2023 09:17:22.272:INFO [launcher]: Launching browsers Chrome with concurrency unlimited
28 02 2023 09:17:22.277:INFO [launcher]: Starting browser Chrome
28 02 2023 09:17:22.279:ERROR [launcher]: No binary for Chrome browser on your platform.
  Please, set "CHROME_BIN" env variable.

Test runner Karma looked for Chrome browser and didn’t find it. This is because I’m running my Angular development environment inside a VSCode Dev Container, and this isolated environment couldn’t access the Chrome browser on my Windows 11 development desktop. It needs its own installation of Chrome browser and be configured to run in headless mode. (Which is itself undergoing an update but that’s not important right now.)

From these directions I installed Chrome in the container via command line.

sudo apt update
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo apt install ./google-chrome-stable_current_amd64.deb

Then Karma needs to be configured to run Chrome in headless mode. However, default Angular app boilerplate did not include karma.conf.js file which we need to edit. We need to tell Angular to create one:

ng generate config karma

Now we can edit the newly created file karma.conf.js following directions from here. Inside the call to config.set(), we need to define a custom launcher called “ChromeHeadless” then use that launcher in the browsers array. The results will look something like the following:

module.exports = function (config) {
  config.set({
    customLaunchers: {
      ChromeHeadless: {
        base: 'Chrome',
        flags: [
          '--no-sandbox',
          '--disable-gpu',
          '--headless',
          '--remote-debugging-port=9222'
        ]
      }
    },
    basePath: '',
    frameworks: ['jasmine', '@angular-devkit/build-angular'],
    plugins: [
@@ -33,7 +44,7 @@ module.exports = function (config) {
      ]
    },
    reporters: ['progress', 'kjhtml'],
    browsers: ['ChromeHeadless'],
    restartOnFileChange: true
  });
};

With these changes I can run “ng test” inside my dev container without any errors about running Chrome browser. Now I have an entirely different set of errors about object creation!


Appendix: Error Messages

Here are error messages I saw during this process, in order to help people to find these instructions by searching for the error message they saw.

Immediately after installing Chrome, running “ng test” will try launching Chrome but not in headless mode which will show these errors:

✔ Browser application bundle generation complete.
28 02 2023 17:50:43.190:WARN [karma]: No captured browser, open http://localhost:9876/
28 02 2023 17:50:43.202:INFO [karma-server]: Karma v6.4.1 server started at http://localhost:9876/
28 02 2023 17:50:43.202:INFO [launcher]: Launching browsers Chrome with concurrency unlimited
28 02 2023 17:50:43.205:INFO [launcher]: Starting browser Chrome
28 02 2023 17:50:43.271:ERROR [launcher]: Cannot start Chrome
        Failed to move to new namespace: PID namespaces supported, Network namespace supported, but failed: errno = Operation not permitted
[0228/175043.258978:ERROR:file_io_posix.cc(144)] open /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq: No such file or directory (2)
[0228/175043.259031:ERROR:file_io_posix.cc(144)] open /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq: No such file or directory (2)

28 02 2023 17:50:43.271:ERROR [launcher]: Chrome stdout: 
28 02 2023 17:50:43.272:ERROR [launcher]: Chrome stderr: Failed to move to new namespace: PID namespaces supported, Network namespace supported, 
but failed: errno = Operation not permitted                                                                                                      [0228/175043.258978:ERROR:file_io_posix.cc(144)] open /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq: No such file or directory (2)
[0228/175043.259031:ERROR:file_io_posix.cc(144)] open /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq: No such file or directory (2)

This has nothing to do with Karma because if I run Chrome directly from the command line, I see the same errors:

$ google-chrome
Failed to move to new namespace: PID namespaces supported, Network namespace supported, but failed: errno = Operation not permitted
[0228/175538.625191:ERROR:file_io_posix.cc(144)] open /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq: No such file or directory (2)
[0228/175538.625244:ERROR:file_io_posix.cc(144)] open /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq: No such file or directory (2)
Trace/breakpoint trap

Running Chrome with just the “headless” flag is not enough.

$ google-chrome --headless
Failed to move to new namespace: PID namespaces supported, Network namespace supported, but failed: errno = Operation not permitted
[0228/175545.785551:ERROR:file_io_posix.cc(144)] open /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq: No such file or directory (2)
[0228/175545.785607:ERROR:file_io_posix.cc(144)] open /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq: No such file or directory (2)
[0228/175545.787163:ERROR:directory_reader_posix.cc(42)] opendir /tmp/Crashpad/attachments/9114d20a-6c9e-451e-be47-353fb54f28be: No such file or 
directory (2)                                                                                                                                    Trace/breakpoint trap

We have to disable sandbox to get further, though that’s not the full solution yet either.

$ google-chrome --headless --no-sandbox
[0228/175551.357814:ERROR:bus.cc(399)] Failed to connect to the bus: Failed to connect to socket /run/dbus/system_bus_socket: No such file or dir
ectory                                                                                                                                           [0228/175551.361002:ERROR:bus.cc(399)] Failed to connect to the bus: Failed to connect to socket /run/dbus/system_bus_socket: No such file or dir
ectory                                                                                                                                           [0228/175551.361035:ERROR:bus.cc(399)] Failed to connect to the bus: Failed to connect to socket /run/dbus/system_bus_socket: No such file or dir
ectory                                                                                                                                           [0228/175551.365695:WARNING:bluez_dbus_manager.cc(247)] Floss manager not present, cannot set Floss enable/disable.

We also have to disable the GPU. Once done as per Karma configuration above, things will finally run inside a Dev Container.

Hello Angular Layout

Using the Angular Material component library gave me some great looking controls, but adding the library into my app increased its size by more than I had anticipated. It’s a good thing Angular Material is an optional component, so I don’t have to use it for space-constrained projects. Something else not core to Angular framework is page layout. I thought it might be part of Angular Material, but I found nothing there, either.

Looking around online for some kind of page layout component for Angular apps, I found something called “Angular Flex Layout” which, like Angular Material, is an optional component. It sounds like the component had always provided a bridge across the gap between the potential capabilities of CSS and the actual capabilities of CSS in shipping browsers. Several years ago, that gap was quite large! But now it appears the gap is small enough for Angular Flex Layout to ride off into the sunset.

I found more details on Angular team blog post “Modern CSS in Angular: Layouts, which confirmed they intend for modern CSS (flexbox and grid got specific callouts) to handle most of what people formerly needed “Angular Flex layout” for. This blog post claimed to be the first in a series of helping people adopt modern standardized CSS for layout instead of using libraries like Angular Flex Layout, but I haven’t seen any additional installments posted. I’ll stay tuned hoping to learn more in the future.

For needs beyond modern CSS, that blog post pointed to layout-related features within Angular’s Component Development Kit (CDK). I read over some introductory pages and didn’t comprehend very much. I hope that after I become more familiar with CSS and Angular, I can articulate the gap CDK Layout fulfills.

And finally, that blog post also mentions using Tailwind CSS in Angular apps, but without further information. I know nothing about Tailwind and very little about Angular and CSS. At the moment I have no idea what tradeoffs are involved in using that library. I’ll stick that on my “To Do” list at the bottom. Right now I have a higher priority item on my list: get a handle on Angular unit testing.

Angular Material Impact on Download Size

I was interested in using Angular Material component library because I thought it would help me build web apps that look better than my usual efforts. My introductory experience with it was very promising and I think it’ll deliver on design aesthetics. The project was also an experiment to see other parts of Angular in action. On the topic of download size, the result was not as good.

One aspect of using Angular that sounded appealing was that it came with an already-configured build pipeline so I didn’t have to build my own. Starting from tools like TypeScript compiler, through to tools like Webpack. It is a multi-stage process to go from source code to a compact package delivered by our web server. It performs optimizations like “tree-shaking” for removing unused code, and “minification” which removed comments, whitespaces, everything nonessential. Ideally this meant the final package would have only what we absolutely need and no more. In practice, I’m not so sure.

After finishing the “Tour of Heroes” tutorial and playing with a bit of CSS, but before I converted over to using Angular Material, my application’s raw download size was a little over 309 kilobytes.

✔ Browser application bundle generation complete.
✔ Copying assets complete.
✔ Index html generation complete.

Initial Chunk Files           | Names         |  Raw Size | Estimated Transfer Size
main.e962da6362f99387.js      | main          | 274.74 kB |                71.19 kB
polyfills.716cc9a230a87077.js | polyfills     |  33.11 kB |                10.70 kB
runtime.6e50dac17b84e715.js   | runtime       | 922 bytes |               517 bytes
styles.19b697cb6d7ac4c1.css   | styles        | 477 bytes |               172 bytes

                              | Initial Total | 309.22 kB |                82.56 kB

After I installed Angular Material with “ng add @angular/material” but before I actually explicitly imported any components, the final bundle size grew by over 50% to 479 kilobytes:

✔ Browser application bundle generation complete.
✔ Copying assets complete.
✔ Index html generation complete.

Initial Chunk Files           | Names         |  Raw Size | Estimated Transfer Size
main.7c4c0522a82c6f83.js      | main          | 338.94 kB |                87.07 kB
styles.4aae9080591fb05a.css   | styles        | 106.13 kB |                 9.64 kB
polyfills.716cc9a230a87077.js | polyfills     |  33.11 kB |                10.70 kB
runtime.6e50dac17b84e715.js   | runtime       | 922 bytes |               517 bytes

                              | Initial Total | 479.08 kB |               107.92 kB

This was disappointing. As far as I know I haven’t actually used anything of Angular Material, yet I had already picked up 170 kilobytes of stuff that the automated optimization tools could not avoid. And even worse, this was completely opaque to me. I don’t know of any way to break down where those 170kB came from, nor what I could try to optimize them.

As I started actually importing and using components from Angular Material, my bundle size grew as expected. I had hoped maybe those 170 kilobytes were majority of core shared code and each additional module would add only minimal size, but this was definitely not the case. My freshman effort at Angular Material conversion added the following modules.

import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatListModule } from '@angular/material/list';
import { MatToolbarModule } from '@angular/material/toolbar';

And by doing so, I picked up almost 300 additional kilobytes for a total of 776 kilobytes.

✔ Browser application bundle generation complete.
✔ Copying assets complete.
✔ Index html generation complete.

Initial Chunk Files           | Names         |  Raw Size | Estimated Transfer Size
main.dc2316f7fe149792.js      | main          | 636.43 kB |               134.91 kB
styles.7a21ac1018e81662.css   | styles        | 105.66 kB |                 9.52 kB
polyfills.716cc9a230a87077.js | polyfills     |  33.11 kB |                10.70 kB
runtime.6e50dac17b84e715.js   | runtime       | 922 bytes |               517 bytes

                              | Initial Total | 776.11 kB |               155.63 kB

Warning: bundle initial exceeded maximum budget. Budget 500.00 kB was not met by 276.11 kB with a total of 776.11 kB.

Final line of this output is a warning from Angular’s build-time bundle size budget watcher, which is half of a good feature. It’s great that it informed me I exceeded size budget, but I don’t understand what exactly contributed to that total and I certainly don’t know what I could do about it. Well, I guess there’s “Don’t use Angular Material” but I hope for something finer-grained than that.

Does this matter? It depends on the context. If I’m writing an app for my own use on my home network, a few hundred kilobytes are insignificant. If I’m writing something to run on my phone via mobile data, such data usage would be notable but not necessarily a deal breaker. If I’m creating a browser app from ESP32, there’s only 4MB of flash memory to serve such files and a few hundred kilobytes could sink the entire project.

It’s just like any other tool: there are right places to use it, and there are wrong places to use it. This applies to Angular itself, to Angular Material component library, and whatever I use for layout. Because for both good and ill, layout is not an intrinsic part of Angular framework.

Hello Angular Material

I made some small layout and styling changes to Angular Framework’s “Tour of Heroes” tutorial app. It looked a little better to me, but those changes didn’t stick around for long. They were merely a HTML/CSS warmup exercise and quickly replaced by the next activity: converting the app to use Angular Material component library.

While I don’t necessarily agree with everything in Google’s Material Design, for better or worse Google’s reach meant most users would already be familiar with them. The option to utilize Angular Material component library was, in fact, the main motivation for me to learn Angular. Now that I’m finally at this point, I eagerly opened up their Getting Started page and got to work. The instructions were fine and putting the library to use was about as easy as I can reasonably ask for. There were two items of note:

Import

This was taught right up front on “Displaying a component” section of Getting Started page, but I didn’t grasp its significance at first. Each Angular Material component is packaged in a module that we have to import before use, making every component (and associated overhead) an opt-in decision. This is not illustrated by their live examples on Stackblitz, because those examples imported everything. I wasted time trying to sort out exactly what was important until I figured out that they tell you in the “API” section of each component’s documentation. Example: the first line of Material Button API page is:

import {MatButtonModule} from '@angular/material/button';

And Reload

After importing the module as per API documentation, I could use that component, but it didn’t look right. Most of the behaviors were there, but appearance-wise it didn’t look quite right. Style bits were missing here and there, the most visible aspect was that I didn’t see any theme colors. We’re supposed to be able to select from “Primary”, “Accent” or “Warn” colors but the control looked plain despite setting color to one of those options.

Hunting on answers from Stack Overflow found a lot of information about defining and using custom themes, which I don’t care about (yet). But buried among custom theming information was my answer: after importing a component, the development server launched by the “ng serve” command would only pick up partial functionality. There was nothing wrong with my code. To pick up everything, including theme colors, I had to stop the running instance of “ng serve” and launch it again.

Looks Great! But…

Getting this far meant I could fold several Angular Material controls into my “Tour of Heroes” app. Using Angular Material controls meant I could delete a lot of CSS associated with custom styling of the tutorial. Utilizing this component library in my own projects should mean less fussing with CSS for styling. This is a great benefit as I don’t consider myself any good at user interface design. But this ease has a tradeoff in larger download size.


Code for this project is publicly available on GitHub

First CSS Exercise with Angular “Tour of Heroes” Tutorial

Running through Angular framework’s “Tour of Heroes” tutorial a second time was good for me. I learned a lot of web development between my first and second runs, helping me understand and retain a lot more information this time around. I’m still short of what I’d call “proficient”, though. In the interest of getting more practice, I will continue my personal Angular learning plan. The main focus of “Tour of Heros” was Angular framework, so it had only minimal attention paid to styling. So my next step is to play with styling this tutorial app.

The main objective is to exercise “think in CSS” portions of my brain. As a computer user it’s not hard for me to think of web interfaces that I enjoyed and didn’t enjoy using. Now I want to develop an ability to articulate my experience in terms of how I can implement them in CSS. This will start out very slow and rough, with many visits to MDN documentation for reference. I expect to get faster at it with practice and practice starts small. This first round will make only minor changes to CSS and almost no changes to HTML at all.

It’s not necessary for results of this first round to be good in an objective sense, but it’s important for the layout to be mine. Pick out small things I didn’t like and verify I can fix it to my liking.

  • Tutorial CSS used Cambria (a serif font) mixed in with majority of non-serif fonts. I felt those styles clashed for no good reason and that was the easiest place to start.
  • I thought the diagnostics message section at the bottom took up too much space and wasn’t distinct enough from the rest of the app. My change packed the “Clear Messages” button on the same horizontal line as the header, removing a lot of whitespace around both. I also changed the diagnostic message text themselves to a monospaced font. This may sound hypocritical because I just removed some serif font usage because they clashed, but in this case a style clash is a feature and not a bug. I always mentally associate monospaced typefaces with technical data, and I wanted it to stand out.
  • Dashboard component had a search box for hero names, and I thought that was not distinct enough from the rest of the dashboard. I gave it a subtly darker background to visually differentiate it from the rest of the dashboard, and I centered the component horizontally because Yahoo, Google, etc. all taught me to expect a search text box to be centered on screen.
  • Similar to how I packed “Clear messages” into the same horizontal space as the message display area header, I packed “Dashboard” and “Heroes” navigation buttons into the same topmost horizontal space as the application header at the top of the screen.
  • In the “Dashboard” view, we have a text box for search at the bottom. In the “Heroes” view, we have a text box for adding a new hero at the top. I didn’t like that ordering because I want to review existing data before I add a hero. I moved “add hero” control to the bottom, which also made it consistent with dashboard search.
  • And finally, in the individual hero view, I again added a CSS flexbox to pack buttons together for a more compact layout.

I preferred how the resulting app looked, and I thought it was a good simple warmup round before I dip my toes into more serious UI styling.


Code for this project is publicly available on GitHub.

Angular “Tour of Heroes” Tutorial Round 2

I’m glad I skimmed through “Understanding Angular” documentation section before going through “Tour of Heroes” tutorial for the second time. Combined with my improved understanding of web development, this time I actually understood everything covered in the tutorial. The best example are Angular directives, which were mysterious black boxes the first time around. I copied them straight from tutorial text without much comprehension of what they did. This time around, I mostly understood how they dictated Angular behavior. I also understood that some of what I see are shorthand versions and, if I get too lost, I can go look for their expanded-out full versions which are more explicit in their actions.

I Know Where Things Go Now

One memory I had of my first run of “Tour of Heroes” were a few steps where the instruction was to copy a snippet of code into a specific file. However, they didn’t say where in the file. The first time as a beginner, I didn’t even understand enough to know where a code snippet should fit. This time around, I never felt that way. When a code snippet is presented, I understood the context where it should be inserted. This was a very satisfying feeling! Once I completed “Tour of Heroes” for the second time (which also went much faster) I had a selection of topics to explore outside the scope of the tutorial.

Need to Revisit Angular Tests

One candidate is testing. At the moment I’m completely ignorant about automated testing of web apps. I know such tools exist, but so far, my web app projects were tested manually when I played around with what I wrote. As my project size grows (and I have ambitions of bigger projects) this approach would not scale well. Angular has provisions for testing frameworks, which we can kick off with “ng test”. Angular boilerplate includes a bare-bones test (filename ending with spec.ts) that went with the corresponding code (filename ending with just .ts.) Since the tutorial only changed code file without updating its corresponding test file, I ran “ng test” expecting to see test failures. But it appears I have some work to do before I even get that far, because I’m running Angular inside a VSCode dev container, and it doesn’t have Chrome installed.

node ➜ /workspaces/angular-tour-of-heroes (main) $ ng test
✔ Browser application bundle generation complete.
11 02 2023 09:44:34.255:WARN [karma]: No captured browser, open http://localhost:9876/
11 02 2023 09:44:34.268:INFO [karma-server]: Karma v6.4.1 server started at http://localhost:9876/
11 02 2023 09:44:34.268:INFO [launcher]: Launching browsers Chrome with concurrency unlimited
11 02 2023 09:44:34.272:INFO [launcher]: Starting browser Chrome
11 02 2023 09:44:34.273:ERROR [launcher]: No binary for Chrome browser on your platform.
  Please, set "CHROME_BIN" env variable.

I know it is possible to run Chrome in an automated test where it runs without any user interaction or even a visible interface. I’ll need to figure out this “headless mode” before I can run “ng test”.

After a few minutes of looking around online, it appears running headless Chrome inside a container requires more knowledge than I have at the moment. Instead of tackling another new thing, I decided to practice what I’ve already learned. I will resume my tentative learning plan, which calls for me to turn my Tour of Heroes into a CSS exercise.

Notes on “Understanding Angular”

My first time through Angular framework’s documentation section had focused on “Getting started” and “Tutorial” sections, just to get a toehold on things. Due to a firehose of information about this large framework, I didn’t retain very much. Now I’m taking a second pass through documentation, armed with more knowledge and experience to understand more of it. I drew up a tentative plan earlier, but I’ve already decided to deviate from that plan. After I read through the StackBlitz tutorial, but before I started hands-on with “Tour of Heroes” tutorial again, I decided to read the documentation section “Understanding Angular” whose overview page started with this:

To understand the capabilities of the Angular framework, you need to learn about the following:

  • Components
  • Templates
  • Directives
  • Dependency injection
    The topics in this section explain these features and concepts, and how you can use them.

I admire this goal, but the reality was I lacked prerequisite knowledge to understand all of it. This is fine! Every time I understand a little more. At a rough guess, I would say I understood and retained about 5% of this information after my first effort, and this second effort had roughly 30% retention.

Live Code Everywhere

The length and quality of documents in this section vary greatly, probably reflecting different authors contributing from their own perspectives. But at the end of the day, developers want to see code and we have them. Most (all?) of the topics included links to sample code in both live-running form (running on StackBlitz) as well as downloadable source code example. Some go much further, for example “component interaction” goes as far as including test code. And in several cases, sample code covered concepts that were not covered on written pages, so it’s always worth checking out that link.

Nonlinear Organization

Sometimes my confusion is not from missing prerequisite knowledge, but counterintuitive organization of information. For example: Under the binding section, Property binding is the next-to-last item on the list yet it is listed as prerequisite by the second (attribute binding) and third (class and style binding) documents. To make sense of everything I have to jump around this page.

Attributes vs. Properties

Several documents in the binding section emphasized that we bind to properties and not attributes. But they didn’t explain what that difference meant! Looking outside this site, I found this StackOverflow thread which included a gem of an explanation: “When writing HTML source code, you can define attributes on your HTML elements. Then, once the browser parses your code, a corresponding DOM node will be created. This node is an object, and therefore it has properties.

Angular Directive Long Form

As an Angular beginner I can see Angular directives as a very powerful mechanism, but the syntax can be tough for me to decipher. Eventually I learned this was because we frequently see shorthands for using directives, and the structure is easier to comprehend when we see the “long form”. This snippet was shown as a *ngFor “long form”, and it was quite illuminating!

<div *ngFor="let product of products">
  <h3>
    <a [title]="product.name + ' details'">
      {{ product.name }}
    </a>
  </h3>
</div>

It was not as concise as the shorthand, but much easier for me to decipher what’s going on. Compare the above to another example of ngFor directive:

<ul>
  <ng-template ngFor let-hero [ngForOf]="heroes">
    <li>{{hero.name}}
  </ng-template>
</ul>

(Note that these are two different examples of ngFor and they do different things, because I copy/pasted them from different sections of Angular documentation. I’m only using them to compare syntax.)

Ready for Round 2

After reading through “Understanding Angular” I can’t say I totally understand Angular yet, but I have a much better understand of how it goes about its business. I expect some of this information to be reinforced as I go through their “Tour of Heroes” tutorial again, because I think I’ll recognize them the next time around and have a better idea of their usage context.

A Quick Look at Angular 15

In the interest of improving the odds that I’ll actually learn something I can put to use, I’ve devised a plan for how I intend to learn and apply Angular application framework. My skills have evolved since the last time I ran through Angular tutorial, and it’s no surprise that Angular has evolved itself. Web technologies move fast! My previous effort worked with Angular 11, and as of about two weeks ago, we’re now up to Angular 15. I looked through the changes to see how much I understood and how much would actually affect the kind of projects I intend to build. It appears that I picked a good time to review Angular — v15 looks to be quite a consequential step forward.

From the “Angular v15 is now available” (2022/11/16) page:

  • Standalone components: I got excited about this item because I didn’t understand Angular terminology. I thought “standalone” meant I could use Angular components piecemeal independent of the Angular framework, I was wrong. Reading more, I’ve learned some people felt Angular components are too dependent on a specific Angular mechanism “NgModule”. Architecturally that meant a NgModule was the smallest unit of reusability, and not Component as intended. This “standalone” feature makes it easier for an Angular Component to run without NgModule.
  • NgOptimizedImage: Sounds like the Angular framework can now handle resizing image files. So large screen desktops don’t get blurry images and small mobile devices don’t waste bandwidth downloading high resolution images.
  • Stack traces are more helpful: Sounds promising. Getting an informative stack trace has always been a problem with debugging asynchronous code.
  • MDC-based components: Angular Material was why I started looking at Angular framework to begin with! At the time it was an Angular-focused set of web controls that are implemented differently from those intended for non-Angular web sites. (The non-Google affiliated Materialize-CSS project was something I’ve tried along similar veins.) Now it seems like the two worlds are converging, nice.

From the “Angular v14 is now available” (2022/6/2) page:

  • Strictly typed forms: Reading the description, it’s the kind of thing that surprised in the “Wait, you meant it wasn’t like that already?” category. Angular uses TypeScript and leverages its compile time type checking to catch bugs. Given this fact I was surprised that forms (a major way to interact with user data) aren’t strictly typed. Perhaps there’s subtlety here I don’t understand but better type checking is almost always a good thing.

From the “Angular v13 is now available” (2021/11/3) page:

  • End of IE11 support: This only impacts me because I have a bunch of old Windows Phones that I had intended to reuse in other projects via its integrated web browser, which is a mobile build of Internet Explorer. Angular 11 didn’t officially support IE but it was possible to build for IE with a few project settings. The results… mostly worked. As of Angular v13 that is no longer an option. If I still want to put those old Windows Phones to work via web apps, I’d have to do it with older versions of Angular or without Angular at all.

From the “Angular v12 is now available” (2021/5/12) page:

  • Sass: I had forgotten about Sass until I read Angular v12 increased support for Sass. I learned about Sass quite some time ago, before my recent efforts to relearn CSS. I’ve forgotten much of Sass and how it addresses challenges of plain CSS. I’m going to refresh my knowledge of Sass before I proceed.

Learning Plan for Angular Round 2

Reviewing the TypeScript Handbook was very educational, even if I didn’t understand all of it. It was enough to make me feel confident I have what I need to get more out of revisiting Angular web framework. When I tried to learn Angular the first time, I only had a basic grasp of HTML, CSS, and JavaScript. Because of this weak foundation with weak supports, I didn’t really know enough to put Angular to work. I just ran through the tutorial and didn’t do much with it. Over the past few weeks, I’ve been patching up holes in my knowledge of web development, and I hope to have better results if I visit Angular again. It’s no guarantee of success, and there’s a good chance I’d only learn enough to realize I need to revisit the other topics like CSS and JavaScript again. But even in that case I’d learn more than I know today, and that is itself a win.

So given what I’ve learned recently, here is how I intend to tackle my second round of learning Angular:

  1. Read through Angular introduction again.
  2. Just skim instructions for the StackBlitz-based shopping cart demo without repeating hands-on activity. I like the idea of StackBlitz but its web-based development environment was different enough from a local development environment that I’ve decided I prefer to skip it in favor of practicing local development.
  3. Hands-on follow through the “Tour of Heroes” tutorial for the second time.

After finishing “Tour of Heroes” again, put my recent learning to work enhancing it:

  1. The “Tour of Heroes” tutorial was focused on Angular application framework mechanics, so the visual HTML and CSS is very plain. Put my recent HTML and CSS learning to work and spiff up that site. Including a mobile-friendly layout via media queries.
  2. The “Tour of Heroes” tutorial used a small class as a local proxy substitution for server-side database backend, storing its data in memory using JavaScript collection classes. Remove that proxy and migrate it to run on a Node.js server.
  3. Upgrade backend interface code to a more robust web API implemented using Express.
  4. Upgrade backend store to a MongoDB instance instead of in-memory JavaScript objects.

If I get this far, I would have practiced the entire MEAN stack. However, the MongoDB side would be quite lightweight given the limited demands of “Tour of Heroes”. Fortunately, in the MongoDB University course, we were given several practice databases of nontrivial size. I could build an Angular web app on top of one of those databases.

And if I’m successful with that, I would then have enough skill to tackle a MEAN stack project from scratch.

Tha’s quite a plan with many steps! I’ll likely deviate from this plan as I hit various roadblocks and work to resolve them, and it’ll take at least several weeks. But it feels exciting to have a longer-term plan. But first, a look at the Angular framework to see how it has changed since my first visit.