<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.3">Jekyll</generator><link href="https://walkingriver.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://walkingriver.com/" rel="alternate" type="text/html" /><updated>2023-10-22T01:35:22+00:00</updated><id>https://walkingriver.com/feed.xml</id><title type="html">WalkingRiver.com</title><subtitle>The best software is both elegant and beautiful, and seldom what you expect.</subtitle><entry><title type="html"></title><link href="https://walkingriver.com/1999-12-31-park-pursuit-privacy-policy/" rel="alternate" type="text/html" title="" /><published>2023-10-22T01:35:22+00:00</published><updated>2023-10-22T01:35:22+00:00</updated><id>https://walkingriver.com/1999-12-31-park-pursuit-privacy-policy</id><content type="html" xml:base="https://walkingriver.com/1999-12-31-park-pursuit-privacy-policy/">&lt;h1&gt;Privacy Policy&lt;/h1&gt;
&lt;p&gt;
  This privacy policy governs your use of the software application Park Pursuit
  (“Application”) for mobile devices that was created by&amp;nbsp;Walking River
  Software.&amp;nbsp;The Application is designed to help pass the time while waiting
  in line or in other situations that require long periods of idleness.
&lt;/p&gt;
&lt;h1&gt;What information does the Application obtain and how is it used?&lt;/h1&gt;
&lt;h2&gt;User Provided Information&amp;nbsp;&lt;/h2&gt;
&lt;p&gt;
  The Application obtains the information you provide when you download and
  register the Application.&amp;nbsp;Registration with us is optional. However,
  please keep in mind that you may not be able to use some of the features
  offered by the Application unless you register with us.
&lt;/p&gt;

&lt;p&gt;
  When you register with us and use the Application, you generally
  provide&amp;nbsp;your name, email address, and password.
&lt;/p&gt;

&lt;h2&gt;Automatically Collected Information&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;
  In addition, the Application may collect certain information automatically,
  including, but not limited to, the type of mobile device you use, your mobile
  devices unique device ID, the IP address of your mobile device, your mobile
  operating system, the type of mobile Internet browsers you use, and
  information about the way you use the Application.&amp;nbsp;
&lt;/p&gt;

&lt;h1&gt;
  Does the Application collect precise real time location information of the
  device?
&lt;/h1&gt;

&lt;p&gt;
  This Application does not collect precise information about the location of
  your mobile device.&amp;nbsp;
&lt;/p&gt;

&lt;h1&gt;
  Do third parties see and/or have access to information obtained by the
  Application?
&lt;/h1&gt;

&lt;p&gt;No.&lt;/p&gt;
&lt;p&gt;We may disclose User Provided and Automatically Collected Information:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;
      as required by law, such as to comply with a subpoena, or similar legal
      process;
    &lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;
      when we believe in good faith that disclosure is necessary to protect our
      rights, protect your safety or the safety of others, investigate fraud, or
      respond to a&amp;nbsp;government request;
    &lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;
      with our trusted services providers who work on our behalf, do not have
      an&amp;nbsp;independent use of the information we disclose to them, and have
      agreed to adhere&amp;nbsp;to the rules set forth in this privacy statement.
    &lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;
      if&amp;nbsp;Walking River Software&amp;nbsp;is involved in a merger, acquisition,
      or sale of all or a&amp;nbsp;portion of its assets, you will be notified via
      email and/or a prominent notice on our Web site of any change in ownership
      or uses of this information, as well as any choices you may have regarding
      this information.
    &lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;What are my opt-out rights?&lt;/h1&gt;

&lt;p&gt;
  You can stop all collection of information by the Application easily by
  uninstalling the Application. You may use the standard uninstall processes as
  may be available as part of your mobile device or via the mobile application
  marketplace or network.
&lt;/p&gt;

&lt;h1&gt;Data Retention Policy, Managing Your Information&lt;/h1&gt;
&lt;p&gt;
  We will retain User Provided data for as long as you use the Application and
  for a reasonable time thereafter. Please note that some or all of the User
  Provided Data may be required in order for the Application to function
  properly.
&lt;/p&gt;

&lt;h1&gt;Children&lt;/h1&gt;

&lt;p&gt;
  We do not use the Application to knowingly solicit data from or market to
  children under the age of 13.
&lt;/p&gt;

&lt;h1&gt;Security&lt;/h1&gt;

&lt;p&gt;
  We are concerned about safeguarding the confidentiality of your information.
  We provide physical, electronic, and procedural safeguards to protect
  information we process and maintain. For example, we limit access to this
  information to authorized employees and contractors who need to know that
  information in order to operate, develop or improve our Application. Please be
  aware that, although we endeavor provide reasonable security for information
  we process and maintain, no security system can prevent all potential security
  breaches.
&lt;/p&gt;

&lt;h1&gt;Changes&lt;/h1&gt;
&lt;p&gt;
  This Privacy Policy may be updated from time to time for any reason. We will
  notify you of any changes to our Privacy Policy by posting the new Privacy
  Policy&amp;nbsp;here&amp;nbsp;and&amp;nbsp;. You are advised to consult this Privacy
  Policy regularly for any changes, as continued use is deemed approval of all
  changes.
&lt;/p&gt;

&lt;h1&gt;Your Consent&lt;/h1&gt;
&lt;p&gt;
  By using the Application, you are consenting to our processing of your
  information as set forth in this Privacy Policy now and as amended by us.
  &amp;quot;Processing,” means using cookies on a computer/hand held device or using
  or touching information in any way, including, but not limited to, collecting,
  storing, deleting, using, combining and disclosing information, all of which
  activities will take place in the United States. If you reside outside the
  United States&amp;nbsp;your information will be transferred, processed and stored
  there under United States&amp;nbsp;privacy standards.&amp;nbsp;
&lt;/p&gt;</content><author><name></name></author></entry><entry><title type="html">Melding Feature Development and Sustainment into a Seamless Workflow</title><link href="https://walkingriver.com/bridging-the-divide/" rel="alternate" type="text/html" title="Melding Feature Development and Sustainment into a Seamless Workflow" /><published>2023-10-21T00:00:00+00:00</published><updated>2023-10-21T00:00:00+00:00</updated><id>https://walkingriver.com/bridging-the-divide</id><content type="html" xml:base="https://walkingriver.com/bridging-the-divide/">&lt;p&gt;In the realm of software development, two core activities dominate the scene – Feature Development and Sustainment. The former is about adding new capabilities to the system, while the latter deals with maintaining and refining the existing system to ensure its steady and reliable operation. Traditionally, these two are seen as separate tracks, often managed by distinct teams with different sets of priorities and timelines. However, as the industry evolves towards more agile and lean practices, it’s time to question this division and advocate for a more unified approach.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;the-kanban-advantage&quot;&gt;The Kanban Advantage&lt;/h2&gt;

&lt;p&gt;Kanban, with its emphasis on visual management, work-in-progress limits, and continuous delivery, acts as a bridge that can unite the two tracks of Feature Development and Sustainment. Under this framework:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Both feature and sustainment work items are visualized on the same board, providing transparency and a holistic view of all the work in the pipeline.&lt;/li&gt;
  &lt;li&gt;Work-in-progress (WIP) limits ensure that the team’s capacity isn’t overwhelmed by an influx of new features or sustainment tasks, promoting a balanced focus.&lt;/li&gt;
  &lt;li&gt;The pull system ensures that work is pulled only when there is capacity, thus avoiding the common pitfall of overloading the team with simultaneous feature and sustainment demands.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;the-rhythm-of-consistent-releases&quot;&gt;The Rhythm of Consistent Releases&lt;/h2&gt;

&lt;p&gt;A consistent release cadence, where releases happen at fixed intervals, irrespective of whether the work item is a new feature or a sustainment task, fosters several beneficial practices:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;It encourages small, incremental changes, which are easier to manage, test, and deploy.&lt;/li&gt;
  &lt;li&gt;It reduces the pressure associated with big releases and the corresponding big-bang integration and testing efforts.&lt;/li&gt;
  &lt;li&gt;It promotes a culture of continuous improvement and feedback, as teams can learn from each release to improve the next.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;ticket-to-release-a-timely-journey&quot;&gt;Ticket to Release: A Timely Journey&lt;/h2&gt;

&lt;p&gt;The journey from ticket creation to release should be swift, yet thorough. An appropriate time-frame might look like:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Implementation&lt;/strong&gt;: 1-2 weeks&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Quality Assurance (QA)&lt;/strong&gt;: 1 week&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Release&lt;/strong&gt;: Within the next scheduled release slot&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This schedule ensures that there’s ample time for development, testing, and any necessary revisions, while still aligning with the regular release cadence.&lt;/p&gt;

&lt;h2 id=&quot;optimistic-branching-the-path-forward&quot;&gt;Optimistic Branching: The Path Forward&lt;/h2&gt;

&lt;p&gt;An optimistic branching strategy could be adopted to keep the momentum going for the next release while handling any defects found in QA for the current release. Under this strategy:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Mainline development continues on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt; branch targeting the next release.&lt;/li&gt;
  &lt;li&gt;A release branch is cut from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt; at the start of the QA phase for the current release.&lt;/li&gt;
  &lt;li&gt;Defects found in QA are fixed on the release branch and merged back to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt; to ensure the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt; branch also receives these fixes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This strategy ensures that development for the next release isn’t held up by the QA phase of the current release, while still providing a mechanism to address defects in a structured manner.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;The divide between Feature Development and Sustainment is a relic of the past that doesn’t serve the agile, fast-paced development environments of today. By adopting Kanban with a consistent release cadence, teams can blend these two tracks into a seamless workflow that delivers continuous value while ensuring the system’s reliability and quality. Through timely ticket to release journeys and an optimistic branching strategy, this unified approach paves the way for a more efficient, predictable, and value-driven software development lifecycle.&lt;/p&gt;</content><author><name>Michael D. Callaghan</name></author><category term="Kanban" /><category term="Software Development" /><category term="Agile" /><category term="Sustainment" /><summary type="html">In the realm of software development, two core activities dominate the scene – Feature Development and Sustainment. The former is about adding new capabilities to the system, while the latter deals with maintaining and refining the existing system to ensure its steady and reliable operation. Traditionally, these two are seen as separate tracks, often managed by distinct teams with different sets of priorities and timelines. However, as the industry evolves towards more agile and lean practices, it’s time to question this division and advocate for a more unified approach.</summary></entry><entry><title type="html">Evolving the Developer-QA Dynamics</title><link href="https://walkingriver.com/cultivating-quality/" rel="alternate" type="text/html" title="Evolving the Developer-QA Dynamics" /><published>2023-10-20T00:00:00+00:00</published><updated>2023-10-20T00:00:00+00:00</updated><id>https://walkingriver.com/cultivating-quality</id><content type="html" xml:base="https://walkingriver.com/cultivating-quality/">&lt;p&gt;The quest for efficiency and quality in software development beckons a paradigm shift, where Quality Assurance (QA) is not seen as a separate entity, but as an integral part of the development lifecycle. Automation stands as a cornerstone of this transformation, offering a myriad of benefits when adopted in the QA process. When sustainment developers and the QA team collaborate in the same repository, contributing to the code as equal partners, a new horizon of productivity and quality unveils.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;seamless-collaboration&quot;&gt;Seamless Collaboration&lt;/h2&gt;

&lt;p&gt;Working within the same repository fosters an environment of seamless collaboration between sustainment developers and the QA team. This setup:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Facilitates real-time communication and feedback, enhancing the speed and accuracy of bug identification and resolution.&lt;/li&gt;
  &lt;li&gt;Promotes a shared understanding of the codebase and the quality standards required, building a common ground for tackling issues and developing new features.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;continuous-quality-assurance&quot;&gt;Continuous Quality Assurance&lt;/h2&gt;

&lt;p&gt;Automation in QA paves the way for Continuous Quality Assurance (CQA), a practice that integrates testing within the development process, rather than a phase that follows development. This integration:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Enables immediate detection and response to issues, reducing the lag between development and QA.&lt;/li&gt;
  &lt;li&gt;Facilitates consistent, repeatable testing processes, ensuring that each code contribution adheres to the established quality benchmarks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;code-contribution-equality&quot;&gt;Code Contribution Equality&lt;/h2&gt;

&lt;p&gt;Treating code contributions from sustainment developers and the QA team as equal partners implies a level of trust and respect for the expertise that each party brings to the table. This equality:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Encourages a culture of collective ownership and accountability for the code quality.&lt;/li&gt;
  &lt;li&gt;Promotes a sense of pride and accomplishment among all contributors, nurturing a conducive environment for continuous improvement and innovation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;accelerated-feedback-loop&quot;&gt;Accelerated Feedback Loop&lt;/h2&gt;

&lt;p&gt;Automated QA significantly accelerates the feedback loop, providing developers with instant feedback on the impact of their code changes. This acceleration:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Reduces the time spent waiting for feedback, allowing for quicker iterations and refinements.&lt;/li&gt;
  &lt;li&gt;Enhances the efficiency and effectiveness of the development process by enabling more frequent releases with assured quality.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;enhanced-traceability&quot;&gt;Enhanced Traceability&lt;/h2&gt;

&lt;p&gt;Having everyone work within the same repository with automated QA processes ensures better traceability of code changes, testing results, and defect tracking. This traceability:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Provides a clear audit trail of who did what and when, which is crucial for debugging and compliance purposes.&lt;/li&gt;
  &lt;li&gt;Encourages accountability and transparency, fostering a culture of quality and continuous improvement.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Incorporating automated QA within the same repository, where sustainment developers and the QA team contribute as equal partners, heralds a new era of collaborative development. This approach not only augments the efficiency and quality of the development process but also builds a culture of shared responsibility and continuous learning. By breaking down the silos between development and QA, and embracing automation, organizations are well-positioned to navigate the complexities of modern software development with agility and confidence.&lt;/p&gt;

&lt;h1 id=&quot;scenario-collaborative-bug-resolution-and-test-automation&quot;&gt;Scenario: Collaborative Bug Resolution and Test Automation&lt;/h1&gt;

&lt;p&gt;Let’s consider a potential real-world scenario where we can see this idea play out.&lt;/p&gt;

&lt;h2 id=&quot;bug-identification-and-automated-test-creation&quot;&gt;Bug Identification and Automated Test Creation&lt;/h2&gt;

&lt;p&gt;During the routine analysis of the system, a QA engineer identifies a bug that causes incorrect handling of date values in a certain module of the application. Recognizing the significance of this bug, the QA engineer decides to create an automated test to consistently reproduce the issue and validate any future fixes.&lt;/p&gt;

&lt;p&gt;In addition to creating a bug ticket in the team’s issue tracker, they clone the repository, create a new branch named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bug/date-handling-fix&lt;/code&gt;, and write a test case that reproduces the bug. They push the test case to the repository, which triggers the automated CI (Continuous Integration) process, and as expected, the new test case fails, signaling the presence of the bug.&lt;/p&gt;

&lt;p&gt;Once completed, the ticket is updated with the test results and the branch with the failing test. This ticket then gets assigned to a developer for review.&lt;/p&gt;

&lt;h2 id=&quot;bug-fix-implementation&quot;&gt;Bug Fix Implementation&lt;/h2&gt;

&lt;p&gt;A notification about the failing test reaches the sustainment developer, who then pulls the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bug/date-handling-fix&lt;/code&gt; branch to their local development environment. They meticulously analyze the bug, identify the root cause, and implement the necessary fix in the code. They run the entire test suite, which now includes the new test case created by the QA engineer. All tests pass, including the newly added one. They commit the changes, push the fix to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bug/date-handling-fix&lt;/code&gt; branch, and create a pull request for merging the fix into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt; branch.&lt;/p&gt;

&lt;h2 id=&quot;continuous-quality-assurance-1&quot;&gt;Continuous Quality Assurance&lt;/h2&gt;

&lt;p&gt;The QA team reviews the pull request, and the automated CI process runs all the tests again, confirming that the bug fix didn’t introduce any regressions. Once the pull request is merged, the QA team, instead of being bogged down in manual verification of the bug fix, can focus on devising more comprehensive tests to prevent similar bugs in the future. They start working on creating an extended suite of date handling tests to cover other potential edge cases, enhancing the robustness of the testing process.&lt;/p&gt;

&lt;h2 id=&quot;reflect-and-adapt&quot;&gt;Reflect and Adapt&lt;/h2&gt;

&lt;p&gt;With the bug successfully resolved and additional test coverage in place, both the sustainment developers and the QA team hold a brief retrospective meeting to discuss what went well and what could be improved. They agree that the collaborative approach of working together in the same repository facilitated a quick and effective resolution of the bug. They also recognize the value of automated testing in freeing up the QA team’s time, allowing them to focus on proactive measures to improve system quality, rather than being in a constant reactive mode chasing bugs.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;This scenario underscores the synergy that can be achieved when sustainment developers and the QA team collaborate closely, leveraging automation to enhance the quality and reliability of the software. Such a collaborative model not only accelerates bug resolution but also fosters a culture of continuous improvement and proactive quality assurance, which are crucial for meeting the ever-evolving demands of modern software development.&lt;/p&gt;</content><author><name>Michael D. Callaghan</name></author><category term="Software Development" /><category term="Quality Assurance" /><category term="Automation" /><category term="Collaboration" /><summary type="html">The quest for efficiency and quality in software development beckons a paradigm shift, where Quality Assurance (QA) is not seen as a separate entity, but as an integral part of the development lifecycle. Automation stands as a cornerstone of this transformation, offering a myriad of benefits when adopted in the QA process. When sustainment developers and the QA team collaborate in the same repository, contributing to the code as equal partners, a new horizon of productivity and quality unveils.</summary></entry><entry><title type="html">Greetings from Post-DEVIntersection</title><link href="https://walkingriver.com/post-dev-intersection/" rel="alternate" type="text/html" title="Greetings from Post-DEVIntersection" /><published>2022-01-03T00:00:00+00:00</published><updated>2022-01-03T00:00:00+00:00</updated><id>https://walkingriver.com/post-dev-intersection</id><content type="html" xml:base="https://walkingriver.com/post-dev-intersection/">&lt;p&gt;&lt;em&gt;Note: the fake template string below is intentional&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Dear {{firstName}},&lt;/p&gt;

&lt;p&gt;I’m sure you’ve received marketing emails that start like this. It’s obvious that someone botched the mail merge. It feels lazy or incompetent, definitely unprofessional. But what if they got it right? What if your name was correct in the greeting? Is that really any better? Honestly, have you ever truly believed they were sending emails just to you?&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;I have hundreds of customers and subscribers to my email list. Does anyone think I send them individually? For me, it’s better to skip the pretend personalization and be straight with you. I send emails to hundreds of people, and we should be OK with that. That doesn’t mean the content of the email isn’t useful. I try to make all my communications valuable and not a waste of my readers’ time.&lt;/p&gt;

&lt;p&gt;I want to start by telling you about an amusing technique I learned on Twitter to combat this very thing. Consider LinkedIn for a moment. I get lots of emails from recruiters who view my LinkedIn profile. Many of them are obviously bulk emails, loaded with irrelevant and sometimes inappropriate job opportunities. A very few of them are genuine, written directly to me, and maybe worth a response. The technique I want to share with us is that I added a US flag emoji to the front of my first name. My official name on LinkedIn is 🇺🇸 Michael Callaghan.&lt;/p&gt;

&lt;p&gt;If someone is using a bulk email system with mail merge capabilities, the software will blindly copy my first name so the email is addressed “Dear 🇺🇸 Michael”. I usually ignore those. Humans, on the other hand, are very unlikely to address me as “🇺🇸 Michael,” and will instead address me as simply “Michael.” I will read and often respond to those emails.&lt;/p&gt;

&lt;p&gt;As I said, a simple, fun, and surprisingly effective technique. Credit goes to &lt;a href=&quot;https://twitter.com/kvlly&quot;&gt;@kvlly on Twitter&lt;/a&gt; for the idea.  It probably won’t last, though. More and more systems will eventually be modified to strip emojis from names. But it’ll be fun until they do.&lt;/p&gt;

&lt;h1 id=&quot;devintersection-fall-2021&quot;&gt;DEVIntersection Fall 2021&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;https://walkingriver.com/assets/img/dev-int-workshop.jpg&quot; alt=&quot;Mike Hosting an Ionic Workshop at DEVIntersection in Las Vegas, December 2021&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now I’ll get to DEVIntersection. This was my first time attending the Las Vegas event. I’ve been to the Orlando conference three times, most recently as a speaker. For this one they accepted my proposal to give an all-day Ionic-Angular workshop. I’ve never tried doing a long workshop publicly before, so I had no idea what to expect. I asked &lt;a href=&quot;https://twitter.com/callaghanbenj&quot;&gt;my son Ben&lt;/a&gt; to assist me.&lt;/p&gt;

&lt;p&gt;We ran the workshop twice. The first one was at my son’s office for his coworkers the Friday before the conference. This led to some tweaks and simplifications.&lt;/p&gt;

&lt;p&gt;After that we drove the six hours from Lehi, Utah to Las Vegas. I spent most of the drive continuing to tweak and improve the deck, and then more time on Sunday, the day before the official workshop.&lt;/p&gt;

&lt;p&gt;Monday morning found us in a small room in the MGM Grand, setting up our recording gear. We ended up getting four attendees. Three of them made it for the entire day. During our 6-hour session, we built a mobile app from scratch using Ionic Framework and Angular. It was entirely hands-on, with each attendee expected to follow along.&lt;/p&gt;

&lt;p&gt;Our final half hour was spent live coding an Xcode project and installing it on my iPhone. Surprisingly, it all worked perfectly! 😁&lt;/p&gt;

&lt;p&gt;The purpose of recording the presentation is so that I can eventually make it available to a wider audience. We had two recordings going all day: one an audio and video of me on the small stage; the other was the information being presented to the class, or so I thought.&lt;/p&gt;

&lt;p&gt;When I opened all the recordings to start stitching them together, I realized my mistake. I recorded the wrong monitor all day. Instead of recording the class content, I recorded my slide notes for 6 hours. All is not lost, though. The video and audio of me seem fine, so I simply need to edit the deck into the video. It won’t be hard, but it will be monotonous. I probably won’t get to it until after the new year.&lt;/p&gt;

&lt;p&gt;Once the first video is ready, I’ll probably put it up on Gumroad and/or YouTube and let you know where you can find it. Depending on its reception, I may decide to finish it and make the entire workshop available for sale.&lt;/p&gt;

&lt;p&gt;The rest of DEVIntersection went pretty smoothly for the most part. I presented two more Angular topics, based on content from Angular Advocate and my Angular PWA book and course.&lt;/p&gt;

&lt;p&gt;They scheduled both sessions back-to-back on Tuesday afternoon. I was to present the final two sessions of the day, in a room about as far away from the expo hall as possible. Each session was scheduled for one hour, but my Angular “Resolve or Die” presentation is barely 20 minutes long. Naturally, it was the first one. I finished more than half an hour too soon and had way too much time between the two sessions. I don’t think anyone would have minded had I finished the final session early. I think it’s time to retire that particular presentation unless someone wants a 15-20 talk about Angular data loading strategies.&lt;/p&gt;

&lt;p&gt;The final session, Angular PWA, went over really well. There was just the right amount of audience participation with lots of good questions.&lt;/p&gt;

&lt;p&gt;Unfortunately, I needed to get back to work and could not complete the conference. I had already taken a week off, so I cut my visit short and flew home Wednesday.&lt;/p&gt;

&lt;h1 id=&quot;whats-next-for-conferences&quot;&gt;What’s Next for Conferences?&lt;/h1&gt;
&lt;p&gt;DEVIntersection is going back to Las Vegas in April 2022. I don’t know if I want to go back there so soon. I’m also considering submitting a few brand new sessions to ng-Conf, being held in March in Salt Lake City. I’ve never been accepted there, so I really want to take my time and get the titles and summaries right.&lt;/p&gt;

&lt;h2 id=&quot;here-are-the-topics-im-considering&quot;&gt;Here are the topics I’m considering.&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;Slow Down to Get Ahead: If you want to make a stronger, more positive impression to those around you in your daily life, personal or professional, I have two words of advice: slow down.&lt;/li&gt;
  &lt;li&gt;Unleash the Shell: The command line is all the rage these days. From node to git to the Angular CLI. Many developers have been using command line tools for decades, but some are only just now getting their feet wet. In addition to hundreds of available commands, most *nix shells make extensive use of symbols.&lt;/li&gt;
  &lt;li&gt;Don’t Tell Them No: Very few people like hearing the word no. It isn’t pleasant. For example, how do you feel when you make a request and the answer is a quick no? Saying no shuts down a conversation. It is final, leaving little room for negotiation. On the other hand, you can’t say yes to everyone. it is simply not possible or realistic to agree with every request. What can you do?&lt;/li&gt;
  &lt;li&gt;Angular to Phone in Minutes: Do you have an Angular app that is currently deployed to the web? Bring a laptop and a USB cable. Open your project in your favorite code editor and then follow along as we deploy it to your mobile device in mere minutes!&lt;/li&gt;
  &lt;li&gt;Progressive (Angular) Web Applications: Deploy any static Angular app as a PWA without paying Google or Apple for the privilege. Not just a presentation, this will be a hands-on experience where you can deploy your own code as a PWA.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thoughts? If you read &lt;a href=&quot;https://amzn.to/32S71x0&quot;&gt;Don’t Say That at Work&lt;/a&gt;, two of them should look familiar. I don’t know how well those topics will translate to a live presentation, but I think I can make it work. Feel free to ping me on Twitter with any feedback.&lt;/p&gt;

&lt;h1 id=&quot;connect-with-me&quot;&gt;Connect with Me&lt;/h1&gt;
&lt;p&gt;PS: If you want to connect with me on LinkedIn or Twitter, you can find me here: &lt;a href=&quot;https://www.linkedin.com/in/michaelcallaghan2/&quot;&gt;Mike Callaghan on LinkedIn&lt;/a&gt; and here: &lt;a href=&quot;https://twitter.com/walkingriver&quot;&gt;@WalkingRiver&lt;/a&gt;.&lt;/p&gt;</content><author><name>Michael D. Callaghan</name></author><category term="DEVIntersection" /><category term="Conferences" /><category term="LinkedIn" /><summary type="html">Note: the fake template string below is intentional Dear {{firstName}}, I’m sure you’ve received marketing emails that start like this. It’s obvious that someone botched the mail merge. It feels lazy or incompetent, definitely unprofessional. But what if they got it right? What if your name was correct in the greeting? Is that really any better? Honestly, have you ever truly believed they were sending emails just to you?</summary></entry><entry><title type="html">Watch Your Writing - Tips to Improve Your Written Communications (Revised Dec 2021)</title><link href="https://walkingriver.com/watch-your-writing/" rel="alternate" type="text/html" title="Watch Your Writing - Tips to Improve Your Written Communications (Revised Dec 2021)" /><published>2021-12-03T00:00:00+00:00</published><updated>2021-12-03T00:00:00+00:00</updated><id>https://walkingriver.com/watch-your-writing</id><content type="html" xml:base="https://walkingriver.com/watch-your-writing/">&lt;p&gt;With the explosion of social media over the past decade or so, the written word is often the first, and sometimes the only, exposure other people will ever have of you. There are hundreds, perhaps thousands, of people who who will only ever know you through your writing. Fair or not, many of them will judge you on how well you express yourself. This article is an attempt to provide some simple guidance to improve your language and your writing, to make that first impression a positive one.&lt;/p&gt;

&lt;!--more--&gt;
&lt;style&gt;
  ul {
    list-style-type: none;
    margin-left: 0px;
}
&lt;/style&gt;

&lt;p&gt;My target audience is native English speakers. While I am sure non-native English speakers may be able to benefit from these tips, I do not pretend to understand the struggles that come with learning another language. I have the utmost respect for all who do so.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;Note: I originally wrote this article as a knee-jerk reaction to some very poor spelling and grammar by a few of my peers. As some had pointed out, the tone was a bit harsh, especially to those for whom English is not their native language. This version is an attempt to rewrite that article in a more positive, upbeat tone. You can still &lt;a href=&quot;https://walkingriver.com/watch-your-language&quot;&gt;read the original here&lt;/a&gt; if you wish.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1 id=&quot;spelling&quot;&gt;Spelling&lt;/h1&gt;
&lt;p&gt;Most spelling mistakes online are probably caused by simple typos. This is especially true if you are typing on a mobile device. First, use a spell checker. In today’s world, spell checkers are everywhere. The code editor I am using right now has one. A word of caution, however: if you are not careful, you can still use a correctly spelled word incorrectly. That is a harder problem to solve and requires more proofreading.&lt;/p&gt;

&lt;p&gt;Even though you may spell words correctly, it is possible to confuse your reader by using inappropriate language, words, and grammar. This section contains the errors I see most often.&lt;/p&gt;

&lt;h2 id=&quot;alright&quot;&gt;Alright&lt;/h2&gt;
&lt;p&gt;There is no such word, even though many spell checkers do not flag it. It is an informal form of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;all right&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;alot&quot;&gt;Alot&lt;/h2&gt;
&lt;p&gt;This word does not appear in the English language. It is an informal way of saying &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a lot&lt;/code&gt;. When in doubt, consider using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;many&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;❌ There were alot of bugs in the last release.&lt;/li&gt;
  &lt;li&gt;✅ There were a lot of bugs in the last release.&lt;/li&gt;
  &lt;li&gt;✅ There were many bugs in the last release.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;awhilea-while&quot;&gt;Awhile/A while&lt;/h2&gt;
&lt;p&gt;I admit to being guilty of this one myself. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A while&lt;/code&gt; refers to an indefinite amount of time. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Awhile&lt;/code&gt; is an adverb, and will usually appear next to a verb. Also, though some people do it, Examples &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;awhile&lt;/code&gt; after a preposition.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;❌ The code took awhile to build.&lt;/li&gt;
  &lt;li&gt;✅ The code took a while to build.
We should discuss our build process for awhile.&lt;/li&gt;
  &lt;li&gt;✅ We should discuss our build process awhile.
    &lt;h2 id=&quot;cancelledcanceled&quot;&gt;Cancelled/Canceled&lt;/h2&gt;
    &lt;p&gt;Though technically either form is correct, in most of the English-speaking world, the proper spelling is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;canceled&lt;/code&gt;. In reality, it does not matter which one you use; the former spelling (cancelled) is becoming more common. My advice is to pick one and be consistent.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;implyinfer&quot;&gt;Imply/Infer&lt;/h2&gt;
&lt;p&gt;If you expect your reader to derive some meaning from your statement, you are implying. Your reader is inferring. Try not to reverse them.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;❌ What are you inferring with that statement?&lt;/li&gt;
  &lt;li&gt;✅ What are you implying with that statement?
    &lt;h2 id=&quot;irregardless&quot;&gt;Irregardless&lt;/h2&gt;
    &lt;p&gt;Again, there is no such word. There are two words you may wish to use instead: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;regardless&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;irrespective&lt;/code&gt;.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;october-2020-update&quot;&gt;October 2020 Update!&lt;/h3&gt;
&lt;p&gt;It was brought to my attention by &lt;a href=&quot;https://twitter.com/33ProductionsSP&quot; target=&quot;_blank&quot;&gt;Team 33 Productions on Twitter&lt;/a&gt; that I am mistaken about this. Evidently irregardless is a word, as &lt;a href=&quot;https://www.npr.org/2020/07/07/887649010/regardless-of-what-you-think-irregardless-is-a-word&quot; target=&quot;_blank&quot;&gt;reported by NPR&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That said, I concur with the following opinion, taken from the article:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;“It’s not a real word. I don’t care what the dictionary says,” responds author Michelle Ray, who teaches English in Silver Spring, Md.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I stand by my recommendation that your writing will be better if you eliminate this word.&lt;/p&gt;

&lt;h2 id=&quot;itsits&quot;&gt;It’s/Its&lt;/h2&gt;
&lt;p&gt;This error is one I probably see more than any other. The confusion is that adding apostrophe-s to most words turns them into a possessive form. This is an exception to that rule. The word &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;it's&lt;/code&gt; is a contraction, meaning &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;it is&lt;/code&gt;. The correct possessive form is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;its&lt;/code&gt;, without the apostrophe.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;❌ My cat just spent the last five minutes chasing it’s tail.&lt;/li&gt;
  &lt;li&gt;✅ My cat just spent the last five minutes chasing its tail.&lt;/li&gt;
  &lt;li&gt;❌ Its a beautiful day outside.&lt;/li&gt;
  &lt;li&gt;✅ It’s a beautiful day outside.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See also &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;who's&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;whose&lt;/code&gt;, which follow a similar pattern.&lt;/p&gt;

&lt;h2 id=&quot;leadled&quot;&gt;Lead/Led&lt;/h2&gt;
&lt;p&gt;The past-tense of the verb &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lead&lt;/code&gt; is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;led&lt;/code&gt;, often confused with its homonym &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lead&lt;/code&gt;, which is a dense, soft metal.&lt;/p&gt;

&lt;h2 id=&quot;lessfewer&quot;&gt;Less/Fewer&lt;/h2&gt;
&lt;p&gt;The rule for this is pretty simple. If you can count something, use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fewer&lt;/code&gt;. If you cannot count it, or you tend to use its singular noun form, use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;less&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;❌ This code has less bugs than the last one.&lt;/li&gt;
  &lt;li&gt;✅ This code has fewer bugs than the last one.&lt;/li&gt;
  &lt;li&gt;✅ This code is less complex than the last one.&lt;/li&gt;
  &lt;li&gt;❌ I have less dollars today than yesterday.&lt;/li&gt;
  &lt;li&gt;✅ I have fewer dollars today than yesterday.&lt;/li&gt;
  &lt;li&gt;✅ I have less money today than yesterday.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;letslets&quot;&gt;Let’s/Lets&lt;/h2&gt;
&lt;p&gt;This is another one I see often. The word &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let's&lt;/code&gt; is a contraction meaning “let us.” On the other hand, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lets&lt;/code&gt; is a verb meaning “allows.”&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;❌ I hope the manager let’s us use Ionic.&lt;/li&gt;
  &lt;li&gt;✅ I hope the manager lets us use Ionic.&lt;/li&gt;
  &lt;li&gt;❌ Lets try Ionic for this project.&lt;/li&gt;
  &lt;li&gt;✅ Let’s try Ionic for this project.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;thenthan&quot;&gt;Then/Than&lt;/h2&gt;
&lt;p&gt;I think this one can be confusing because we no longer use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;then&lt;/code&gt; in most programming languages for conditionals. Shell script and BASIC programmers probably do not make this mistake.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Then&lt;/code&gt; follows &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if&lt;/code&gt;. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Than&lt;/code&gt; is used to compare two things.&lt;/p&gt;

&lt;p&gt;Examples&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;❌ I like TypeScript better then JavaScript&lt;/li&gt;
  &lt;li&gt;✅ I like TypeScript better than JavaScript&lt;/li&gt;
  &lt;li&gt;❌ First I run my tests, than I commit the code.&lt;/li&gt;
  &lt;li&gt;✅ First I run my tests, then I commit the code.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;theretheirtheyre&quot;&gt;There/their/they’re&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;There&lt;/code&gt; is a directional indicator. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Their&lt;/code&gt; is the possessive form of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;they&lt;/code&gt;. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;They're&lt;/code&gt; is a contraction meaning “they are.”&lt;/p&gt;

&lt;p&gt;Example&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;✅ Their defense is over there on the sideline discussing how they’re going to put more pressure on the quarterback.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;waswere&quot;&gt;Was/Were&lt;/h2&gt;
&lt;p&gt;Most people get this one right. The errors often occur when they are referring to a person and a situation that is not currently true, may happen in the future, or is unlikely ever to happen. This is known as the “subjunctive mood,” and the normal rules do not apply.&lt;/p&gt;

&lt;p&gt;Basically, the subjunctive rule is to use the plural form of the verb with the singular form of the noun. If you are not accustomed to seeing it, you will think the correct form is the error and vice versa.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;❌ If I was the manager of this team, things would be different.&lt;/li&gt;
  &lt;li&gt;✅ If I were the manager of this team, things would be different.&lt;/li&gt;
  &lt;li&gt;❌ If John was to run for office, he would have my vote.&lt;/li&gt;
  &lt;li&gt;✅ If John were to run for office, he would have my vote.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;whosewhos&quot;&gt;Whose/Who’s&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Whose&lt;/code&gt; is possessive. However, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;who's&lt;/code&gt; is a contraction meaning &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;who is&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;❌ Who’s pull request is this?&lt;/li&gt;
  &lt;li&gt;✅ Whose pull request is this?&lt;/li&gt;
  &lt;li&gt;❌ Whose responsible for the new feature?&lt;/li&gt;
  &lt;li&gt;✅ Who’s responsible for the new feature?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;youryoursyoure&quot;&gt;Your/Yours/You’re&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Your&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yours&lt;/code&gt; are another example of possessive words that do not use an apostrophe. Its incorrect use almost always comes in the form of someone using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;your&lt;/code&gt; when they mean &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;you're&lt;/code&gt;, or “you are.” Every now and then, I see &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;your's&lt;/code&gt;, which as far as I know, is never proper English.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;❌ Your welcome&lt;/li&gt;
  &lt;li&gt;✅ You’re welcome&lt;/li&gt;
  &lt;li&gt;❌ Are these pull requests your’s?&lt;/li&gt;
  &lt;li&gt;✅ Are these pull requests yours?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whenever I see &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;your welcome&lt;/code&gt; in a message, I always want to reply, “my welcome what?” I imagine that this is almost always a typo, but it is worth mentioning.&lt;/p&gt;

&lt;h1 id=&quot;a-note-on-apostrophe-use&quot;&gt;A Note on Apostrophe Use&lt;/h1&gt;
&lt;p&gt;An odd trend I have seen on Twitter lately is an overall incorrect use of the apostrophe to make common nouns plural. I even see this from professional writers.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;❌ I need some approval’s for my pull request.&lt;/li&gt;
  &lt;li&gt;✅ I need some approvals for my pull request.&lt;/li&gt;
  &lt;li&gt;❌ My parent’s are visiting for the holiday’s this year.&lt;/li&gt;
  &lt;li&gt;✅ My parents are visiting for the holidays this year.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are times where it is appropriate, often when simply adding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;s&lt;/code&gt; would cause confusion.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;❌ There are two as in algebra.&lt;/li&gt;
  &lt;li&gt;✅ There are two a’s in algebra.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For more information, see &lt;a href=&quot;https://editorsmanual.com/articles/apostrophes-in-plurals/&quot; target=&quot;_blank&quot;&gt;this excellent article on the subject&lt;/a&gt;. It contains many more examples and details. It can be a complex subject.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;❌ He lead our standup meeting today.&lt;/li&gt;
  &lt;li&gt;✅ He led our standup meeting today.&lt;/li&gt;
  &lt;li&gt;✅ Alchemists tried to turn lead into gold.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;split-infinitives&quot;&gt;Split Infinitives&lt;/h1&gt;
&lt;p&gt;An infinitive is a verb form that is used as a noun. For example, the word &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;replicate&lt;/code&gt; is a verb. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;To replicate&lt;/code&gt; is a noun. In the sentence &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Docker enables teams to replicate&lt;/code&gt;, the verb is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;enables&lt;/code&gt; and the noun is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;to replicate&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Splitting an infinitive involves adding another word between the two, and is technically a grammatical error.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;❌ Docker enables teams to quickly replicate their development and production environments.&lt;/li&gt;
  &lt;li&gt;✅ Docker enables teams to replicate their development and production environments quickly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also correct, though less common:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;✅ Docker enables teams quickly to replicate their development and production environments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Though splitting infinitives tends to be acceptable and widespread, especially in informal writing, I recommend avoiding them in formal and business writing.&lt;/p&gt;

&lt;h1 id=&quot;contractions&quot;&gt;Contractions&lt;/h1&gt;
&lt;p&gt;Some two-word phrases are commonly abbreviated into contractions (can’t, shouldn’t, we’re, they’ll, etc.). Most of us who are not &lt;a href=&quot;https://memory-alpha.fandom.com/wiki/Verbal_contraction&quot; target=&quot;_blank&quot;&gt;Lt. Commander Data&lt;/a&gt; use them almost unconsciously.&lt;/p&gt;

&lt;p&gt;Though not a critical rule, I recommend avoiding them. Admittedly this is merely as a preference on my part. I was taught many years ago that one should avoid using contractions in professional and technical writing. Their use is considered too informal. Microsoft Word automatically flags their use when you configure your document type to any sort of professional setting.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;❌ It’s come to my attention that you’re having questions, and don’t understand why we’re doing this project.&lt;/li&gt;
  &lt;li&gt;✅ It has come to my attention that you are having questions, and do not understand why we are doing this project.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;profanity&quot;&gt;Profanity&lt;/h1&gt;
&lt;p&gt;Though it has become more acceptable in society, if you are doing any professional, business, or technical writing, try to avoid profanity of any kind.&lt;/p&gt;

&lt;p&gt;I suspect that I am much in the minority on this one, but I stand by my opinion. If you are writing a novel, things might be different.&lt;/p&gt;

&lt;h1 id=&quot;summary&quot;&gt;Summary&lt;/h1&gt;
&lt;p&gt;First impressions are lasting ones. It may not be fair, but people do judge you based on your words. Sloppy writing in a professional setting can you noticed for all the wrong reasons. You owe it to yourself to spend some time making sure that your meaning conveys your intent.&lt;/p&gt;

&lt;p&gt;If you have some favorites of your own that I have forgotten, or you believe I made any spelling or grammatical errors in this article, please let me know.&lt;/p&gt;

&lt;h1 id=&quot;resources&quot;&gt;Resources&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://www.grammarly.com&quot;&gt;Grammerly Web Site&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In addition, these two books have probably been more help to me than any other throughout my career.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://amzn.to/2B80IEf&quot;&gt;&lt;img src=&quot;https://walkingriver.com/assets/img/2019-10-14-06-59-36.png&quot; alt=&quot;Strunk and White - Elements of Style&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://amzn.to/35yeP3T&quot;&gt;&lt;img src=&quot;https://walkingriver.com/assets/img/2019-10-14-07-07-12.png&quot; alt=&quot;The Mac is Not a Typewriter&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: These are affiliate links to Amazon.com, and I could potentially receive a commission for any sales resulting from them.&lt;/em&gt;&lt;/p&gt;</content><author><name>Michael D. Callaghan</name></author><category term="writing" /><category term="spelling" /><category term="grammar" /><summary type="html">With the explosion of social media over the past decade or so, the written word is often the first, and sometimes the only, exposure other people will ever have of you. There are hundreds, perhaps thousands, of people who who will only ever know you through your writing. Fair or not, many of them will judge you on how well you express yourself. This article is an attempt to provide some simple guidance to improve your language and your writing, to make that first impression a positive one.</summary></entry><entry><title type="html">Mike’s Mics - A Simple Test of Common Microphones in Virtual Meetings</title><link href="https://walkingriver.com/mikes-mics/" rel="alternate" type="text/html" title="Mike’s Mics - A Simple Test of Common Microphones in Virtual Meetings" /><published>2021-11-08T00:00:00+00:00</published><updated>2021-11-08T00:00:00+00:00</updated><id>https://walkingriver.com/mikes-mics</id><content type="html" xml:base="https://walkingriver.com/mikes-mics/">&lt;p&gt;Like most of you, I have attended countless online virtual meetings over the past couple of years. In every one of those meetings, there is always someone who sounds truly awful. Most people, I presume, do not give much thought to how they sound when they buy a new headset. They are more concerned with the quality of their speakers. I wondered, “If others sound horrible, what do I sound like? Am I using the best microphone to make it easy for others to understand me?” These meetings are bad enough as it is; I hate to complicate them by not being understood when I speak. That is when I decided to find out.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;I tend to collect computer hardware and peripherals the way some people collect travel souvenirs. One day I realized I had more than ten different microphones and headsets sitting around my house. Some are cheap ear buds that came with a mobile phone years ago. Others I have purchased when making video courses. A few were purchased for me. The point is that I have a bunch, and I felt it was time to find out which one I should use for my virtual meetings going forward.&lt;/p&gt;

&lt;h1 id=&quot;my-testing-criteria&quot;&gt;My Testing Criteria&lt;/h1&gt;
&lt;p&gt;What I’m looking for in a microphone is simple:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;First, it has to sound good in a normal room. I don’t want to have to build a recording studio to sound decent.&lt;/li&gt;
  &lt;li&gt;It can’t cost too much money. Though one of the headsets I tested is around $300, most cost less than $100. One of them was free.&lt;/li&gt;
  &lt;li&gt;It shouldn’t be distracting to other people in the meeting. In other words, I don’t want anything that calls attention to itself. This rules out most full headsets, though I did test a few for completeness.&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;the-videos&quot;&gt;The Videos&lt;/h1&gt;
&lt;p&gt;I created a brief video for each microphone I tested. Each one is embedded below, and I have included links to the individual videos at the end of this article.&lt;/p&gt;

&lt;h1 id=&quot;my-environment&quot;&gt;My Environment&lt;/h1&gt;
&lt;p&gt;As I said, I did not want to run my tests in a recording studio, or even a room with any acoustic panels, as some recommend. Most people I see on these calls are sitting in a normal room in their homes. Therefore, I would replicate that environment as best I could. The room where I conducted my tests is a carpeted bedroom with painted walls, a bookshelf, a dresser, a desk, and a twin bed. The room has no sound deadening, and my tests have no post-processing. What you will hear is what I recorded.&lt;/p&gt;

&lt;h1 id=&quot;the-tests&quot;&gt;The Tests&lt;/h1&gt;
&lt;p&gt;Here are the tests and mini-reviews, in alphabetical order by product name.&lt;/p&gt;

&lt;h2 id=&quot;alvoxcon-usb-wireless&quot;&gt;Alvoxcon USB Wireless&lt;/h2&gt;
&lt;p&gt;If you are a regular reader of my blog posts, you may have seen my &lt;a href=&quot;https://walkingriver.com/no-zoom-deed&quot;&gt;months-long struggle to host the webinar for our weekly church services&lt;/a&gt;. I bought this microphone as the solution to my audio problems in a large open chapel. I was looking for something wireless, and the UHF transmitter and receiver seemed perfect. The little lapel mic can be connected to the speaker’s clothing, clipped to something in front of the speaker, or even plugged in directly to the transmitter. The receiver is then connected to a computer or phone. The kit comes with everything you need to connect it to a computer’s USB port, and includes adapters for Apple Lightning and USB-C ports. Most importantly, the sound quality for vocals is amazing, especially given its sub-$100 price point.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/PC1S8HmzUnw&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;amazon-echobuds-2&quot;&gt;Amazon EchoBuds 2&lt;/h2&gt;
&lt;p&gt;I got these during an Amazon sale for the sole purpose of having a headset on virtual meetings. I got them in black, hoping that would make them stand out less. For some reason, I find the white Air Pods people use to be very distracting. Ultimately, I was a little disappointed. Though the Echo Buds sound pretty good (once you fit them properly), they are simply mediocre for meetings. They still tend to fall out of my ear, battery life is only a few hours, and the microphone quality seems more designed for Alexa than for making you sound your best.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/eGEjViuNaWs&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;apple-earpods&quot;&gt;Apple EarPods&lt;/h2&gt;
&lt;p&gt;I couldn’t do a complete test without including the Apple wired EarPods that came with my iPhone 7 years ago. I like these so much I own four or five sets of them. At about $20 each, their sound quality is second-to-none in the budget space. My only complaint about them is the very white wire is visible to everyone. If you don’t mind such things, you can’t go wrong with them.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/L7SCdqjX8zA&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;apple-iphone-11&quot;&gt;Apple iPhone 11&lt;/h2&gt;
&lt;p&gt;Some people attend virtual meetings on their phones. If you are in a listen-only meeting, this might be acceptable. Please don’t do this if you need to participate, especially if you are presenting anything. The camera angle is almost never flattering, and the microphone, while passable, works best when held close to your mouth. Using your phone without a headset should be a last resort.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/RLF51T6D_2Y&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;apple-macbook-air-2020-m1&quot;&gt;Apple MacBook Air (2020 M1)&lt;/h2&gt;
&lt;p&gt;I started with the hardware I have on hand. Most people, myself included at times, will simply fire up the virtual meeting software and connect. They don’t even stop to consider that they should use a different microphone. That is why I wanted to use my daily laptop as the baseline. Overall, it doesn’t sound bad, about what you’d expect. You can definitely tell I’m in an open room.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/oC6TZnkHplI&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;apple-macbook-pro-16-inch-2019&quot;&gt;Apple MacBook Pro (16-inch, 2019)&lt;/h2&gt;
&lt;p&gt;I was on a call recently with someone who did not appear to be using a headset of any kind. I asked him later what he was using because he sounded fantastic. He told me he was simply using his MacBook Pro. I have already tested the mic on my MacBook Air and was not impressed. Maybe the MacBook Pro has better audio? I have a MacBook Pro available to me, so I decided to test it also. My results were not as good as what I heard from my colleague. My conclusion is that the Macs are very sensitive to their environment. If you’re in a quite room without a lot of hard surfaces, it might be fine. In my room, it sounded no better to me than the MacBook Air.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/l5w2R18IDF0&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;apple-thunderbolt-display&quot;&gt;Apple Thunderbolt Display&lt;/h2&gt;
&lt;p&gt;I tested the Thunderbolt Display for the same reason I tested the MacBook Air: it’s here and I know some people will default to their monitor’s built-in microphone. Though “free” (not including the cost of the monitor), unless you have no other choice, you probably want to skip this one.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/Jz3Y0py6Y_Y&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;blue-snowball&quot;&gt;Blue Snowball&lt;/h2&gt;
&lt;p&gt;This was my primary microphone when I made my early &lt;a href=&quot;https://bit.ly/ps-mike&quot;&gt;video courses for Pluralsight&lt;/a&gt;. The price seems to have come down quite a bit since then, making it an excellent choice for someone who wants better sound quality on a budget. Probably my biggest complaint about the Snowball is that it’s too sensitive. It picks up everything. I remember once having to re-record a segment of a course because the cat came into the room and curled up in a wicker basket. The microphone picked up every creak. This one works best when mounted on a swing arm, with a pop filter and shock mount. Because of that, it may be too high maintenance for some.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/mr95D_CfbuE&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;cambridge-soundworks-oontz-angle&quot;&gt;Cambridge SoundWorks Oontz Angle&lt;/h2&gt;
&lt;p&gt;I bought this inexpensive little Bluetooth speaker to have something better than my phone for music playback when working around the house. It happens to have a mic, and it’s pretty small. I thought it might be decent option if I place it on my desk right in front of me, just below the camera’s field of view. As you will see in the video, it’s pretty terrible.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/d1hkiJ5vFXk&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;depstech-webcam&quot;&gt;DepsTech WebCam&lt;/h2&gt;
&lt;p&gt;When I presented at a developer conference in the summer of 2021, I was told I needed a high-quality webcam for those attending virtually. I bought the DepsTech WebCam, which features a high-quality 4k camera and a tripod mount. These were the features I wanted for the conference. I didn’t concern myself with its microphone quality because I knew I wouldn’t be using it. Now that I’m back in my home office, I keep it mounted above my desk so that it’s at eye-level when I stand. This makes it perfect for me to do remote presentations. I learned while performing this test that its audio isn’t up to the same standards as its video.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/O4rfyhJcJs4&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;plantronics-voyager-8200&quot;&gt;Plantronics Voyager 8200&lt;/h2&gt;
&lt;p&gt;A few years ago, we moved to an “open office” concept at my day job. We quickly discovered just how loud such places can be. To mitigate this somewhat, our director bought us each this bluetooth noise cancelling headset. I used it for months to isolate myself from the noisy office. However, I remember being on a call one day when I noticed someone using the same headset. He was almost unintelligible. After that, I stopped using it as a microphone. I still use it as headphones in noisy environments, and take it with me whenever I fly. This is the most expensive headset I tested. I only included it because I already happened to have one.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/GVSJ9TjRnfs&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;rode-video-me&quot;&gt;Rode Video Me&lt;/h2&gt;
&lt;p&gt;My wife often makes audio recordings of our daughter’s high school band concerts, and wanted something that would sound better than her Galaxy phone’s built-in mic. The Rode Video Me seemed perfect for that task. It is an inexpensive and wonderful little directional mic that clips right onto the phone. It includes a big puffy wind filter, which I did not use for the test. I also didn’t use a mobile phone. Instead, I connected it directly to the microphone jack of my laptop. Its sound quality was near the top end of all the microphones I tested.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/aOdgir9M9_o&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;senheiser-sc60&quot;&gt;Senheiser SC60&lt;/h2&gt;
&lt;p&gt;My other video course mic is this Senheiser headset. I bought it to replace the Blue Snowball, not because I didn’t like it, but because my house is simply too noisy. The sound quality is superb, and it picks up almost nothing that’s going on behind you. I once recorded a video while a loud light and music show was taking place just outside my window. My recording contained none of those sounds. The Senheiser has two issues that keep me from choosing it as the overall winner. Fist, it has to be positioned really close to your mouth, so it tends to pick up your breathing if you aren’t careful. The second is that I don’t like seeing headsets on video conferences.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/ZDKwbmOzpdE&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h1 id=&quot;future-tests&quot;&gt;Future Tests&lt;/h1&gt;
&lt;p&gt;There are a few more mics I want to test, but I simply haven’t gotten hold of them yet. I was on a call with someone the other day who was using Samsung Galaxy Buds, and from what I could tell, they were phenomenal. I have also heard good things about the Beats Flex. I may want to do my own test on them some time.&lt;/p&gt;

&lt;p&gt;In case you’re wondering, I have no plans to test the Apple AirPods. I have been on too many calls with people using them, and quite frankly they don’t sound that good.&lt;/p&gt;

&lt;p&gt;That said, I’m open to reviewing any microphone that someone wants to send me (and no, I don’t expect you to let me keep them).&lt;/p&gt;

&lt;h1 id=&quot;results&quot;&gt;Results&lt;/h1&gt;
&lt;p&gt;I wanted to break down the results into something manageable. It wasn’t as simple as picking one winner. Most of the microphones I tested had something going for and against them.&lt;/p&gt;

&lt;h2 id=&quot;best-overall-blue-snowball&quot;&gt;Best Overall: Blue Snowball&lt;/h2&gt;
&lt;p&gt;This was a hard decision to make. I used the Senheiser for all my recent video courses. I really like its sound quality, and I have never found anything better when it comes to eliminating background noise. That said, for virtual meetings, I have to go with the Blue Snowball.&lt;/p&gt;

&lt;h3 id=&quot;runner-up-the-senheiser-sc700&quot;&gt;Runner Up: The Senheiser SC700&lt;/h3&gt;
&lt;p&gt;If it weren’t for the fact that I don’t want to wear a headset during meetings, this one might have been my top pick.&lt;/p&gt;

&lt;h2 id=&quot;best-budget-apple-earpods&quot;&gt;Best Budget: Apple EarPods&lt;/h2&gt;
&lt;p&gt;Honestly, I don’t think you can go wrong with the original Apple EarPods. For around $20, you get excellent sound quality, no fit hassles for most people, and you never have to worry about batteries. Even better, you probably have one sitting in a drawer somewhere, maybe even unopened!&lt;/p&gt;

&lt;h3 id=&quot;runner-up-rode-video-me&quot;&gt;Runner Up: Rode Video Me&lt;/h3&gt;
&lt;p&gt;I want to try testing this one again, but mounted on an arm and using its wind filter. I get the feeling it can be a contender for the top spot.&lt;/p&gt;

&lt;h2 id=&quot;best-wireless-alvoxcon-usb-wiress&quot;&gt;Best Wireless: Alvoxcon USB Wiress&lt;/h2&gt;
&lt;p&gt;The quality of this system’s lapel mic surprised me. It is a very directional microphone, which leads to excellent background noise reduction. Plus, it comes with everything you need to connect to a computer or any mobile device.&lt;/p&gt;

&lt;h3 id=&quot;runner-up-amazon-echobuds-2&quot;&gt;Runner Up: Amazon EchoBuds 2&lt;/h3&gt;
&lt;p&gt;Let’s face it. I didn’t test too many wireless headsets. I expect this will change once I test others.&lt;/p&gt;

&lt;h2 id=&quot;avoid-at-all-costs-plantronics-voyager-8200&quot;&gt;Avoid at All Costs: Plantronics Voyager 8200&lt;/h2&gt;
&lt;p&gt;The worst headset I tested was also the most expensive. Though it sounds great and has amazing noise cancellation features, its microphone is almost useless.&lt;/p&gt;

&lt;h3 id=&quot;runner-up-depstech-webcam&quot;&gt;Runner Up: DepsTech WebCam&lt;/h3&gt;
&lt;p&gt;I don’t think microphone quality it a top priority for webcam manufacturers. At least it isn’t in this case.&lt;/p&gt;

&lt;h1 id=&quot;direct-links-to-videos&quot;&gt;Direct Links to Videos&lt;/h1&gt;
&lt;p&gt;For convenience, I am including links to each of the above videos, all in one place.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://youtu.be/PC1S8HmzUnw&quot;&gt;Alvoxcon USB Wireless&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://youtu.be/eGEjViuNaWs&quot;&gt;Amazon EchoBuds 2&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://youtu.be/L7SCdqjX8zA&quot;&gt;Apple EarPods&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://youtu.be/RLF51T6D_2Y&quot;&gt;Apple iPhone 11&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://youtu.be/oC6TZnkHplI&quot;&gt;Apple MacBook Air (2020 M1)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.youtu.be/l5w2R18IDF0&quot;&gt;Apple MacBook Pro (16-inch, 2019)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://youtu.be/Jz3Y0py6Y_Y&quot;&gt;Apple Thunderbolt Display&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://youtu.be/mr95D_CfbuE&quot;&gt;Blue Snowball&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://youtu.be/d1hkiJ5vFXk&quot;&gt;Cambridge SoundWorks Oontz Angle&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://youtu.be/O4rfyhJcJs4&quot;&gt;DepsTech WebCam&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://youtu.be/GVSJ9TjRnfs&quot;&gt;Plantronics Voyager 8200&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://youtu.be/aOdgir9M9_o&quot;&gt;Rode Video Me&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://youtu.be/ZDKwbmOzpdE&quot;&gt;Senheiser SC60&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;follow-me-on-twitter&quot;&gt;Follow Me on Twitter&lt;/h1&gt;
&lt;p&gt;If you want to know when I test more microphones, my Twitter followers will be the first people I tell. You can find me there at &lt;a href=&quot;https://twitter.com/walkingriver&quot;&gt;@WalkingRiver&lt;/a&gt;.&lt;/p&gt;</content><author><name>Michael D. Callaghan</name></author><category term="Zoom" /><category term="Virtual Meetings" /><category term="Microphones" /><summary type="html">Like most of you, I have attended countless online virtual meetings over the past couple of years. In every one of those meetings, there is always someone who sounds truly awful. Most people, I presume, do not give much thought to how they sound when they buy a new headset. They are more concerned with the quality of their speakers. I wondered, “If others sound horrible, what do I sound like? Am I using the best microphone to make it easy for others to understand me?” These meetings are bad enough as it is; I hate to complicate them by not being understood when I speak. That is when I decided to find out.</summary></entry><entry><title type="html">No Good (Zoom) Deed Goes Unpunished</title><link href="https://walkingriver.com/no-zoom-deed/" rel="alternate" type="text/html" title="No Good (Zoom) Deed Goes Unpunished" /><published>2021-11-02T00:00:00+00:00</published><updated>2021-11-02T00:00:00+00:00</updated><id>https://walkingriver.com/no-zoom-deed</id><content type="html" xml:base="https://walkingriver.com/no-zoom-deed/">&lt;p&gt;Earlier this year the leader of our church congregation asked me to take over the Zoom meetings for our weekly services. We meet in person, but some of our members still do not feel comfortable gathering indoors. The church pays for a Zoom license, so we set up a recurring webinar for them. All I need to do each week is provide a device, log on, start the Zoom meeting, and monitor the chat for any issues. I’m a technical guy. I can manage a Zoom meeting. It sounded like a simple enough assignment, so I agreed. It has been anything but simple, but I’ve learned a lot along the way, some of which may be useful to others.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h1 id=&quot;what-device&quot;&gt;What Device?&lt;/h1&gt;
&lt;p&gt;At first, we attached an iPhone to one of those small, flexible tripod, and placed it on the podium. If you have ever attended a Zoom meeting where someone props their phone at a 45-degree angle looking up at them, you can imagine how great that looked. In fact, you need not imagine. It looked a lot like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://walkingriver.com/assets/img/mike-stone-wall.jpg&quot; alt=&quot;Mike in front of a stone wall&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I decided that if they wanted me to run these webinars, I was going to do something about that horrible angle.&lt;/p&gt;

&lt;h1 id=&quot;fix-the-angle&quot;&gt;Fix the Angle&lt;/h1&gt;
&lt;p&gt;To do that, I knew that the camera needed to be much farther back in the room. I tried mounting it higher, but we realized quickly that it would be a distraction. I remembered having an older tripod sitting at home unused in a closet, a relic from my 35mm Nikon days.&lt;/p&gt;

&lt;p&gt;For the next few weeks, I mounted my iPhone 11 on the tripod at the back of the chapel. It was easy enough, but it introduced a few new wrinkles. First, the iPhone’s digital zoom is OK, and no one attending remotely complained. The angle was fixed, but the video wasn’t as good. Second, the mic was now about 50 feet from the front of the room, so we were picking up the building’s PA system to hear the speaker, along with all the background noise (A/C units, people coughing, children fussing, etc.).&lt;/p&gt;

&lt;h1 id=&quot;fix-the-audio&quot;&gt;Fix the Audio&lt;/h1&gt;
&lt;p&gt;Ok, so how to fix the audio? Well, we still had that other iPhone we had been using. Maybe we could connect both phones to the Zoom webinar and make them co-hosts. The iPhone on the podium would mute its camera and unmute its mic. My iPhone would do the opposite. We quickly found the flaw in that plan.&lt;/p&gt;

&lt;p&gt;I expected synchronization issues with the audio and video from the separate devices, but no one ever complained about that. The problem was control.&lt;/p&gt;

&lt;p&gt;For a variety of reasons, parts of the service require us to mute the audio and video of the webinar. We soon discovered that one device, even when logged in as the webinar host, cannot completely control the audio and video mute settings of another device. We could mute each other, but not unmute. Also, I discovered that when I turn on the camera, it resets to 1x zoom. This meant that I had to jump up and re-zoom, while someone at the front of the room had to stand up and unmute that iPhone.&lt;/p&gt;

&lt;p&gt;This was not the fix we hoped it would be.&lt;/p&gt;

&lt;h1 id=&quot;more-professional-gear&quot;&gt;More Professional Gear&lt;/h1&gt;
&lt;p&gt;I got permission to spend a little bit of money to see if I could find some better gear than the two iPhones. I researched web cameras and wireless microphone systems. At first, I was looking for something I could add to my iPhone. Then I found out that Apple does not condone external cameras on iPhones. Well, I had a computer I could use, so I shifted my search to something more traditional. I had never heard of a USB camera with an optical zoom lens, but figured it couldn’t hurt to look around. I found a bunch.&lt;/p&gt;

&lt;h2 id=&quot;alpcam-8mp-webcam&quot;&gt;ALPCAM 8MP Webcam&lt;/h2&gt;
&lt;p&gt;After a little research, I decided I would try an ALPCAM Webcam, a USB camera which includes an optical zoom lens.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.amazon.com/gp/product/B08FX15MJY?ie=UTF8&amp;amp;th=1&amp;amp;linkCode=li2&amp;amp;tag=twitter-std-20&amp;amp;linkId=47b5e1303ff8d87dca61c9072426d1fd&amp;amp;language=en_US&amp;amp;ref_=as_li_ss_il&quot; target=&quot;_blank&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;//ws-na.amazon-adsystem.com/widgets/q?_encoding=UTF8&amp;amp;ASIN=B08FX15MJY&amp;amp;Format=_SL160_&amp;amp;ID=AsinImage&amp;amp;MarketPlace=US&amp;amp;ServiceVersion=20070822&amp;amp;WS=1&amp;amp;tag=twitter-std-20&amp;amp;language=en_US&quot; /&gt;&lt;/a&gt;&lt;img src=&quot;https://ir-na.amazon-adsystem.com/e/ir?t=twitter-std-20&amp;amp;language=en_US&amp;amp;l=li2&amp;amp;o=1&amp;amp;a=B08FX15MJY&quot; width=&quot;1&quot; height=&quot;1&quot; border=&quot;0&quot; alt=&quot;&quot; style=&quot;border:none !important; margin:0px !important;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It uses a standard screw-type tripod mount, so would fit on my tripod without issue. When it arrived, I connected it to an older laptop I had lying around, fired up the Zoom webinar, and tested it out. The camera has manual zoom, manual focus, and manual brightness. Pointing it out the window allowed me to test all three to my satisfaction.&lt;/p&gt;

&lt;h2 id=&quot;alvoxcon-usb-lapel-mic-system&quot;&gt;Alvoxcon USB Lapel Mic System&lt;/h2&gt;
&lt;p&gt;At the same time, I still needed to solve the audio problems, too. It occurred to me that if I were going to bring a laptop with a camera, I might as well connect an external mic to it, also. I had used wireless lapel mics before, so I had some idea what I was looking for. After a bit of reading, I found the Alvoxcon USB Lapel Mic System.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.amazon.com/gp/product/B08L52SM66?ie=UTF8&amp;amp;psc=1&amp;amp;linkCode=li3&amp;amp;tag=twitter-std-20&amp;amp;linkId=63a6d773508483810dffd7ca5571da89&amp;amp;language=en_US&amp;amp;ref_=as_li_ss_il&quot; target=&quot;_blank&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;//ws-na.amazon-adsystem.com/widgets/q?_encoding=UTF8&amp;amp;ASIN=B08L52SM66&amp;amp;Format=_SL250_&amp;amp;ID=AsinImage&amp;amp;MarketPlace=US&amp;amp;ServiceVersion=20070822&amp;amp;WS=1&amp;amp;tag=twitter-std-20&amp;amp;language=en_US&quot; /&gt;&lt;/a&gt;&lt;img src=&quot;https://ir-na.amazon-adsystem.com/e/ir?t=twitter-std-20&amp;amp;language=en_US&amp;amp;l=li3&amp;amp;o=1&amp;amp;a=B08L52SM66&quot; width=&quot;1&quot; height=&quot;1&quot; border=&quot;0&quot; alt=&quot;&quot; style=&quot;border:none !important; margin:0px !important;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This system consists of a small lapel microphone, a rechargeable UHF transmitter, and a wireless USB-powered receiver. Now I could plug the camera and receiver into two USB ports and manage the entire webinar from a laptop in the back of the room. Because everything is connected to my laptop, I could control the audio and video muting entirely, without being a distraction.&lt;/p&gt;

&lt;p&gt;All I had to do was arrive a little early to get everything set up. I clip the lapel mic to the stalk of the boom mic already at the podium, wrap the wire around the mic stalk, clip the transmitter unobtrusively on the edge of the podium, turn it on, and walk away. Everything else is controlled entirely from my laptop.&lt;/p&gt;

&lt;p&gt;I recently did a short review of this microphone, which you can see and hear below.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/PC1S8HmzUnw&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;For about $120, I solved all my problems (or so I thought at the time).&lt;/p&gt;

&lt;h1 id=&quot;the-test&quot;&gt;The Test&lt;/h1&gt;
&lt;p&gt;The Wednesday evening after the mic and camera arrived, I took the laptop to the church building to try to test it in the actual location. Though there were people in the building (Wednesday is our youth night), I had the chapel to myself. After positioning the camera and connecting everything together, I started the Zoom webinar and began testing it out. The laptop felt sluggish. I knew Zoom used a lot of resources, but this poor laptop simply couldn’t keep up. I grabbed my iPhone and connected to the webinar to see how bad it was. My conservative estimate is that I was getting about 5-10 frames per second at best, and the audio was dropping intermittently. This laptop would not work, and I only had a few days to come up with an alternative.&lt;/p&gt;

&lt;h1 id=&quot;macbook-pro&quot;&gt;MacBook Pro&lt;/h1&gt;
&lt;p&gt;Fortunately, I have access to an i9 MacBook Pro that I could borrow. Even Zoom wouldn’t be able to bog it down! Knowing that this was only a temporary solution, I showed up the following Sunday with all my new gear and the borrowed MacBook. As before, I got there early to get it all set up before people started to arrive.&lt;/p&gt;

&lt;p&gt;To my tremendous relief, everything went off without a hitch! The video was solid and the audio sounded better than ever before.&lt;/p&gt;

&lt;h1 id=&quot;attendance&quot;&gt;Attendance&lt;/h1&gt;
&lt;p&gt;It wasn’t long before someone asked me to report on who attended the webinar. The leadership wanted to know whether anyone was attending our services remotely. If not, then running the webinar didn’t make much sense. It is simple to look at the particpant count, but we weren’t sure how many people were watching behind each name on the list.&lt;/p&gt;

&lt;p&gt;Could I get a more accurate attendance count?&lt;/p&gt;

&lt;p&gt;I discussed the matter with our church’s clerk and we came up with a solution. We would use the polling feature built into Zoom to ask people to self-report how many were watching with them. Each week at the start of the meeting, I display the poll to everyone watching virtually. It is a single-question poll with 10 radio buttons labeled 1 to 10+. At the end of the meeting, I take a screenshot of the poll results and forward that to the clerk.&lt;/p&gt;

&lt;p&gt;Problem solved! Until the next problem, of course.&lt;/p&gt;

&lt;h1 id=&quot;wifi-issues&quot;&gt;WiFi Issues&lt;/h1&gt;
&lt;p&gt;One Sunday, I found I had no internet on the church’s WiFi system. I could connnect to the router, but Zoom wouldn’t connect at all and no internet sites would load.&lt;/p&gt;

&lt;h2 id=&quot;verizon-hotspot&quot;&gt;Verizon Hotspot&lt;/h2&gt;
&lt;p&gt;Fortunately, my Verizon Wireless plan included mobile hotspot, so I turned it on and got back online. It’s situations like these that cause me to show up early each week. Once the laptop was connected to the phone’s hotspot, I was quickly able to get back online, connect to Zoom, and get the meeting going. No one but me even noticed.&lt;/p&gt;

&lt;h1 id=&quot;macbook-air&quot;&gt;MacBook Air&lt;/h1&gt;
&lt;p&gt;When I first agreed to run the weekly Zoom webinar, I really thought it would only be for a few weeks, maybe a month or two at most. As you can probably imagine from my story so far, it had stretched out to be a much longer assignment.&lt;/p&gt;

&lt;p&gt;At this point, I realized that I couldn’t keep borrowing that MacBook Pro. I experimented with my wife’s laptop, but it too had issues with Zoom at the church. I never figured out why. The Mac, on the other hand, worked perfectly. Earlier this year, I purchased a brand new Mac mini with the Apple M1 chip. It handled everything I threw at it and I knew that it would be perfect, if not for one thing. The Mac mini is tiny, but it isn’t quite a portable machine. It has no monitor or keyboard, so I ruled out using that. I really needed a laptop for this.&lt;/p&gt;

&lt;p&gt;I realized that I would have been better off with an M1 MacBook Air, but was well past my 30-day return window with Apple. Selling the 7-month-old Mac mini and getting an Air would cost me about $700-1000 net, depending on what the mini was worth. That isn’t the kind of cash I can usually throw around without some serious thought.&lt;/p&gt;

&lt;p&gt;I remember thinking that if I’m doing this for the church, maybe I could some divine intervention.&lt;/p&gt;

&lt;h2 id=&quot;app-sumo&quot;&gt;App Sumo&lt;/h2&gt;
&lt;p&gt;A few nights later, I received an email from an unfamiliar source. The email stated that $3000 US had just been deposited into my account. I nearly deleted it, assuming it was a scam of some sort. The next morning I looked at the email more closely and realized what it was.&lt;/p&gt;

&lt;p&gt;AppSumo.com had been running a promotion, trying to get more creative content into their catalog. They were &lt;a href=&quot;https://finance.yahoo.com/news/appsumo-launches-1m-black-friday-190300052.html&quot;&gt;offering $1000 each to the first 400 new products added&lt;/a&gt;. I had added three of my eBook titles to their catalog and then thought no further of it. It appeared that those books were in the first 400, so they had sent me $3000 as promised. That was more than enough to get the MacBook Air, which I ordered and picked up the very next day.&lt;/p&gt;

&lt;p&gt;By the following Sunday, I had everything configured to run the Zoom webinar on the new laptop.&lt;/p&gt;

&lt;h1 id=&quot;no-more-hotspot&quot;&gt;No More Hotspot&lt;/h1&gt;
&lt;p&gt;Everything worked well for a few more weeks, until my son asked to take advantage of a recent Verizon deal to get himself a new phone. Supposedly, you sign up for a new line, pick your phone, and then get a monthly rebate equal to the monthly payment of the phone over two years. In theory, this would make the new phone free — except that it really doesn’t.&lt;/p&gt;

&lt;p&gt;What happened instead was that the other four phones on the account, plus the new one for my son, caused me to lose the existing wireless plan I had been locked into. Suddenly, I was bumped into a higher priced plan with no hotspot capability. This new phone and line increased my monthly bill by more than $100, with fewer features than I had before. Two weeks of back and forth with Verizon customer service yielded zero results.&lt;/p&gt;

&lt;p&gt;This also meant two weeks without internet at church, so I ended up begging someone else in the congregation to use his phone as my hotspot.&lt;/p&gt;

&lt;p&gt;I knew there had to be a better option.&lt;/p&gt;

&lt;h1 id=&quot;us-mobile&quot;&gt;US Mobile&lt;/h1&gt;
&lt;p&gt;I found a solution at US Mobile, which is where my son had his mobile plan prior to the Verizon fiasco. At the end of a fruitless appeal to their customer service (they would not return me to my prior plan), I canceled my wireless plan in total frustration. That same day (it was a Saturday), I signed up with &lt;a href=&quot;usmobile.com&quot;&gt;US Mobile&lt;/a&gt;. They are an MVNO (Mobile Virtual Network Operator) that resells access to the Verizon Wireless network. Their unlimited plan is only $25/mo for 3 or more phones (per line, tax included), which is less than I had been paying Verizon before my disaster.&lt;/p&gt;

&lt;p&gt;The only drawback is that mobile hotspot is an extra $10/mo per line for 20GB. As it turns out, though, I only need one of my lines to have that. So, now I’m looking at a total of $110/mo for 4 phones with unlimited talk, text, 5G data, and 20GB of hotspot.&lt;/p&gt;

&lt;p&gt;The Verizon disaster turned out working in my favor. If you’re interested in learning more, Android Central has a &lt;a href=&quot;https://www.androidcentral.com/mint-mobile-vs-us-mobile&quot;&gt;comprehensive review of US Mobile and Mint Mobile&lt;/a&gt;. Should you decide you’d like to try US Mobile, send me a &lt;a href=&quot;https://twitter.com/walkingriver&quot;&gt;DM on Twitter&lt;/a&gt; and I’ll get you a referral code for $10 off your first bill.&lt;/p&gt;

&lt;h1 id=&quot;slideshow&quot;&gt;Slideshow&lt;/h1&gt;
&lt;p&gt;With my hardware and internet problems apparently solved, I could again focus on the assignment itself, which was simply hosting the weekly webinar and relaying the self-reported virtual attendance.&lt;/p&gt;

&lt;p&gt;I mentioned earlier that there is a period of the meeting where I am required to mute the audio and video. The remote viewers of the meeting have become accustomed to seeing a black screen with a tiny logo in the center. One day it occurred to me that I could easily download a few appropriate images, share my screen, and provide a modest slideshow during this time. So, that’s what I did. I ended up with folder of about 30 images. In practice, I only show just one picture per meeting. Our leadership thought that an actual slideshow might be distracting. I am not entirely convinced, but it’s not my decision, and I tend to do what I’m told in these situations.&lt;/p&gt;

&lt;h1 id=&quot;hot-mic&quot;&gt;Hot Mic&lt;/h1&gt;
&lt;p&gt;Remember before when I said I clip the wireless microphone to the mic at the podium? Well, the leaders conducting the meeting control the power to the mic in the chapel itself, but I control my mic. One week, about ten minutes before our service began, I got a message over the Zoom chat from a remote viewer. She informed me that she could hear everything being said by the people behind the podium and asked whether they knew they were on a “hot mic,” as it were.&lt;/p&gt;

&lt;p&gt;One thing I don’t do often enough is monitor the audio, especially before the meeting starts. I quickly sent a text to the leaders warning them of the hot mic. Fortunately, they weren’t saying anything that shouldn’t be overheard.&lt;/p&gt;

&lt;p&gt;I’ve seen enough political gafffes on TV to know that even innocent comments can be misunderstood or taken the wrong way, so I needed to rectify this problem. I could mute the mic until the meeting starts, of course, but we have someone playing prelude music, and I was asked to make sure the remote viewers can hear it. Well, what if I provided my own music?&lt;/p&gt;

&lt;p&gt;Not really me, of course. I don’t play music, can’t sing, and can’t even carry a tune. But our church has plenty of pre-recorded music that would be totally appropriate to use as prelude music.&lt;/p&gt;

&lt;p&gt;I spent about an hour locating and downloading this music and creating a “Prelude Music” playlist in Apple Music on my MacBook.&lt;/p&gt;

&lt;p&gt;As soon as I start the Zoom webinar, about 15 minutes before services begin, I mute the wireless mic, open Apple Music, shuffle that playlist, and share my screen. When the services begin, I have asked whomever is conducting that week to give me a few seconds’ heads-up so that I can turn off the sharing and unmute the mic. So far, so good.&lt;/p&gt;

&lt;h1 id=&quot;summary&quot;&gt;Summary&lt;/h1&gt;
&lt;p&gt;And that brings us to today. I don’t know what the next challenge will be, or how I will solve it. But that’s what has made this so fascinating, at least to me.&lt;/p&gt;

&lt;p&gt;What began as a simple request to start and stop a Zoom webinar turned into a months’ long quest for virtual meeting perfection. I don’t think I’ve quite attained that milestone, but over all it’s been an interesting experience. The amazing thing is that after all this, I have no regrets.&lt;/p&gt;

&lt;p&gt;My current webinar configuration now looks like this:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;iPhone 11 w/WiFi hotspot enabled&lt;/li&gt;
  &lt;li&gt;USB Web Cam w/optical zoom lens&lt;/li&gt;
  &lt;li&gt;Tripod mount&lt;/li&gt;
  &lt;li&gt;Wireless, UFH microphone system&lt;/li&gt;
  &lt;li&gt;MacBook Air (2020 M1)&lt;/li&gt;
  &lt;li&gt;Slideshow of images to show during audio/video mute&lt;/li&gt;
  &lt;li&gt;Apple Music with preselected hymns for prelude music&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, I took this picture a while ago to show what it all looks like from my perspective.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://walkingriver.com/assets/assets/img/chapel-av-setup.jpg&quot; alt=&quot;My Zoom AV setup&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Zoom and the Zoom logo are trademarks of Zoom Video Communications, Inc.&lt;/p&gt;</content><author><name>Michael D. Callaghan</name></author><category term="Zoom" /><category term="Virtual Meetings" /><category term="Webinar" /><summary type="html">Earlier this year the leader of our church congregation asked me to take over the Zoom meetings for our weekly services. We meet in person, but some of our members still do not feel comfortable gathering indoors. The church pays for a Zoom license, so we set up a recurring webinar for them. All I need to do each week is provide a device, log on, start the Zoom meeting, and monitor the chat for any issues. I’m a technical guy. I can manage a Zoom meeting. It sounded like a simple enough assignment, so I agreed. It has been anything but simple, but I’ve learned a lot along the way, some of which may be useful to others.</summary></entry><entry><title type="html">An Exercise Routine for the Sedentary Developer</title><link href="https://walkingriver.com/exercise-for-developer/" rel="alternate" type="text/html" title="An Exercise Routine for the Sedentary Developer" /><published>2021-10-18T00:00:00+00:00</published><updated>2021-10-18T00:00:00+00:00</updated><id>https://walkingriver.com/exercise-for-developer</id><content type="html" xml:base="https://walkingriver.com/exercise-for-developer/">&lt;p&gt;Sitting behind a desk all day is a typical part of a software developer’s life. It is easy to become lazy and sedentary, which describes me over most of my career. That all changed in 2020. The COVID pandemic  brought on a new era of remote work. Not having a daily commute gave me the chance to come up with a new morning routine.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;&lt;img src=&quot;https://walkingriver.com/assets/img/routine.png&quot; alt=&quot;Daily bike ride routine&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;some-warnings&quot;&gt;Some warnings&lt;/h1&gt;
&lt;p&gt;Before I begin, I want to point out some things that should be obvious.&lt;/p&gt;

&lt;p&gt;First, you should never take diet or exercise advice from a software developer, particularly one you have never met. This stuff worked for me, to accomplish the things I set out to do. I share the stories below as a means to inspire. As always, seek professional advice before making any significant life changes.&lt;/p&gt;

&lt;p&gt;Second, if your purpose in exercising is specifically to lose weight, you will probably be severely disappointed. I learned a long time ago that exercise primarily builds muscle. Contrary to popular belief, it does not directly lead to weight loss. Indirectly, it may help you lose weight simply because it changes your attitude towards your health. You may find yourself eating better.&lt;/p&gt;

&lt;p&gt;On the other hand, if you overdo the exercise, you could end up being more hungry, which will lead to overeating.&lt;/p&gt;

&lt;h1 id=&quot;early-attempts&quot;&gt;Early Attempts&lt;/h1&gt;

&lt;h2 id=&quot;tennis-anyone&quot;&gt;Tennis anyone?&lt;/h2&gt;
&lt;p&gt;A few years ago, when I lived in Central New Hampshire, any sort of regular exercise was difficult. The long New England winters do not cooperate. One school year, during the fall and following spring, I was able to take 2-hour lunches and play tennis most weekdays with my wife. We would drop our daughter at afternoon kindergarten, go to the courts, play for a while, and then go home. After quick meal and shower, I would be back at my desk and work until dinner.&lt;/p&gt;

&lt;p&gt;Once the snows hit, tennis became impossible. I looked for indoor courts, but they would have required a long drive and a hefty membership fee. There were indoor pools, but also relatively expensive and more than 30-minutes away by car.&lt;/p&gt;

&lt;h2 id=&quot;maybe-a-treadmill&quot;&gt;Maybe a treadmill?&lt;/h2&gt;
&lt;p&gt;At one point, I bought a used treadmill and an original XBox. I had an old 13” color TV sitting in the basement, so I hooked it up to the XBox and placed it just in front of the treadmill. I used that treadmill for about an hour every morning for a year. I finished Splinter Cell and Splinter Cell 2, and ran through countless seasons of Madden NFL.&lt;/p&gt;

&lt;p&gt;One year into this routine, I was stronger and slightly fitter, but had lost no weight and no inches off my waist. The only physical improvement I noticed was that our next Disney trip was far easier on my feet and legs.&lt;/p&gt;

&lt;h2 id=&quot;couch-to-5k&quot;&gt;Couch to 5k&lt;/h2&gt;
&lt;p&gt;When I left New Hampshire and moved to Florida, I again decided that I needed to be more physically active. I downloaded a Couch-to-5k app on my phone and started following it. If you are not aware of how they work, it is pretty simple. On the first day, you walk for 5 minutes, then run for 30 seconds, then walk, then run, etc. Every few days, the ratio of walking to running decreases. Eventually, if you follow the program as designed, and complete the entire thing, you should be able to run a 5k in about six weeks. What I discovered is that my middle-aged knees simply could not handle even that modest level of abuse. After the end of the second week, they hurt so bad I could not walk properly for a month.&lt;/p&gt;

&lt;p&gt;I took a break from exercise for the next few years.&lt;/p&gt;

&lt;h1 id=&quot;what-finally-worked&quot;&gt;What finally worked?&lt;/h1&gt;

&lt;h2 id=&quot;covid&quot;&gt;COVID&lt;/h2&gt;
&lt;p&gt;When the COVID pandemic struck in March 2020 and we were all told to work from home. I decided to try to create another exercise routine. The Central Florida town where I live has miles of walking and biking trails, so I figured that is where I would start.&lt;/p&gt;

&lt;h2 id=&quot;goals&quot;&gt;Goals&lt;/h2&gt;
&lt;p&gt;I came up with a few goals I wished to achieve:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;It has to be interesting enough that I want to do it.&lt;/li&gt;
  &lt;li&gt;It has to be low-impact to protect my knees.&lt;/li&gt;
  &lt;li&gt;It needs to be year-round (easier in Florida).&lt;/li&gt;
  &lt;li&gt;Eventually, I want to be able to jog a mile, which means strengthening my knees.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;audio-books&quot;&gt;Audio books&lt;/h2&gt;
&lt;p&gt;For the first, I re-activated my audible.com account and downloaded a bunch of audio books from my wishlist. I have listened to audio books and podcasts for decades during my morning and evening commutes. One of the few drawbacks to working from home is that I no longer have that time in the car, so my audio book habit dwindled. I vowed to myself only to listen to the books during my exercise routine, which provided me an extra incentive.&lt;/p&gt;

&lt;h2 id=&quot;bicycling&quot;&gt;Bicycling&lt;/h2&gt;
&lt;p&gt;Next, I tuned up my bicycle and made sure it was road/trail worthy. There are not many exercises  lower-impact than bicycling. I selected a decent roundtrip path that would take me 30-45 minutes, and decided to start the next day.&lt;/p&gt;

&lt;p&gt;My year-round requirement is mostly easy to manage in Florida. It would be harder in colder climates. Florida summers can be rough, however, so my routine starts a few minutes before sunrise.&lt;/p&gt;

&lt;h2 id=&quot;cold-weather&quot;&gt;Cold Weather&lt;/h2&gt;
&lt;p&gt;Even in Florida, morning temperatures in the winter months can be in the 30s and 40s. Add the wind-chill from bike riding at 10-15 mph, and it can be downright unpleasant. So, when the weather turns cooler, I shift to my goal. I decided that when it is chilly in the morning, I would simply wear something a bit warmer and then walk and run.&lt;/p&gt;

&lt;p&gt;I still budget 30-45 minutes, but I walk for most of the route. It had occurred to me that the problem with my knees might have been related to the hard surface I was using and my shoes. I bought a pair of new running shoes and decided not to run on the road or sidewalk. Most of our town trails are wooden boardwalks, and they provide quite a bit more cushion than any of the hard surfaces.&lt;/p&gt;

&lt;h2 id=&quot;the-routine&quot;&gt;The Routine&lt;/h2&gt;
&lt;p&gt;Every morning I start my trip by walking briskly for about 5-7 minutes, working my way to the nearest boardwalk. I try to maintain a speed of at least 3.5 mph, which I track with my Apple Watch. When I get to the boardwalk, I begin to jog.&lt;/p&gt;

&lt;p&gt;On the very first day, I doubt I ran more than 60 seconds before I had to stop, completely out of breath. Then I walked until I could breathe normally. As soon as I was able, I went back to a jog. However long I manage to jog, I make sure I am paying attention to the way my feet strike the surface to protect my knees. I also try to keep my breathing deep and controlled. As soon as either my lungs or knees start to feel uncomfortable, I reduce my speed to a walk. I repeated this pattern for the better part of a month. My pace and ability slowly improved.&lt;/p&gt;

&lt;p&gt;On some mornings, when it feels warm enough, I choose to ride my bike. On cooler days, I walk and run instead. Then one day it struck me. I do not remember when the change happened, but suddenly it was no longer a question about whether I would go outside and do something, but instead it was a question of what exercise I would do that day. I had established a pattern and habit of exercise.&lt;/p&gt;

&lt;p&gt;Though I am not yet able to run that mile, I am getting closer. My watch keeps track of my average speed and time, both of which continue to improve.&lt;/p&gt;

&lt;h1 id=&quot;for-amusement&quot;&gt;For amusement&lt;/h1&gt;
&lt;p&gt;Around the summer of 2020, one of our executives thought it would be fun for us to share any new routines we created during the pandemic. Always being a bit of a jokester, I grabbed a camera and put together a short clip of my morning bike ride, but with a surprise ending. It is only a minute long. I trust it will give you a mild chuckle.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/u0zcLjKdDmU&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h1 id=&quot;summary&quot;&gt;Summary&lt;/h1&gt;
&lt;p&gt;I hope you enjoyed this brief look at how I finally created an exercise routine I both enjoy and can do consistently. I have not lost any weight yet, but I am stronger, can run and walk for longer periods of time, and I feel a lot better.&lt;/p&gt;

&lt;p&gt;Now I need to do something about my atrocious diet.&lt;/p&gt;</content><author><name>Michael D. Callaghan</name></author><category term="Exercise" /><category term="Routine" /><category term="Developers" /><summary type="html">Sitting behind a desk all day is a typical part of a software developer’s life. It is easy to become lazy and sedentary, which describes me over most of my career. That all changed in 2020. The COVID pandemic brought on a new era of remote work. Not having a daily commute gave me the chance to come up with a new morning routine.</summary></entry><entry><title type="html">Angular ActivatedRoute to Page Route</title><link href="https://walkingriver.com/angular-activated-route/" rel="alternate" type="text/html" title="Angular ActivatedRoute to Page Route" /><published>2021-02-01T00:00:00+00:00</published><updated>2021-02-01T00:00:00+00:00</updated><id>https://walkingriver.com/angular-activated-route</id><content type="html" xml:base="https://walkingriver.com/angular-activated-route/">&lt;p&gt;How to tie changes to an HTML dropdown (select) to your page’s URL and reactively respond to those changes, without forcing a page reload. Angular’s ActivatedRoute and some basic RxJS make it easy to do.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;&lt;img src=&quot;https://walkingriver.com/assets/img/angular-logo.png&quot; alt=&quot;Angular&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;background&quot;&gt;Background&lt;/h1&gt;
&lt;p&gt;On my page I have a dropdown control representing a set of “stores” in the application. When the user selects a new store from the dropdown, information about the newly-selected store is retrieved from a remote service and displayed on the screen.&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;select&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;(change)=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;storeChanged($event)&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;option&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;*ngFor=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;let store in stores&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;[value]=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;store.id&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/select&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Wiring that up to the dropdown was as simple as implementing the appropriate event handler.&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;storeChanged&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;storeId&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;detail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;storeInfo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;storeService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;storeId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;page-refresh&quot;&gt;Page Refresh&lt;/h1&gt;

&lt;p&gt;As simple as this approach is, it does not lend itself to page refresh or bookmarking in the browser. The page URL never changes, so if you refresh the page, you have to select your store again. I admit that this is a minor annoyance, but what if there were a straightforward way to make that happen?&lt;/p&gt;

&lt;h1 id=&quot;activateroute-and-router&quot;&gt;ActivateRoute and Router&lt;/h1&gt;

&lt;p&gt;If your first thought was to change the page’s route to include the store ID, give yourself a pat on the back. This approach makes a minor change to the dropdown’s change event handler, and then sets up a subscription on the ActivatedRoute parameters. And as you will see shortly, the component will also navigate to a new route using the Angular Router.&lt;/p&gt;

&lt;p&gt;Before making any further changes to the code, there are two classes that need to be imported at the top of the component file.&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ActivatedRoute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Router&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;@angular/router&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Both then need to be injected into the component’s constructor.&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;route&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ActivatedRoute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;router&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Router&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Other services injected&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;change-the-route&quot;&gt;Change the Route&lt;/h2&gt;

&lt;p&gt;The next thing to do is to change the page’s route to include the storeId. This will end up looking something like this in your routing module.&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Before&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;stores&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;StoreComponent&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// After&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;stores/:storeId&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;StoreComponent&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;stores&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;StoreComponent&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Place the original route without the storeID as the second route so that a route like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/stores/circuit-city/&lt;/code&gt; is matched before &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/stores&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;update-the-change-event-handler&quot;&gt;Update the Change Event Handler&lt;/h2&gt;

&lt;p&gt;Next, update the dropdown component’s change event handler to navigate to a new URL when the dropdown changes. It should end up looking something like this.&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;storeChanged&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;storeId&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;detail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;storeId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;router&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;navigateByUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`stores/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;storeId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;router&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;navigateByUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;stores&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The event handler first checks to see whether the selection has a value. If it does, it calls the Angular Router to navigate to the URL that includes the storeId. Otherwise, it navigates to the default route without the storeId.&lt;/p&gt;

&lt;p&gt;The code that used to be there to load the selected store’s details will be moved, as you will see next.&lt;/p&gt;

&lt;h2 id=&quot;subscribe-to-the-activatedroute&quot;&gt;Subscribe to the ActivatedRoute&lt;/h2&gt;

&lt;p&gt;To get everything working together, it is necessary to detect and respond to changes to the page’s route. Angular will not force a page reload. You could do that, of course, but it would require bootstrapping of your entire application, and is absolutely the wrong thing to do here.&lt;/p&gt;

&lt;p&gt;Instead, take advantage of the fact that ActivatedRoute provides a number of Observables to which you can subscribe. In this case, the only thing we care about are the route parameters, which are part of ActivatedRoute.params.&lt;/p&gt;

&lt;p&gt;We can set up a subscription inside of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ngOnInit&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-typeScript&quot;&gt;ngOnInit(): {
  this.selectedStoreSubscription = this.route.params.subscribe(params =&amp;gt; {
    const storeId = params.storeId || '';

    if (storeId) {
      this.storeInfo = this.storeService.getStore(storeId);
    } else {
      // Clear existing results
      this.storeInfo = null;
    }
  });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now, any time the route changes, the subscription will receive a new set of params. If the params contain a storeId, it will load the new store. Otherwise, it will clear the existing storeInfo.&lt;/p&gt;

&lt;h2 id=&quot;unsubscribe-from-the-subscription&quot;&gt;Unsubscribe from the subscription&lt;/h2&gt;
&lt;p&gt;Because I am subscribing to an Observable with no specific completion, it is necessary to unsubscribe when the component is destroyed. For that, I can simply unsubscribe in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ngOnDestroy&lt;/code&gt; function.&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;ngOnDestroy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;selectedStoreSubscription&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;selectedStoreSubscription&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;unsubscribe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;After writing this code and being satisfied that it works as expected, I wondered whether or not I could have gotten similar results with an Angular async pipe, and completely dispensed with the subscription. I decided that even though it might be possible, it was probably not desirable.&lt;/p&gt;

&lt;p&gt;Almost every detail of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;storeInfo&lt;/code&gt; would be data-bound on HTML view. I probably could have made it work, but this code is simple enough that I am happy with it as-is. It is easy to understand and easy to test, and there is no reason to change that.&lt;/p&gt;

&lt;h1 id=&quot;angular-advocate&quot;&gt;Angular Advocate&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;https://walkingriver.com/assets/img/aa-3d-small.jpg&quot; alt=&quot;Angular Advocate Book&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If you are interested in more content like this, please consider my recently-released book, &lt;em&gt;Angular Advocate: How to Awaken the Champion Within and Become the Go-to Expert at Work&lt;/em&gt;, available in a &lt;a href=&quot;https://amzn.to/3p00l67&quot;&gt;Kindle Edition at Amazon&lt;/a&gt; or &lt;a href=&quot;https://gum.co/angular-advocate&quot;&gt;DRM-free on Gumroad&lt;/a&gt;.&lt;/p&gt;</content><author><name>Michael D. Callaghan</name></author><category term="Angular" /><category term="Routing" /><category term="RxJS" /><summary type="html">How to tie changes to an HTML dropdown (select) to your page’s URL and reactively respond to those changes, without forcing a page reload. Angular’s ActivatedRoute and some basic RxJS make it easy to do.</summary></entry><entry><title type="html">Diagnosing Random Angular Test Failures</title><link href="https://walkingriver.com/angular-test-failures/" rel="alternate" type="text/html" title="Diagnosing Random Angular Test Failures" /><published>2021-01-14T00:00:00+00:00</published><updated>2021-01-14T00:00:00+00:00</updated><id>https://walkingriver.com/angular-test-failures</id><content type="html" xml:base="https://walkingriver.com/angular-test-failures/">&lt;p&gt;Have you ever had an intermittent or random failure in your unit tests? I did, and I was pulling my hair out trying to figure out the problem. Below I will describe how I finally managed to find the offending tests and solve the problem.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h1 id=&quot;background&quot;&gt;Background&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;/assets/img/angular-jasmine-karma.png&quot; alt=&quot;Angular + Jasmine + Karma&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I am using Angular 10 in this specific project, writing unit tests with Jasmine, and running them with Karma.&lt;/p&gt;

&lt;p&gt;My karma-jasmine configuration when I began was completely empty, meaning I was using the defaults. Not understanding those defaults caused much of my problem. Out of the box, your unit tests are run in a random order. I was vaguely aware of this, but it had completely slipped my mind at the time. If you take nothing else away from my story, remember this: Whenever you experience intermittent or random test failures, you can almost be sure that one test is causing another to break. The trouble is figuring out which combination.&lt;/p&gt;

&lt;h1 id=&quot;after-the-first-failure&quot;&gt;After the First Failure&lt;/h1&gt;

&lt;p&gt;As soon as I saw my first failure, I naively assumed it was the most recent test I added. Naturally, I removed it. At that point, the failure went away. I spent the next half hour trying to rewrite the “offending” test to figure out how it caused a failure in an unrelated test. The fact that the new test and the failing test were unrelated should have been my first clue that something else was at work here.&lt;/p&gt;

&lt;h1 id=&quot;start-disabling-tests&quot;&gt;Start Disabling Tests&lt;/h1&gt;

&lt;p&gt;As I write this, my project has 153 unit tests. The failure was occurring in an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;afterAll&lt;/code&gt; function, which I did not even have. The error referred to a specific component, but not a specific test. I could not even determine which test to skip. Instead, I decided to start running a subset of tests by selectively disabling some of the other tests and test suites.&lt;/p&gt;

&lt;h1 id=&quot;disable-random-test-ordering&quot;&gt;Disable Random Test Ordering&lt;/h1&gt;

&lt;p&gt;By this point, I was reasonably sure the problem was one test causing a failure in another one. The trick now was to figure out which one. On a whim, I decided to turn off random ordering in the Jasmine configuration. As I said, I am running my tests with the Karma test runner. So, inside the karma.conf.js file, I simply needed to add some Jasmine-specific configuration to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;client&lt;/code&gt; section.&lt;/p&gt;

&lt;p&gt;Before, that section looked like this:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;clearContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// leave Jasmine Spec Runner output visible in browser&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To disable random test order, I added this:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;clearContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// leave Jasmine Spec Runner output visible in browser&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;jasmine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This would guarantee that my tests would run “in order,” whatever that means. It occurred to me that this probably would not reveal any new insights. I was right. All tests passed. The good news is that the test run was now repeatable. Every test run passed now.&lt;/p&gt;

&lt;p&gt;Did it solve my problem? Nope, but at least I felt I was making progress.&lt;/p&gt;

&lt;h1 id=&quot;you-can-specify-the-random-seed&quot;&gt;You Can Specify the Random Seed!&lt;/h1&gt;

&lt;p&gt;The next thing I did was to turn random ordering back on, but this time provide my own random seed. If you are not familiar with a seed, it is a number used to initialize (or “seed”) the random number generator. The benefit of this approach is that using the same value to seed the random number generator will provide the identical sequence of random values on subsequent runs. Thus, once I found a seed that caused my test failure to show itself, I could continue using that seed during my investigation.&lt;/p&gt;

&lt;p&gt;I started with the seed &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1234&lt;/code&gt;. Because I had no idea what value might cause the problem, it really did not matter. On the first run with that value, all the tests passed, so that was no help. I continued changing the seed value until my test failed. Fortunately, it only took me a couple of attempts. I ended up with a configuration like this:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;clearContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// leave Jasmine Spec Runner output visible in browser&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;jasmine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;seed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;12345&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;start-isolating-tests&quot;&gt;Start Isolating Tests&lt;/h1&gt;

&lt;p&gt;Now that I could reliably reproduce the problem, it was time to start isolating the failed test from the working ones.&lt;/p&gt;

&lt;p&gt;On my first attempt, I simply ran only the failing test in that test suite, by changing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;describe&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fdescribe&lt;/code&gt;. This block had only one &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;it&lt;/code&gt; block, so I was only running a single test from this suite. The rest of the test suites continued to run as normal. I was not yet ready to turn them off.&lt;/p&gt;

&lt;p&gt;As I expected would happen, the test passed. When run in isolation from the other tests in the test suite, it worked just fine.&lt;/p&gt;

&lt;p&gt;As a sanity check, I disabled every other test in the project, and as expected, the remaining test passed.&lt;/p&gt;

&lt;p&gt;Next, I began turning on one test at a time. I considered turning on half, and then the other half. However, this particular suite only had ten tests in it, so I simply started at the top and turned them on one by one. As luck would have it, the very first test I enabled caused the problematic test to fail.&lt;/p&gt;

&lt;p&gt;Now the real investigation began. What code did the first test run that caused the second test to fail?&lt;/p&gt;

&lt;h1 id=&quot;give-asynchronous-code-a-closer-look&quot;&gt;Give Asynchronous Code a Closer Look&lt;/h1&gt;

&lt;p&gt;As I looked closer at the failing test, I noticed that it was testing a failure scenario. A passing test actually meant that an error occurred inside my component. This test used the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async/await&lt;/code&gt; pattern to wait on the asynchronous service call it had to make.&lt;/p&gt;

&lt;p&gt;I had seen this problem before, and almost certainly knew what had caused it. The function in many of my components that made asynchronous service calls were not returning the promise to the caller. Consider this block of code (not my actual code):&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Component function&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;getSomeData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// Service function&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In this code, the promise returned by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;service.getData()&lt;/code&gt; is being returned by the component’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getSomeData()&lt;/code&gt; function to its caller. If you forget to do that, the testing code will have nothing to await. My component had many such functions, and I had been meticulously going through them all to make sure they returned the service call’s promise. I immediately checked the particular function being exercised by the failing test. To my surprise, it was fine.&lt;/p&gt;

&lt;p&gt;That, however, led me to another realization. The test itself was bad. It looked like this:&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;should set an error if it throws&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// service here is a test double, a mock of the real service&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;rejects&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;test-error&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;fixture&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;detectChanges&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getSomeData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toBeTruthy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The testing code was using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async/await&lt;/code&gt;, but had no &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try/catch&lt;/code&gt;! I decided that I would rewrite the test without async/await, instead using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;catch&lt;/code&gt;. The modified test looked something like this:&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;should set an error if it throws&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// service here is a test double, a mock of the real service&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;rejects&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;test-error&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;fixture&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;detectChanges&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getSomeData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toBeTruthy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;At last, I was pretty sure I had found the problem. I eagerly enabled all units tests and reran the tests with the same seed I had been using. The problematic test failed again, with the exact same error!&lt;/p&gt;

&lt;h1 id=&quot;new-test-suite&quot;&gt;New Test Suite&lt;/h1&gt;
&lt;p&gt;At this point, I decided to start with a clean slate. I disabled all tests in that component’s test suite. Then I used the Angular CLI to create a brand new component, complete with its unit test boilerplate.&lt;/p&gt;

&lt;p&gt;Once that was done, I painstakingly began copying tests one at a time from the old test suite into the new one. I started simply, copying just enough code to initialize the component. That test failed due to missing service doubles. Next, I copied those test doubles (mostly service mocks) into the new suite.&lt;/p&gt;

&lt;p&gt;With the test doubles in place, my component creation test passed. One down, nine to go.&lt;/p&gt;

&lt;p&gt;The next test up was the failing one. It did something only one other test in the suite did. It changed the behavior of one of my mock services. Could that be the piece I had been missing?&lt;/p&gt;

&lt;h1 id=&quot;suspect-your-test-doubles&quot;&gt;Suspect Your Test Doubles&lt;/h1&gt;
&lt;p&gt;I deleted my test doubles and reentered them individually. While doing this, I noticed that two of my mock services were created as object literals directly in the test suite. This was odd, as I also had a mock service defined as a class in another file. I decided I should try to use that instead.&lt;/p&gt;

&lt;p&gt;The code used to look something like this:&lt;/p&gt;
&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;service&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;getData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// This was provided to the component's testing module like this:&lt;/span&gt;
&lt;span class=&quot;nl&quot;&gt;providers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;provide&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;DataService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;useValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;service&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I moved the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getData&lt;/code&gt; call into my Mock Service and changed the provider to look like this:&lt;/p&gt;
&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;DataService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// This was provided to the component's testing module like this:&lt;/span&gt;
&lt;span class=&quot;nl&quot;&gt;providers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;provide&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;DataService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;useClass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;DataService&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I had left in the variable declaration so that I could redefine its behavior in the error path. But now I had a compiler error. This line in my failing test was no longer valid.&lt;/p&gt;
&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;nx&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;rejects&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;test-error&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That is because service was now strongly-typed to be a DataService, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getData&lt;/code&gt; returned a promise. It had no function named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rejects&lt;/code&gt;. The solution was to replace the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getData&lt;/code&gt; function completely, but just for this test.&lt;/p&gt;

&lt;p&gt;Recall that I said this test was inside its own &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;describe&lt;/code&gt; block, so I added a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;beforeEach&lt;/code&gt; to it, where I could replace the behavior of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getData&lt;/code&gt; function to reject instead of resolve the promise. Now my test looked something like this:&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;getSomeData (error path)&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// Redefine service.getData here, outside the test itself.&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;beforeEach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getData&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;sinon&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;rejects&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;test-error&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;should set an error if it throws&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// service here is a test double, a mock of the real service&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;fixture&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;detectChanges&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getSomeData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toBeTruthy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here I am using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sinon&lt;/code&gt; to create the stub, but you can use a similar strategy with Jasmine’s own functions.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;beforeEach&lt;/code&gt; on the overall test suite itself creates a brand new instance of the mock service before each test. Then, in this test, its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getData&lt;/code&gt; function is replaced with a new function that returns a rejected promise instead.&lt;/p&gt;

&lt;p&gt;I was feeling pretty good about where this was heading. I enabled all the tests and reran them. All passed. Finally, I removed the seed value from the Jasmine configuration and let the tests run in a random order. The test has been passing ever since.&lt;/p&gt;

&lt;h1 id=&quot;summary&quot;&gt;Summary&lt;/h1&gt;

&lt;p&gt;I have been careful not to show any of the actual test code that caused my problem, but instead just enough code to get my point across. My goal was to discuss how to isolate the problems in your tests, rather than discuss testing strategy. To that end, I hope I have succeeded.&lt;/p&gt;

&lt;p&gt;In short:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Understand that tests run in no particular order, and that is ideally what you want.&lt;/li&gt;
  &lt;li&gt;Turn off random execution to help find the culprit.&lt;/li&gt;
  &lt;li&gt;If sequential execution does not help, try various random “seed” values until the tests are failing consistently.&lt;/li&gt;
  &lt;li&gt;Isolate the failing test or test suite by using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fdescribe&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fit&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xdescribe&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xit&lt;/code&gt; to turn tests on or off selectively.&lt;/li&gt;
  &lt;li&gt;Pay close attention to asynchronous code and test doubles.&lt;/li&gt;
  &lt;li&gt;Ensure your test doubles are typed correctly, especially your mock services.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope you found this summary to be worthwhile and useful. If you have any tricks or tips of your own when it comes to diagnosing unit tests, please feel free to share them with me.&lt;/p&gt;

&lt;h1 id=&quot;angular-advocate&quot;&gt;Angular Advocate&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://walkingriver.com/assets/img/aa-3d-small.jpg&quot; alt=&quot;Angular Advocate Book&quot; /&gt;
If you are interested in more content like this, please consider my recently-released book, &lt;em&gt;Angular Advocate: How to Awaken the Champion Within and Become the Go-to Expert at Work&lt;/em&gt;, available in a &lt;a href=&quot;https://amzn.to/3p00l67&quot;&gt;Kindle Edition at Amazon&lt;/a&gt; or &lt;a href=&quot;https://gum.co/angular-advocate&quot;&gt;DRM-free on Gumroad&lt;/a&gt;.&lt;/p&gt;</content><author><name>Michael D. Callaghan</name></author><category term="Unit Tests" /><category term="Angular" /><category term="Web Development" /><summary type="html">Have you ever had an intermittent or random failure in your unit tests? I did, and I was pulling my hair out trying to figure out the problem. Below I will describe how I finally managed to find the offending tests and solve the problem.</summary></entry></feed>