Running TypeScript scripts on Angular code

I needed to check my Angular application for data consistency. Those checks included completeness of internally referenced data, as well as the existence of files referenced by file name in that data.

Since Angular is TypeScript, it would be nice to use TypeScript for those checks, as I can directly reference all types and data declared in the application code using the import mechanism.

(I had previously used gulp for such tasks, but used JavaScript at that time. Apparently, there also exists a way to integrate gulp with TypeScript)

To run a TypeScript from the command line, I installed ts-node as a dev dependency in my Angular application:

npm i -D ts-node

However, running the script using

npx ts-node path-to-script.ts

would raise the error message

(node:1060467) Warning: To load an ES module, set “type”: “module” in the package.json or use the .mjs extension.
(Use node --trace-warnings ... to show where the warning was created)

Fortunately, this issue was quite easy to resolve thanks to this SO comment, as I only needed to edit tsconfig.json to add the following configuration to the JSON root:

"ts-node": {
  "compilerOptions": {
    "module": "commonjs"
  }
}

Fixing the Multi-Column Sort behavior of a Kendo UI Grid

Kendo UI for Angular contains a Grid component which also supports sorting multiple columns.

The sample grid only contains 3 columns, and sorting by multiple columns does not have much effect, but this is only about the sorting behavior, not the data being sorted.

As you check the “Enable multiple columns sorting” box, you’ll notice a behavior that I find counter-intuitive:

  • click on the Product Name column, and the grid is sorted by Product Name, showing 1 sort arrow
  • click on the ID column, and the grid is sorted by Product Name and then ID, indicated by the column order next to the sort arrows (Product Name – 1, ID – 2)
  • click on the Product Name column again, to reverse the sort order of the Product Name column.

What do you expect?

What the Kendo UI Grid does, it changes the order of columns so that the column clicked last ends up being the last in the order of columns.

My understanding is that the user wanted to change the direction of the sorted column (ascending vs. descending), and not change the order of the sorted columns.

Fortunately, the <kendo-grid> component provides a (sortChange) event, where you can implement your favorite sort behavior.

I created a multi-column sort sample on StackBlitz with my preferred sort behavior, the code can be viewed and forked here.

TypeScript logging with log4javascript in an ASP.Net MVC application

I needed to add logging functionality to an existing TypeScript application hosted on ASP.Net MVC.

Since I usually use log4net in C#, log4javascript was an obvious choice, as it provides a similar API.

First, I needed to declare the parts of the log4javascript API I use in TypeScript:

declare module log4javascript {
    class Logger {
        info(msg: string);
        info(msg: string, value: any);
        addAppender(app: AjaxAppender);
    }
    function getLogger(name: string): Logger;
    function getNullLogger(): Logger;
    class AjaxAppender {
        constructor(url: string);
        setSessionId(id: string);
        setLayout(layout: JsonLayout);
        addHeader(key: string, value: string);
    }
    class JsonLayout {
        constructor(readable: boolean, combine: boolean);
    }
}

In the logging class, I added logger and AjaxAppender. Depending on the data to log, I also configure a JsonLayout.

logger: log4javascript.Logger;

A non-logging logger instance is created using getNullLogger():

this.logger = log4javascript.getNullLogger();

Plain-text AjaxAppender

Use these statements to create an Ajax-based logger in TypeScript:

this.logger = log4javascript.getLogger("MyLoggerName");
var app = new log4javascript.AjaxAppender("http://myhost/myapp/mylogger");
this.logger.addAppender(app);

On the MVC side, the controller receives the parameters: logger, timestamp, level, url, message, sessionid, and layout. (Note that these parameter names can be changed using the setKeys() method)

The value for sessionid is set using

ajaxAppender.setSessionId(sessionId);

To log a string message, simply write

this.logger.info("my info log message");

The C# controller method stub therefore looks something like this:

[ValidateInput(false)]
[HttpPost]
public ActionResult Log(long? timestamp, string level, string sessionid, string message)
{
    var ipaddr = Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
    if (string.IsNullOrEmpty(ipaddr))
        ipaddr = Request.ServerVariables["REMOTE_ADDR"];
    if (string.IsNullOrEmpty(ipaddr))
        ipaddr = Request.UserHostAddress;

    if (level == "INFO")
        logger.Info(ipaddr + " " + sessionid + " " + message);
    else
        logger.Debug("[" + level + "] " + ipaddr + " " + sessionid + " " + message);
    return Json(null);
}

where logger is an instance of log4net.Ilog.

JSON-formatted AjaxAppender

To change the appender’s data format to JSON, we set its layout to JsonLayout:

this.logger = log4javascript.getLogger("MyJsonLogger");
var app = new log4javascript.AjaxAppender("http://myhost/myapp/myjsonlogger");
app.setLayout(new log4javascript.JsonLayout(false, false));
app.addHeader("Content-Type", "application/json");
this.logger.addAppender(app);

The C# controller method receives a JSON array of log entries

    [
        {
            "logger":"MyJsonLogger",
            "timestamp":1448621329758,
            "level":"INFO",
            "url":"http://localhost/myapp/...",
            "message":
            [    "message1",
                "message2", ...
            ]
        }
    ]

where message1, message2, etc are the parameters of the logger’s debug/info/warn etc. calls.

We create a C# class containing the required properties

public class Data
{
    public string logger;
    public long? timestamp;
    public string level;
    public string url;
    public string[] message;
}

and deserialize the HTTP request’s InputStream using JSON.Net:

[ValidateInput(false)]
[HttpPost]
public ActionResult Execute()
{
    string inputContent;
    using (var sr = new StreamReader(HttpContext.Request.InputStream))
    {
        inputContent = sr.ReadToEnd();
    }
    var data = JsonConvert.DeserializeObject<Data[]>(inputContent);

    if (data != null)
    {
        foreach (var d in data)
        {
            if (d.level == "INFO")
                logger.Info(d.message[0] + " " + d.message[1]);
            else
                logger.Debug("[" + d.level + "] " + d.message[0] + " " + d.message[1]);
        }
    }
    return Json(null);
}

Again, logger is an instance of log4net.Ilog.

Migrating a TypeScript application from ASP.Net MVC to Angular2 – Lessons Learned

To evaluate Angular2, I started to convert a TypeScript application hosted in an ASP.Net MVC application to be hosted by the Angular framework.

The application continuously polls data from a web service, performs a couple of calculations, and displays the calculated results. It is a display-only auto-paging application – the only user interaction being to allow for manual paging. Parts of the display are updated by requesting a PartialView and adding it to the display using jQuery append().

Getting started is quite easy:

  • download, install, and configure Visual Studio Code
  • download and install node.js
  • create application directory and configuration files, and run npm install

Components

First, we need to translate MVC Controllers and Views into Angular2 @Component classes and templates:

A page (controller+view) typically translates into a component with an associated HTML template.

Depending on your application, a PartialView may also be represented by a component. It may also translate into a part of a template being activated by NgSwitch or NgIf.

The file app/app.component.ts defines the URL routes, with @RouteConfig being the equivalent of MVC routing configuration. Each page you can directly address by a URL in a browser needs to be added to the @RouteConfig declaration.

Angular Exceptions

During development, you will occasionally meet the error condition 

The selector “my-app” did not match any elements

Currently, the only reliable way for me to resolve the condition is to close the browser window, restart npm lite-server, and hitting ctrl-F5.

Unfortunately, this was not the only (recurring) exception generated by the Angular2 framework. True, this is still a beta version (15), but if an exception in the framework occurs, there is almost no way of debugging it, as developers are used to debug their code. There is simply too much magic happening in the background. I repeatedly had to remove code or html and re-insert it step by step to figure out which part of the code caused a framework exception. And judging from browsing through GitHub issues, the situation has improved considerably from earlier versions.

Coding and Legacy Code

If your component needs to process URL parameters, there is the built-in routeParams.get() method. There is no automatic mapping of URL parameters to model class properties, but I developed a simple TypeScript function which will provide this convenience.

Adding and referencing existing TypeScript code to an Angular2 application is straight-forward. As I wanted to keep changes to existing code to a minimum, I simply added the scripts in the main index.html page using <script> tags.

However I found that Angular2 does not render <script> elements inside HTML templates. Fortunately, this code can be moved to and thus accessed from the @Component class, either in its constructor or one of the component’s lifecycle hooks, the Angular equivalent of ASP.Net events.

Angular2 uses import to load and reference TypeScript code. To reference code loaded by a <script> tag, you need to declare external symbols, as is typical for TypeScript’s .d.ts files.

To implement calls from the legacy code into the Angular2 code, I needed to store the Angular component in a window property, and access this property to invoke a method of that component:

// myComponent.ts:
constructor() {
  (<any>window).myComponent = this;
}
methodCall() {
  ..implementation..
}
// legacy.ts
(<any>window).myComponent.methodCall();

Just a tiny change. Alternatively, every referenced declaration (and their dependencies) would have needed to be marked as export.

Comparing Architecture and TypeScript Hosting in ASP.Net MVC and Angular2

Comparing Architecture and TypeScript Hosting in ASP.Net MVC and Angular2

Dynamic Views

To dynamically render additional views, I created another component and assigned it a selector. The parameters displayed in the dynamic view are stored in a class, and the main component stores an array of this class:

export class MySubView {
  ... members ...
}

In the main component:

public subviews: MySubView[] = [];

In the main component’s HTML template:

<mysubview *ngFor="#subview of subviews" [data]="subview"></mysubview>

The sub component is declared as

@Component({
 selector: 'mysubview'
}
export class MySubViewComponent {
  @Input() data: MySubView;
}

A new mysubview item is simply created by Angular2 by adding an object to the array:

this.subviews.push(new MySubView(...parameters..));

If a component needs to dynamically switch between several available templates, things get a bit complicated, but it’s feasable.

Disabling some Angular2 features

Angular2 allows CSS styles per @Component, and when rendering a component’s CSS, adds a component-specific attribute to the HTML elements, as well as to their CSS declarations.

Since I did not want to edit my CSS (which would have certainly made the CSS declarations cleaner and more specific), I deactivated this CSS magic by declaring the component

encapsulation: ViewEncapsulation.None,

Also, I wanted to disable Angular’s data-binding mechanism, since my legacy code takes care of all data-binding, and I wanted to avoid any interference between the two.

Adding the component declaration

changeDetection: ChangeDetectionStrategy.OnPush

switches change detection and auto-updates off.

Conclusion

Starting with Angular2 certainly comes with a learning curve. Switching platforms, even though they are comparable, may be a major effort. Whereas my little sample just concerned hosting existing functionality in Angular2, adapting existing code as a “real” Angular2 application – designing and using components, CSS handling, data-binding and change detection – is probably an effort similar to writing the application from scratch.

Resources

Migrating ASP.Net MVC PartialViews to Angular2

An Angular2 @Component combines the equivalents of MVC Controller and View. This mostly also applies to MVC PartialViews.

In my application, TypeScript dynamically adds PartialViews to the page’s HTML.

Migrating the code to Angular2, the page’s component, let’s call it ParentComponent, contains an array of ChildComponents. If a new ChildComponent is added to the array, Angular2 will automatically render its template or templateURL, and add the resulting HTML inside the <childcomponent> tag

<childcomponent *ngFor="#c of childcomponents" [childdata]="c">
</childcomponent>

Your custom JavaScript code can then process the rendered component in one of the LifeCycle Hooks.

The story gets more complicated if your code needs to dynamically select which PartialView to render. Of course, there is the NgSwitch directive, which acts as the templating’s switch/case mechanism, but template code may easily get ugly and overly complex.

The alternative is to define the template or templateURL programmatically by calling loader.loadIntoLocation() of DynamicComponentLoader, but, as GitHub issues show (#7815, #6701, #7596, #2753), this is not as straight-forward as one might hope, especially if template data-binding is involved (#6223, #7453SO, SO).

The solution that I extracted from the referenced issues looks like this

constructor(private loader: DynamicComponentLoader, 
    private elementRef: ElementRef, 
    private ref: ChangeDetectorRef) {
  this.myData = ..some data..;
  this.myOtherData = ..some data..;
}

ngOnInit() {
  var templateURL = .... calculate template ...;

  this.loader.loadIntoLocation(
    this.toComponent(templateURL + '.html', null),
    this.elementRef, 'newItem'
  ).then(component => { 
    component.instance.myData = this.myData;
    component.instance.myOtherData = this.myOtherData;
    this.ref.markForCheck() 
  }); 
}

Based on SO and plnkr

toComponent(templateUrl, directives = []) {
  @Component({ selector: 'child-fake-component', 
  templateUrl: templateUrl,
  directives: [COMMON_DIRECTIVES])
  class ChildFakeComponent {}
 
  return ChildFakeComponent;
}

Note that I need ChangeDetectorRef to explicitly update the template because I deactivated change detection using ChangeDetectionStrategy.OnPush, so this need not apply to your case.

The essential part is that you need to copy all data referenced by the template to component.instance. #newItem is the anchor of the HTML element in the @Component’s template or template, and the loaded template will be attached after the original template, not replacing the original template.

Invoking Custom JavaScript Code from Angular2

I am trying to migrate an ASP.Net MVC application to Angular2. One thing I am still missing is how to invoke custom/legacy JavaScript code, as Angular2 does not render <script> tags contained in templates.

Actually we do not need <script> tags, as custom JavaScript/TypeScript can be called from the @Component!

Custom code can be invoked from various places of your component’s TypeScript code:

Logging to the console shows that code is executed in the following order

  • constructor
  • ngAfterContentInit
  • ngAfterViewInit
  • asynchronous code invoked in constructor (e.g. by http.get())

(for more details, see Angular’s documentation of Lifecycle Hooks)

Since executed code resides in the @Component, we do not need to pass JavaScript objects to the template.

Whereas in ASP.Net MVC we have a strict distinction between Controller and View, and process and data flow are from the controller to the view, and the view cannot (usually does not) reference the controller, this distinction does not apply in Angular2, where we end up in the component after the template has been rendered.

Angular2 “Does not support more than 9 expressions”

If you need to pass more than just a couple of values from your Angular2 component to the template, you’ll sooner or later run into the error message

EXCEPTION: Does not support more than 9 expressions

I needed to do just that to provide TypeScript/JavaScript objects with a huge set of values. The solution to work around this restriction is to create an object in the component which stores all these values, and then access its JSONified value

export class MyComponent {
  o: any;

  constructor() {
    var o = { .... complex JavaScript object ... };
    this.o = JSON.stringify(o);
  }
}

from the template

< script >
  var o = {{ o }};
  ....
</script >

…except that <script> tags are not rendered in Angular2 templates.

We can still store the JSONified value in an HTML attribute. But to call custom or legacy JavaScript code, we can extend the @Component.

Mapping URL Querystring Parameters to Parameter Model Classes in Angular2

In ASP.Net MVC we can define a parameter model class, which is automatically filled by the framework before invocation of the controller’s method implementing the action. (More precisely, the Databinder sets the values of the public properties of the object)

public class QueryStringParameters 
{
  public int Id { get; set; }
  public string Foo  { get; set; }
  public string Bar  { get; set; }
}

Angular2 does not seem to provide such a mechanism out of the box. I found 3 classes handling URL parameters, and there are probably more

  • import { RouteParams } from 'angular2/router';
  • import { URLSearchParams } from 'angular2/http';
  • UrlParser in angular2/ts/src/router/url_parser

where UrlParser cannot be imported in TypeScript code.

So I tried to figure out how to use RouteParams and URLSearchParams and, using the URL http://localhost:3000/default?id=3&foo=8&bar=4711, found these differences:

routeParams.params

routeParams.params

URLSearchParams

URLSearchParams

Whereas routeParams.params is a simple Object with propery names derived from the query string, URLSearchParams contains a Map<string, string[]> (see url_search_params.d.ts in node_modules\angular2\src\http).

I chose routeParams to continue, because I do not use multiple occurrences of URL parameters.

URL parameters are case-sensitive in Angular2 / TypeScript / JavaScript, whereas they are case-insensitive in ASP.Net MVC. To copy the query string parameter values into a model class, a method to ignore case of both the URL parameters and the model class properties is therefore necessary.

To get the names of the parameters, we can use the function Object.keys() applied on routeParams.params, and we map the lower-case version of the name to the name.

Next, we iterate through the Object.keys of the parameter model class, and assign the model class property the value given by the query string parameter if one exists:

import {RouteParams} from 'angular2/router';

export function fromRouteParams<T>(model: T, routeParams: RouteParams): T {
  var rk = [];
  Object.keys(routeParams.params).forEach(k => { rk[k.toLowerCase()] = k; });

  var mk = Object.keys(model);
  mk.forEach(
    k => {
      if (rk.indexOf[k.toLowerCase()] != -1)
      model[k] = routeParams.get(rk[k.toLowerCase()]);
  });

  return model;
}

Finally, given the TypeScript parameter model class

export class QueryStringParameters {
  public Id: number = null;
  public Foo: number = null;
  public Bar: number = null;
}

the component defined as

import {RouteParams} from 'angular2/router';
import {QueryStringParameters} from './model/QueryStringParameters';
import {fromRouteParams} from './helpers/routeParamsHelpers';
export class DefaultComponent { 
  private q: QueryStringParameters; 
  constructor(routeParams: RouteParams) {
    this.q = fromRouteParams(new QueryStringParameters(), routeParams);
  }
}

containing a simple Angular2 template

template: `Id: {{ q.Id }}, Bar: {{ q.Bar }}, Foo: {{ q.Foo }}`

we can navigate to the URL http://localhost:3000/default?id=3&foo=8&bar=4711 and view the resulting HTML:

Id: 3, Bar: 4711, Foo: 8

Note that my function does not perform type checks on the model class properties, as it just copies the original parameter string values.

Getting Started with Visual Studio Code and Angular2

Version 1.0 of Visual Studio Code has been released. Time to give it a try along with TypeScript and Angular2.

  • Download and install Visual Studio Code
  • Run Visual Studio Code
  • Check your settings regarding Updates and Telemetry (i.e. “phoning home”), and everything else
  • VS Code will start with the OS’es locale it it is supported. To change to English, create the file locale.json in your user directory C:\Users\[UserName]\AppData\Roaming\Code\User containing the lines
{
"locale":"en"
}

nodejs firewall

  • Navigate to http://localhost:3000, where :3000 is defined by the browser-sync nodes module

Which Version of TypeScript does Visual Studio use?

I really love TypeScript.

I started out with version 0.8 in VS 2010 because of a (mostly) client-side web project. I always tried to avoid JavaScript because I prefer programming languages that compile, and I found that finally TypeScript was the way to go.

Projects live on, and VS 2013 got installed on the PC, along with (manually installed) TypeScript 1.4. Now colleagues are already on VS 2015, and we need to have the same TypeScript compiler. So which version of TypeScript does Visual Studio use?

ts2013

No indication that version 1.4 is installed

As far as I’m aware, VS 2013 does not provide the means to display the currently used TypeScript version. Answers to a related question on SO suggest to

  • open cmd and run tsc -v
  • use VS Command Prompt and run tsc -v
  • open the Package Manager Console and run tsc -v

but the always result in the message

Unknown option ‘v’

What’s going on here?

TypeScript is installed under C:\Program Files\Microsoft SDKs\TypeScript (or C:\Program Files (x86)\Microsoft SDKs\TypeScript on 64-bit Windows). To my surprise, I found 3 versions installed: 0.8.0.0, 1.0, 1.4, but the list of installed software in Programs and Features only listed 0.8.0.0 (“TypeScript for Microsoft VS 2012”) and 1.4.

Where did TypeScript version 1.0 come from? Apparently it got installed while applying VS2013 Update 5, judging from the directory timestamps. Oops: It might also have been installed with VS2013, and some timestamps updated during upgrade.

Different versions, different help message

This is the output if you run tsc from the directory 0.8.0.0:

Syntax:   tsc [options] [file ..]

Examples: tsc hello.ts
          tsc --out foo.js foo.ts
          tsc @args.txt

Options:
  -c, --comments  Emit comments to output
  --declarations  Generates corresponding .d.ts file
  -e, --exec      Execute the script after compilation
  -h, --help      Print this message
  --module KIND   Specify module code generation: "commonjs" (default) or "amd"
  --nolib         Do not include a default lib.d.ts with global declarations
  --out FILE      Concatenate and emit output to single file
  --target VER    Specify ECMAScript target version: "ES3" (default), or "ES5"
  @<file>         Insert command line options and files from a file.

Note that there is no '-v' option!

This is the output of tsc run from directory 1.0:

Version 1.0.3.0
Syntax:   tsc [options] [file ..]

Examples: tsc hello.ts
          tsc --out foo.js foo.ts
          tsc @args.txt

Options:
  --codepage NUMBER             Specify the codepage to use when opening source
files.
  -d, --declaration             Generates corresponding .d.ts file.
  -h, --help                    Print this message.
  --mapRoot LOCATION            Specifies the location where debugger should loc
ate map files instead of generated locations.
  -m KIND, --module KIND        Specify module code generation: 'commonjs' or 'a
md'
  --noImplicitAny               Warn on expressions and declarations with an imp
lied 'any' type.
  --out FILE                    Concatenate and emit output to single file.
  --outDir DIRECTORY            Redirect output structure to the directory.
  --removeComments              Do not emit comments to output.
  --sourcemap                   Generates corresponding .map file.
  --sourceRoot LOCATION         Specifies the location where debugger should loc
ate TypeScript files instead of source locations.
  -t VERSION, --target VERSION  Specify ECMAScript target version: 'ES3' (defaul
t), or 'ES5'
  -v, --version                 Print the compiler's version: 1.0.3.0
  @<file>                       Insert command line options and files from a fil
e.

tsc -v results in

Version 1.0.3.0

This is the output of tsc run from directory 1.4:

Version 1.4.0.0
Syntax:   tsc [options] [file ...]

Examples: tsc hello.ts
          tsc --out file.js file.ts
          tsc @args.txt

Options:
 -d, --declaration                 Generates corresponding '.d.ts' file.
 -h, --help                        Print this message.
 --mapRoot LOCATION                Specifies the location where debugger should
locate map files instead of generated locations.
 -m KIND, --module KIND            Specify module code generation: 'commonjs' or
 'amd'
 --noEmitOnError                   Do not emit outputs if any type checking erro
rs were reported.
 --noImplicitAny                   Warn on expressions and declarations with an
implied 'any' type.
 --out FILE                        Concatenate and emit output to single file.
 --outDir DIRECTORY                Redirect output structure to the directory.
 --preserveConstEnums              Do not erase const enum declarations in gener
ated code.
 --removeComments                  Do not emit comments to output.
 --sourceMap                       Generates corresponding '.map' file.
 --sourceRoot LOCATION             Specifies the location where debugger should
locate TypeScript files instead of source locations.
 --suppressImplicitAnyIndexErrors  Suppress noImplicitAny errors for indexing ob
jects lacking index signatures.
 -t VERSION, --target VERSION      Specify ECMAScript target version: 'ES3' (def
ault), 'ES5', or 'ES6' (experimental)
 -v, --version                     Print the compiler's version.
 -w, --watch                       Watch input files.
 @<file>                           Insert command line options and files from a
file.

tsc -v results in

message TS6029: Version 1.4.0.0

Due to whatever installation magic the installers perform, the PATH environment variable contains the directories related to TypeScript in this order:

C:\Program Files\Microsoft SDKs\TypeScript\0.8.0.0\;
C:\Program Files\Microsoft SDKs\TypeScript\1.0\;
C:\Program Files\Microsoft SDKs\TypeScript\1.4\;

which explains why the oldest version of TypeScript is called when invoked from command line.

This sequence is the same in Visual Studio Command Prompt (2010), which is OK due to compatibility. But the Developer Command prompt for VS2013 prepends version 1.0, resulting in

C:\Program Files\Microsoft SDKs\TypeScript\1.0\;
C:\Program Files\Microsoft SDKs\TypeScript\0.8.0.0\;
C:\Program Files\Microsoft SDKs\TypeScript\1.0\;
C:\Program Files\Microsoft SDKs\TypeScript\1.4\;

This is because the VsDevCmd.bat – which initializes all VS-related paths and environment variables – checks for version 1.0, but no higher version:

@rem Add path to TypeScript Compiler
@if exist "%ProgramFiles%\Microsoft SDKs\TypeScript\1.0" set PATH=%ProgramFiles%\Microsoft SDKs\TypeScript\1.0;%PATH%
@if exist "%ProgramFiles(x86)%\Microsoft SDKs\TypeScript\1.0" set PATH=%ProgramFiles(x86)%\Microsoft SDKs\TypeScript\1.0;%PATH%

And my project?

And just in case you’re wondering which version of TypeScript your project is using, open its .csproj file in a text editor and search for TypeScript:

    <TypeScriptToolsVersion>1.4</TypeScriptToolsVersion>

Blogger Allen Conway had similar experience regarding this question.