Decorating angular exceptionHandler service in Typescript using VS 2013 - angularjs

Using Typescript and Visual Studio 2013
I want to decorate the angularjs $exceptionHandler and I'm not quite sure of the correct way to do it. I have decorated $log and it works fine.
My configuration:
MyAngularModule.config(['$provide',($provide) => {
$provide.decorator('$log', ['$delegate', function ($delegate) {
var myLog: MyLogService.LogService = new MyLogService.LogService();
return myLog;
}]);
}]);
MyLogService.ts
export = Example;
module Example {
export class LogService implements ng.ILogService{
constructor() {}
public assertEmpty = () => { };
public reset = () => { };
public info: ng.ILogCall = ...all my code...
}
}
Now when I try doing a similar thing with $exceptionHandler:
$provide.decorator('$log', ['$delegate', function ($delegate) {
var myExHandler: MyExHandler.ExHandlerService = new MyExHandler.ExHandlerService();
return myExHandler;
}]);
The interface definition for exceptionHandler in the type files is:
interface IExceptionHandlerService {
(exception: Error, cause?: string): void;
}
How do I code this similar to the way I did in decorating the log service?
I tried:
export = Example;
module Example {
export class ExHandlerService implements ng.IExceptionHandlerService{
constructor(anException: Error, aCause?: string) {}
}
}
And Visual Studio is complaining that the interface is implemented wrong. I don't really understand what (exception: Error, cause?: string): void; means, is it the constructor? The typings file does show a name, just this anonymous method definition.
I don't understand how I even approach this in the decorate call (ie. where do the error and cause parameters come from and how do I keep the default implementation operating as well?)
Thanks in advance.
Update:
Considering in angular.d.ts:
interface IExceptionHandlerService {
(exception: Error, cause?: string): void;
}
I guess my root confusion is how this interface would be implemented in Typescript??
Ok, I found that interfaces can define a function type in this case. That created confusion with the way I have viewed interfaces typically. It seems I can't use an "implements" in this case?

I think I've found my own answer. In any case here is how I decorated the angular exception handler service in Typescript.
export class MyExceptionHandlerService {
private _defaultExceptionHandlerService: ng.IExceptionHandlerService;
constructor(paramDelegate: ng.IExceptionHandlerService) {
this._defaultExceptionHandlerService = paramDelegate;
}
public exception: ng.IExceptionHandlerService = (paramException: Error, paramCause?: string): void => {
console.error("MY ERROR HANDLER: " + paramException.message)
this._defaultExceptionHandlerService(paramException, paramCause);
}
}
And I use it from my angular config section like this:
$provide.decorator('$exceptionHandler', ['$delegate', function ($delegate: ng.IExceptionHandlerService) {
var localExceptionHandler: MyHandler.ExceptionHandlerService = new MyHandler.ExceptionHandlerService($delegate);
return localExceptionHandler.exception;
}]);
It's working as expected for me.

You could have also done something like this without using objects...not sure if this is good or not. Feedback would be awesome!
((): void => {
'use strict';
angular
.module('app.blocks')
.config(config);
config.$inject = ['$provide'];
function config($provide: ng.auto.IProvideService): void {
$provide.decorator('$exceptionHandler', extendExceptionHandler);
}
extendExceptionHandler.$inject = ['$delegate', 'app.blocks.logger'];
function extendExceptionHandler(
$delegate: any,
logger: app.blocks.ILogFactory): Function {
return (exception: Error, cause?: string, source? : string) => {
$delegate(exception, cause);
logger.log(exception.message, { exception: exception, cause: cause }, source, 'error');
}
}
})();

Related

Angular 2 TS object array only defined while subscribed to service

I'm in the process of learning Angular 2 using TypeScript. So far I've written a little API service that uses HTTP get method to feed me json data using observables. Everything is working fine, I can use the data in my view, I can also use the data in my component, but only while I'm subscribed to the getData() method.
Why is that and what other possibilities do I have to make the object array available to all methods in my component for easy iteration and management?
Example component:
export class SomeComponent implements OnInit {
public someData: DataObject[];
public constructor(private service: SomeService) {}
public ngOnInit(): void {
this.loadData();
this.useData();
}
private loadData(): void {
this.service.getData().subscribe(data=> {
this.someData = data;
this.someData.forEach(dataObject => {
// this works fine
});
});
}
private useData(): void {
this.someData.forEach(dataObject => {
// dataObject is (of type?) undefined, why?
});
}
}
It's because http calls are async. Your this.useData(); does not wait this.loadData(); to finish. This should work:
private loadData(): void {
this.service.getData().subscribe(data=> {
this.someData = data;
this.useData();
});
}

Restangular with Typescript confusing when 'restangularized' response object

I am using Angular 1.5.x with TypeScript. For accessing a remote API I use restangular. As a summary this is my scenario:
My API has the following resource http://localhost:53384/api/timezones. Sending a request with the verb GET to that url returns a JSON array:
[
{
"code":"Dateline Standard Time",
"name":"(UTC-12:00) International Date Line West"
},
{
"code":"UTC-11",
"name":"(UTC-11:00) Coordinated Universal Time-11"
},
{
"code":"Hawaiian Standard Time",
"name":"(UTC-10:00) Hawaii"
}
]
Now in my client AngularJs application with TypeScript:
Restangular configuration being restangularProvider: restangular.IProvider
restangularProvider.setBaseUrl("http://localhost:53384/api");
The TimeZone object representation in the client side with typescript
module app.blocks {
"use strict";
export class TimeZone {
public code: string;
public name: string;
}
}
Factory(restangular.IService) to wrap the restangular all 'timezones' resource
module app.services {
factory.$inject = ["Restangular"];
function factory(restangular: restangular.IService): restangular.IElement {
return restangular.all("timezones");
}
angular
.module("app.services")
.factory("app.services.TimeZonesRestangular", factory);
}
Service that uses TimeZonesRestangular to wrap its restangular functionality and return chained promises to whoever requests timezones in an asynchronous way
module app.services {
"use strict";
export interface IStaticDataService {
getTimeZones(): ng.IPromise<app.blocks.TimeZone[]>;
}
class StaticDataService implements IStaticDataService {
constructor(private timeZonesRestangular: restangular.IElement) {
}
public getTimeZones(): ng.IPromise<blocks.TimeZone[]> {
return this.timeZonesRestangular.getList()
.then((timeZones: blocks.TimeZone[]) => {
return timeZones;
}, (restangularError: any) => {
throw "Error retrieving time zones. Status: " + restangularError.status;
});
}
}
factory.$inject = ["app.services.TimeZonesRestangular"];
function factory(timeZonesRestangular: restangular.IElement): IStaticDataService {
return new StaticDataService(timeZonesRestangular);
}
angular
.module("app.services")
.factory("app.services.StaticDataService", factory);
}
And finally in the controller using the service to get the 'timezones' asynchronously I have this statement
//..other controller things not relevant for this sample
this.staticDataService.getTimeZones()
.then((timeZones: blocks.TimeZone[]) => {
this.timeZones = timeZones;
});
There are 2 PROBLEMS:
The type definition for restangular (which I installed with tsd install restangular --resolve --save) tells me that the successCallback in the getTimeZones() method is a promiseValue: any[], which is fine because it is indeed an array. I thought it would be an array of TimeZone[] and typescript compiles properly because it accepts any[], but when debuggin I see that the successCallback promised value it's not an array of TimeZone[]. It has the properties I expected (code and name) but it also has many other things restangular-ish. An object within that array looks like this (plus some functions):
{
"code":"Dateline Standard Time",
"name":"(UTC-12:00) International Date Line West",
"route":"timezones",
"reqParams":null,
"restangularized":true,
"fromServer":true,
"parentResource":null,
"restangularCollection":false
}
As per https://github.com/mgonto/restangular/issues/150 it looks as if my response had been "restangularized". Scary description for somebody new to restangular like myself..
What interface in restangular type definition should I use to represent the array of restangularized TimeZone[] ?
Is there any example on how to achieve something similar with TypeScript?
Thank you.
After digging a little bit further I found out that the best way to handle this is by expecting a promised value of type restangular.ICollection (which inherits from IService and Array<any>) so that I can de-restangularize the response like this:
public getTimeZones(): ng.IPromise<blocks.TimeZone[]> {
return this.timeZonesRestangular.getList()
.then((restangularizedTimeZones: restangular.ICollection) => {
return restangularizedTimeZones.plain();
}, (restangularError: any) => {
throw "Error retrieving time zones. Status: " + restangularError.status;
});
}
Now everthing seems to be fine and the response is, indeed, a promise of TimeZone[]

Using $http interceptor with custom fields in a typesafe manner in angular + typescript

I currently implemented an angular $http interceptor adding custom headers to requests based on a localstorage value (I need to implement a "Su" feature in my app)
I need to "deactivate" this behaviour on some special requests (=I need to be able to configure this on a per-request basis), and I'd like to do this by putting an extra config parameter denoting this when calling my $http methods.
The interceptor is looking like this :
$httpProvider.interceptors.push((localStorageService: ng.local.storage.ILocalStorageService) => {
return {
request: (config: ng.IRequestShortcutConfig) => {
var su = localStorageService.get<string>('Su');
if(su && !("avoidSudo" in config)){
config.headers.Su = `{ "principal": "${su}" }`;
}
return config;
}
}
});
And the $http service call is looking like this when I want to deactivate my "su" feature :
this.$http.get('/api/sessions/current', { avoidSudo: true })
In typescript 1.6, this code doesn't compile as $http.get()'s second argument is expected to be a ng.IRequestShortcutConfig which obviously doesn't contain my specific avoidSudo field (Typescript compilation error is perfectly right here)
I can workaround the compilation error by replacing the { avoidSudo: true } by <any>{ avoidSudo: true } but this is clearly not ideal in terms of typesafety
I tried to create a new SkipSudoRequestShortcutConfig dedicated class (implementing ng.IRequestShortcutConfig) for this purpose. Something like this :
module mymodule {
export class SkipSudoRequestShortcutConfig implements ng.IRequestShortcutConfig {
// For testing purposes only
_noSudo: boolean;
constructor(
public params?: any,
public headers?: any,
public xsrfHeaderName?: string,
public xsrfCookieName?: string,
public cache?: any,
public withCredentials?: boolean,
public data?: any,
public transformRequest?: any,
public transformResponse?: any,
public timeout?: any,
public responseType?: string
){
this._noSudo = true;
}
}
}
called like this :
var config = new SkipSudoRequestShortcutConfig();
console.log(config instanceof SkipSudoRequestShortcutConfig); // Shows true
console.log(config._noSudo); // Shows true
this.$http.get('/api/sessions/current', config)
and used like this in the interceptor :
request: (config: ng.IRequestShortcutConfig) => {
var su = localStorageService.get<string>('Su');
console.log(config instanceof SkipSudoRequestShortcutConfig); // Shows *false* :(
// console.log(config._noSudo); // Doesn't compile, but if executed at runtime with a breakpoint, it works and display true
if(su && !(config instanceof mymodule.SkipSudoRequestShortcutConfig)){
config.headers.Su = `{ "principal": "${su}" }`;
}
return config;
}
but once in the request handler, the instanceof test was falsy.
I was wondering what would be the best/simplest way to achieve this goal.
Do you think the ng.IRequestShortcutConfig is missing a special config field allowing to put custom fields from $http invocations to interceptor handlers ?
Thanks in advance,
It's important to remember that types in TypeScript are just "type helpers" and removed when transpiled to javascript. So you don't need to implement a new $http service. Instead you can just create a new type that suites your needs.
In reality it's because the angular type definition is lacking.
You can fix this by creating the following interfaces.
export interface IRequestShortcutConfigWithCustomConfig extends ng.IRequestShortcutConfig {
[key: string]: any;
}
I'm using a dictionary style type cause that'll support everything. But you could change that to avoidSudo: boolean; if you don't want a general definition.
export interface IHttpServiceWithCustomConfig extends ng.IHttpService {
get<T>(url: string, config?: IRequestShortcutConfigWithCustomConfig): ng.IHttpPromise<T>;
}
Then in your controller you just use the new interface.
constructor(private $http: IHttpServiceWithCustomConfig) {
$http.get("/api/sessions/current", { avoidSudo: true }
}
You do the exact same with IHttpInterceptor
export interface IRequestConfigWithCustomConfig extends ng.IRequestConfig, IRequestShortcutConfigWithCustomConfig {
}
export interface IHttpInterceptorWithCustomConfig extends ng.IHttpInterceptor {
request?: (config: IRequestConfigWithCustomConfig) => IRequestConfigWithCustomConfig | ng.IPromise<IRequestConfigWithCustomConfig>;
}

How to use Typescript Q.when()

I have a function that returns angular.IPromise<any>
onActivate():IPromise<any>
{
return Q.when(true);
}
but when it runs this it says
Q is not defined
Although, when I look in Q.d.ts file, the module is declared and the function when is exported.
How to use Q service in typescript classes?
The entire class is this:
/// <reference path="../../typings/q/q.d.ts" />
module app.common.modals
{
export class RenameModalCtrl extends app.common.controllers.ControllerBase
{
public viewModel: RenameModalModel;
private $modalInstance: ng.ui.bootstrap.IModalServiceInstance;
static $inject = ['common', '$translate', '$modalInstance', 'viewModel'];
constructor(common: any, $translate: angular.translate.ITranslateService, $modalInstance: ng.ui.bootstrap.IModalServiceInstance, viewModel: RenameModalModel)
{
super(common, $translate);
this.viewModel = viewModel;
this.$modalInstance = $modalInstance;
}
onActivate(): ng.IPromise<any>
{
return Q.when(true);
}
}
angular.module('app').controller('renameModalCtrl', RenameModalCtrl);
}
Thanks
You have to add $q as one of your injectables in $inject. Then add it to your constructor as type Q: ng.IQService
edit: Thought you meant the angular q. I think you just need to import the module:
import * as Q from 'q';

How can I define an AngularJS service using a TypeScript class that doesn't pollute the global scope?

I am using AngularJS and TypeScript. I want to implement an AngularJS service using a Typescript class, like this:
class HelloService {
public getWelcomeMessage():String {
return "Hello";
}
}
angular.module('app.services.helloService', []).factory('helloService', () => {
return new HelloService();
});
This compiles to the following javascript code:
var HelloService = (function () {
function HelloService() {
}
HelloService.prototype.getWelcomeMessage = function () {
return "Hello";
};
return HelloService;
})();
angular.module('app.services.helloService', []).factory('helloService', function () {
return new HelloService();
});
This pollutes the global namespace with the variable HelloService, which I obviously don't want. (Using Chrome's console I verified that HelloService was an object.) How can I solve/avoid this problem?
I tried the obvious:
angular.module('app.services.helloService', []).factory('helloService', function () {
class HelloService { ...}
return new HelloService();
});
but that gives me a compile error ("Unexpected token; 'statement' expected.").
One possible solution I can think of is using TypeScript's import and export somehow, which in turn will use RequireJS. This probably will wrap the HelloService within a define function, thus avoiding pollution of the global scope with HelloService. However, I don't want to use RequireJS in my AngularJS application for now, as I think AngularJS is good enough for my use, and it adds complexity.
So, my question is, how can I define an AngularJS service using a TypeScript class that doesn't pollute the global scope?
2016-05-06: New example using ES6-style modules
The static $inject array and constructor remain unchanged from the previous example.
The only change is to split the classes into multiple files and use ES6 modules to pull in the class definitions.
/lib/HelloService.ts:
export class HelloService {
public getWelcomeMessage():String {
return "Hello from HelloService";
}
}
/lib/AnotherService.ts:
import {HelloService} from './HelloService';
/**
* Service that depends on HelloService.
*/
export class AnotherService {
// Define `HelloService` as a dependency.
static $inject = ['HelloService'];
constructor(
// Add the parameter and type definition.
public HelloService: HelloService
){}
public getWelcomeMessage():String {
// Access the service as: `this.HelloService`
// Enjoy auto-completion and type safety :)
var helloMsg = this.HelloService.getWelcomeMessage();
return "Welcome from AnotherService, " + helloMsg;
}
}
/index.ts:
// Using the services.
import {HelloService} from './lib/HelloService';
import {AnotherService} from './lib/AnotherService';
angular.module('HelloApp', [])
.service('HelloService', HelloService)
.service('AnotherService', AnotherService)
.run(['AnotherService', function(AnotherService: AnotherService){
console.log(AnotherService.getWelcomeMessage());
}]);
Previous answer: using namespaces
Building from Steve Fenton's answer:
To allow dependency injection, add a static $inject array on your class.
See the Angular $injector documentation on how the $inject array works.
The dependencies will be injected into your constructor in the order given by the array (and makes it work with minification).
Dependency Injection Example:
namespace MyModule {
/**
* Angular Service
*/
export class HelloService {
public getWelcomeMessage():String {
return "Hello from HelloService";
}
}
/**
* Service that depends on HelloService.
*/
export class AnotherService {
// Define `HelloService` as a dependency.
static $inject = ['HelloService'];
constructor(
// Add the parameter and type definition.
public HelloService: MyModule.HelloService
){}
public getWelcomeMessage():String {
// Access the service as: `this.HelloService`
// Enjoy auto-completion and type safety :)
var helloMsg = this.HelloService.getWelcomeMessage();
return "Welcome from AnotherService, " + helloMsg;
}
}
}
// Using the services.
angular.module('app.services.helloService', [])
.service('HelloService', MyModule.HelloService)
.service('AnotherService', MyModule.AnotherService)
.run(['AnotherService', function(AnotherService: MyModule.AnotherService){
console.log(AnotherService.getWelcomeMessage());
}]);
I should provide what I actually ended doing:
module MyModule {
export class HelloService {
public getWelcomeMessage():String {
return "Hello";
}
}
angular.module('app.services.helloService', []).factory('helloService', () => {
return new HelloService();
});
}
In this way I can use
return new HelloService();
instead of
return new MyModule.HelloService();
I have two solutions, the first gives you class-based syntax, the second leaves absolutely nothing in the global scope...
You could compromise slightly by only adding a single handle to the global scope (this really applies if you have multiple classes that you want to avoid placing in the global scope as currently you only have one class).
The following code leaves only the module in the global scope.
module MyModule {
export class HelloService {
public getWelcomeMessage():String {
return "Hello";
}
}
export class AnotherService {
public getWelcomeMessage():String {
return "Hello";
}
}
}
angular.module('app.services.helloService', []).factory('helloService', () => {
return new MyModule.HelloService();
});
angular.module('app.services.anotherService', []).factory('anotherService', () => {
return new MyModule.AnotherService();
});
Alternatively, to leave not a single thing in global scope, you could avoid the class syntax and use "plain old JavaScript":
angular.module('app.services.helloService', []).factory('helloService', () => {
var HelloService = (function () {
function HelloService() {
}
HelloService.prototype.getWelcomeMessage = function () {
return "Hello";
};
return HelloService;
})();
return new HelloService();
});
This is the method that I'm following:
module HelperServices {
export class helloService {
sayHelloWorld: () => string;
constructor() {
this.sayHelloWorld = () => {
return "Hello World";
}
}
}
}
As simple as that..

Resources