Monday, February 19, 2018

Building a Top-Level Style Switcher (Themes) for Angular

Typically, if your Angular application has CSS styling that is conceptually "shared" or does not belong to a particular component (we're talking Angular 2+ here), you'll have one or more top-level .css files referenced with non-encapsulated class definitions. In an angular-cli build, these get listed in the "styles" array in .angular-cli.json. If you want your application to support different "themes", you might want to swap out one or more of these top-level stylesheets. It turns out that this is not super-easy to do.

One approach to theming that works is to have a component-per-theme with non-encapsulated styles (encapsulation: ViewEncapsulation.None). Such components have no HTML template markup, and their only purpose in life is to sit at or near the top of the application and get dynamically get turned on and off as the theme is changed.

A simple theme-switcher component with a dropdown selector might look like this:

<select [(ngModel)]="selectedStyle">
  <option value="redOnGreen">Red on Green</option>
  <option value="greenOnRed">Green on Red</option>
</select>
<app-red-on-green-style *ngIf="selectedStyle === 'redOnGreen'">
</app-red-on-green-style>
<app-green-on-red-style *ngIf="selectedStyle === 'greenOnRed'">
</app-green-on-red-style>

Each theme component has the same class names in its corresponding styleUrl list. For a "red-on-green" theme:

.title {
  background-color: green;
  color: red;
}

.subtitle {
  background-color: green;
  color: red;
}

And its inverse:

.title {
  background-color: red;
  color: green;
}

.subtitle {
  background-color: red;
  color: green;
}

Placing this at the top of the demo application, you will see this the first time you open it:



And after changing the selector:



The problem comes when you try to change it back. Turns out, you can't. That's because when a component with ViewEncapsulation.None first gets initialized, its styles get injected into the of the page, and they never go away. So in this case, the second component's styles are sitting last in the , and they permanently override the first component's, regardless of which one is "on". And the situation is made all the bleaker by the fact that the Angular-injected style tags don't have a "name" attribute or anything else that would help you, say, find and remove them.

The first step in my approach to address this issue is to augment the CSS for each theme with a dummy class that uniquely identifies it. For the red-on-green theme:

.red-on-green {}

And green-on-red:

.green-on-red {}

Now, with a bit of DOM-programming determination, we can manipulate the style nodes in the head to get the effect that we want. I wrapped a quick-and-dirty implementation in an Angular service:

import { Injectable, Inject } from '@angular/core';
import { DOCUMENT } from '@angular/platform-browser';

@Injectable()
export class StyleApplicationService {

  constructor(@Inject(DOCUMENT) private document: any) {
  }

  public apply(styleName: string): void {
    let foundStyleSheet;
    const fullStyleName = '.' + styleName;
    for (const styleSheet of this.document.styleSheets) {
      if (styleSheet.cssRules && styleSheet.cssRules.length) {
        const rule = styleSheet.cssRules[0];
        if (rule.selectorText === fullStyleName) {
          foundStyleSheet = styleSheet;
          break;
        }
      }
    }
    if (foundStyleSheet) {
      const child = this.document.head.removeChild(foundStyleSheet.ownerNode);
      if (child) {
        this.document.head.appendChild(child);
      }
    }
  }
}

This apply() method of this service surfs the document's styleSheets looking for the one whose 0th rule matches the given theme name. Then it removes the corresponding node and re-appends it, thus making it appear last and giving it the winning set of class definitions. Each theme component then injects this service and calls apply() passing the theme name in ngAfterViewInit. For example:


import { Component, ViewEncapsulation, AfterViewInit } from '@angular/core';
import { StyleApplicationService } from '../style-application.service';

@Component({
  selector: 'app-red-on-green-style',
  template: '',
  styleUrls: ['./red-on-green-style.component.css'],
  encapsulation: ViewEncapsulation.None
})
export class RedOnGreenStyleComponent implements AfterViewInit {
  constructor(private styleApplier: StyleApplicationService) {
  }
  
  ngAfterViewInit(): void {
    this.styleApplier.apply('red-on-green');
  }
}

Ideally, Angular would support this scenario better. Perhaps Material themes (which are now kind-of limited to colors) will expand to address more complete CSS re-theming of Angular applications.

Monday, January 16, 2017

Publishing the Traffic Cam Bot to Azure

After leaving the Traffic Cam Bot on the shelf for a few months while I was busy with other things, I finally came back to it last week.

First I brought it up to speed with the latest version of the Microsoft Bot Framework. Then I set out to publish the bot to Azure following along with the instructions given here in the section entitled "Publishing your Bot Application to Microsoft Azure". All in all, it was a fairly smooth process. But I did run into three issues at this stage that I want to bring up here in case anyone else runs into similar things.

403 Forbidden (App Service Instance)
This was the first thing I ran into when I published my bot from Visual Studio using the steps in the Getting-Started guide -- an HTTP 403 "Forbidden" error. I went through the various steps of ensuring that my appId and password were correct, and that I entered "https" for my service URL (something that you need to pay close attention to when first publishing). None of that helped.

What I finally realized was that when I deployed, I picked an Azure "Free" app service instance because, hey, who doesn't like "free"? However, upon closer inspection of the instance types, I noticed that "Free" instances don't have SSL support. If you poke around the Bot Framework docs, they state (correctly) that the framework handles SSL for you. However, apparently no so much if there is no SSL on the instance. So I redeployed to a B1 instance and got a little further.

"Test Connection" Failure (Ping)
The next thing I noticed on my bot details page, the web chat widget started working. But the "Test Connection" button always reported a failure. That seemed particularly strange, because clearly I could "connect" to my bot, as I was actually sending it messages and receiving responses.

This one had to do with how my ApiController implemented the ActivityTypes.Ping system message. In earlier versions of the Bot Framework and Bot Application Visual Studio template, this message was handled with a string response. It seems that in more recent Bot Frameworks, you are supposed to explicitly not reply to the message with a body, but merely return HTTP 200 (OK).

Duplicate Messages in Skype/Teams (Extra await)
Lastly, after getting my bot running on Azure, I wanted to try chatting with it using Skype. This worked great, except for the fact that in certain situations, I was seeing duplicated requests coming from Skype, a behavior that I was also able to replicate using the Teams client. The thing that made this problem especially flummoxing was that the Web Chat and emulator clients did not display this behavior.

It turned out that my ApiController's Post handler had a bug in it such that I was calling await twice along some code paths. I still don't understand the differences in behavior between Skype/Teams and the Web Chat / emulator clients under these conditions. But suffice to say, once I cleaned up my code, the problem went away.

Perhaps these notes will help somebody. I hope to be able to get the Traffic Cam Bot in the Bot Directory soon. But in the meantime, feel free to watch the code on Github.

Sunday, October 16, 2016

C# List-Sampling Extension Method

While messing around with some for-fun code in C# today, I found myself looking for something along the lines of Ruby's Array#sample method, but for .NET's IList. Strangely (or perhaps not-so-strangely, because when you search for something like "C# IList sample extension", you get tons of noise), I couldn't find anything. So I wrote a simple one to share here:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public static class ListExtensions
{
 readonly static Random myRand = new Random();
 readonly static object lockObj = new object();

 public static T Sample<T>(this IList<T> list)
 {
  lock (lockObj)
  {
   return Sample(list, myRand);
  }
 }

 public static T Sample<T>(this IList<T> list, Random rand)
 {
  if (list == null || list.Count == 0)
  {
   return default(T);
  }
  return list[rand.Next(list.Count)];
 }
 public static IList<T> Sample<T>(this IList<T> list, int size)
 {
  lock (lockObj)
  {
   return Sample(list, size, myRand);
  }
 }

 public static IList<T> Sample<T>(this IList<T> list, int size, Random rand)
 {
  if (list == null)
  {
   return new List<T>();
  }
  return list.Select(x => new { Entropy = rand.Next(), Item = x })
   .OrderBy(x => x.Entropy)
   .Select(x => x.Item)
   .Take(size)
   .ToList();

 }
}

Notes:

  • I put this on the IList interface instead of, say, IEnumerable, because the random access helps greatly with the efficiency of the size-1 overloads. (And that just happens to be the specific case I needed today).
  • You can use the overloads that require you to pass your own Random instance, or not, but if you choose not to, note that you'll take a locking penalty since System.Random isn't thread-safe.
  • Samples of size > 1 are implemented via that Linq-style shuffle.

Tuesday, July 19, 2016

Registering All Implementations of an Interface with Unity

Currently, the extensibility story for the TrafficCamBot requires anyone wanting to add a new set of cameras to implement a specific .NET interface (directly inside the bot assembly, although this may change in the future). All implementations of said interface need to be registered with the .NET Unity dependency injection container, for this is how the master "camera data manager" finds them.

Originally, I was using an overload of UnityContainer.RegisterType that took generic type parameters. This is well-and-good, but because generic type parameters are really only helpful at compile-time, this approach would have required each new implementation to add an individual line of additional code to call RegisterType.


            container.RegisterType<ICameraDataService, SeattleCameraDataService>(
                typeof(SeattleCameraDataService).Name, new ContainerControlledLifetimeManager());
            container.RegisterType<ICameraDataService, BayAreaCameraDataService>(
                typeof(BayAreaCameraDataService).Name, new ContainerControlledLifetimeManager());
            // etc.

Ideally, this would not be necessary, as it is an additional step for the camera data contributor and is prone to error. Fortunately, we can reflect over all of the types in the assembly looking for appropriate implementations and use a non-generic RegisterType overload as follows:


            var cameraDataServiceTypes = from t in Assembly.GetExecutingAssembly().GetTypes()
                                         where t.IsClass && !t.IsAbstract && t.GetInterface(typeof(ICameraDataService).Name) != null
                                         select t;
            foreach (var cameraDataServiceType in cameraDataServiceTypes)
            {
                container.RegisterType(typeof(ICameraDataService), cameraDataServiceType,
                    cameraDataServiceType.Name, new ContainerControlledLifetimeManager(),
                    new InjectionMember[] { });
            }

Later, since Unity is happy with multiple registered implementations per interface, these can be snarfed out of the container using UnityContainer.ResolveAll:


            var managers = UnityConfig.GetConfiguredContainer().ResolveAll(typeof(ICameraDataService));
            foreach (ICameraDataService manager in managers) {
                var service = manager as ICameraDataService;
                services[service.Name] = service;
            }

Monday, July 18, 2016

Faking NLP With Lucene

Since I'm going back to work at Microsoft soon, I decided I ought to start up a C# / .NET project to stretch my skills a bit. I went looking for good ideas and came up with a Microsoft Bot Framework bot to answer queries for public traffic camera data. I started a Github project here, so check it out. I will blog a little about it as opportunities arise.

Once thing I wanted to do was support as much of a natural conversational style as possible, as is the trend with bots. Here is an example dialog between me and my bot:

I spent a little bit of time investigating how I might do "true" natural language processing (NLP) to answer queries like "Show me traffic at Sunset", perhaps using something like Stanford CoreNLP. The question then arose as to how I would train an appropriate model. With traffic cameras, the camera names sometimes look sorta like addresses, which is clearly trainable. But many times, they don't, and in fact I finally decided they didn't really fall into any trainable pattern whatsoever.

Instead, I decided to apply search techniques. I set up a Lucene index. Each document in the index represents one traffic camera. I added text to it with different combinations of possible abbreviations. For example, a camera named "NE 85th St" might be added to the index with a document like:

ne 85th st
northeast 85th st
ne 85th street
northeast 85th street

        private Document CreateDocument(string title, IEnumerable<string> altNames)
        {
            var doc = new Document();
            doc.Add(new Field(CAMERA_NAME_FIELD, title, Field.Store.YES, Field.Index.NOT_ANALYZED));
            var sb = new StringBuilder();
            sb.Append(title);
            sb.Append('\n');
            foreach (var altName in altNames)
            {
                sb.Append(altName);
                sb.Append('\n');
            }
            var content = sb.ToString();
            doc.Add(new Field(CONTENT_FIELD, content, Field.Store.YES, Field.Index.ANALYZED));
            return doc;
        }

When it comes time to process a query, we first look for exact (conjunctive terms) and fuzzy matches. This will fail for the natural-language style text. So at that point (and kind of as a last resort), the whole query string gets passed directly to the index with no preprocessing other than lowercase normalization, and the results scored. All of the documents passing a certain threshold are returned.

        private IList<string> RunQuery(Query query)
        {
            var collector = TopScoreDocCollector.Create(MAX_SEARCH_RESULTS, false);
            searcher.Search(query, collector);
            var scoreDocs = collector.TopDocs().ScoreDocs;
            logger.Debug("Searching for " + query +
                ", top score is " + (scoreDocs.Any() ? scoreDocs[0].Score.ToString() : "non-existent"));
            var results = from hit in scoreDocs
                          where hit.Score >= HitScore
                          select searcher.Doc(hit.Doc).Get(CAMERA_NAME_FIELD);
            return results.ToList();
        }
If there is only one matching document, we have achieved "magic" and present the camera directly to the user. Otherwise (as in the example dialog above), we present a choice menu.

What I found in practice is that this works for a wide variety of query and camera names. Typically, the desired camera document(s) will have a score around 0.3, and there is an order-of-magnitude drop-off in the scores of other "matching" documents (which perhaps just match a generic term like "avenue").

So at the end of the day, with no true NLP algorithms in play at all, it seems the bot can do a fairly decent job of handling natural-language style queries in this limited domain.