While doing Cool:GEN migratiotions to Java and C# we produce rather big Angular applications.
Everything is fine: server runs a REST APIs, and client is an Angular application with components per each window, dialog or screen. The only problem is with the word big.
We observe that enterprises that used Cool:GEN to develop their workflow come to migration stage with applications containing thousands of windows. In simple cases, after assessment, clients are able to split their monolith workflow into a set of independent applications. But even then we are dealing with Angular applications containing hundreds to many thousands components.
Now, lets look at Angular world. Best practices advice to (and actually almost force you to) use Ahead Of Time, Ivy compilation of all components and their templates.
Naive attempt to build such monolith Angular application will most surely fail. Angular uses nodejs for build, and chances are close to 100% of nodejs to run out of memory during the ng build .
You may fight and throw at it a capable build machine with 16 or better with 32GB of RAM, and instruct nodejs to use all of it.
Well, it's rather poor and extensive way of dealing with scale problems but it works.
Next hurdle you run into is time. We know it might take days just to build it.
You may ask why?
Well, angular is doing its best to validate templates!
Unfortunately the only viable workaround is to switch this nice feature off for such a big project.
With such setup you're able to build angular project in just 20-30 minutes!
Well, this is a big progress if you compare complete failure vs something that at least passes the build.
But what's next?
Sure, there are next problems:
- scripts both development and production are of nonsense size: like several dozen megabytes for production, and some even higher number for development.
ng serve eats even more memory and builds even longer making nightmare out of development and support of such an application;
- startup of such application, if it will start at all, is very slow.
So, what can be done? How can we create a manageable Angular application containing that many components?
Angular advices Lazy-loading feature modules.
That's reasonable advice!
We can create coarse grained modules containing subsets of components or fine grained modules containing one component.
So, does it help?
Yes, but it does not solve all problems:
ng build and ng serve are still very slow;
- build produces multiple small scripts that are loaded on demand, so at least application works in browser.
Yet, other important problem is still there: we have multiple severly separated server REST controllers with components that represent them.
On the server side we have Java or C# REST controllers hosting business logic. They are separated per multiple projects probably managed by separate teams, and probably kept in separate GITs (or whatever). On the client side we have a fat angular project storing everything kept in single source repository.
This is not going to work from management perspective.
So the next step is try to split fat Angular project into multiple small projects. So, let's say we shall have 100 small angular libraries combinded by master project.
This is not going to work either due to nature of npm projects, as it will requre terabytes of cloned node_modules folders for each library, and many hours to build each of them.
It seems there is a room for improvments in npm area. There is no point to make dedicated copies of node_modules. It's much easier to have a local cache of artifacts.
So, what is the direction? How to create big angular project after all?
What we have here is:
- a big enterprise level application;
- it is modular but modules must work together to form desired workflow;
- different modules are supported by different teams (both server and client side);
- client Angular components correspond to REST controllers on the server.
- all pages use the same styles and the same set of UI controls;
Looking from this perspective all development can be seen as:
- development and support of unified styles and ui components that must be reused through the application;
- development of server side and REST controllers that implement business logic;
- development of templates of components (note that components themselves do nothing except expose their templates).
Studying this design suggests important and independent role of templates just like it is in AngularJS!
In contrast Angular templates are only a tool used by components. It's not obvious how to use thousands of templates without first building thousands components; neither it's obvious how to dynamically host those templates (routes do not help here).
Though not obvious it's still possible to do it though it requires use a bit lower level API than tutorials suggest. Ingredients are:
- use of Just In Time (in contrast to Ahead Of Time) compilation, and use View Enginev (in contrast to Ivy);
- use ViewContainerRef to host components dynamically;
- Dynamic components and modules that you can create on demand using templates loaded e.g. through
HttpClient .
To make things short we shall show the example of dynamic components in next article.
Here we shall emphasize that such design allows us to create small angular application that builds under 20 seconds with component templates served along with the REST controllers, and stored in the same Git.
So, if you say have a server subproject exposing REST controller say in the form: api/area/MyComponent then its template may be exposed as resource: page/area/MyComponent. Templates are loaded and compiled on demand at runtime thus making application light. At the same time templates may be cached in browser cache thus reducing number of roundtrips to the server.
People compare these two technologies, and it seems an established fact is that Angular is evolutionally more advanced framework. We're not going to contradict, contrary, we agree with it, but it's better for an opinion to be grounded on facts that one can evaluate and verify.
Fortunately we got a chance to make such a comparison.
We support conversions of Cool:GEN (a legacy CASE tool with roots in 80th) to java or C#. In its time Cool:GEN allowed to greatly automate enterprise development using Mainframes as a server side and Mainframe terminals or Win32 GUIs as clients.
The legacy of this tool are probably hundreds of business and database models, milions of programs generated on COBOL on Mainframes and on C or Java on Windows and Linux. All this runs to this time in many econimic sectors.
Usually the client is some enterprise that invested a lot into design, development and support of their business model using Cool:GEN but now most such clients a trying not to lose this legacy but to convert it into something that goes in parallel with todays technologies.
As original technology is sound, so it is possible to map it to todays Java or C# on server, REST or SOAP as a transport, and Angular, AngularJS or some other on client. Such automatic conversion is an essense of our conversions efforts.
To understand a scope consider a typical enterprise client that has 2-3 thousand windows that are backed by 20-30 thousand programs.
Now, consider that the conversion is done. On output among other things we produce a clean java or C# web application with REST and SOAP interface, and Angular or AngularJS web client that encapsulates those 2-3 thousand windows.
Each window definition is rather small 5-10 KB in html form, but whole mass of windows takes 10-30 MB, which is not small any more.
For AngularJS we generate just those html templates, but for Angular we need to generate separate components for each window that includes typescript class, template and style.
While amout of generated resource for AngularJS stays in those 10-30 MB, generated Angular takes at least 5-10 MB more.
The next step is build.
AngularJS builds distribution that includes all used libraries and a set of templates, and it takes something like a minute from the CPU. Produced output is about 300 KB minified script and those 10-30 MB of templates (multiple files with 5-10 KB each one).
Angular (here we talk about version 9) builds distribution that includes all used libraries and a set of compiled components that are to be loaded lazily on demand. Without of the both angular builder that performs tree shaking build takes days. With tree shaking off it takes 40 minutes. This is the first notable difference. Produced output for ES2015 (latest javascript) is about 1 MB, and 15-100 KB per each compiled component. This is the second notable difference that already impacts end user rather than developer.
The third difference is in the end user experience. Though we have built equalvalent Angular and AngularJS frontend we observe load time of angular is higher. This cannot only be ascribed to bigger file sizes. It seems internal initialization also takes more time for Angular.
So, our experience in this particular test shows that Angular has more room to improve. In particular: compile time, bundle size, runtime speed and simplicity of dynamic loading (we have strong cases when template compilation is not the best approach).
Although the golden age of IE8 has already passed and Microsoft
already has stopped its support, this browser still occupies about
3% of the of the world market desktop browsers. Despite this, many big organisations still
use this browser for enterprise web applications. We may confirm this, since we deal with
such organisations around the world. Companies try to get rid of IE8, but this
often requires Windows upgrade and resources to re-test all their web applications. If such company has many web terminals with Windows 7 or even with XP, this task becames rather expensive. So, this process
advances rather slowly. Meanwhile, these organizations don't stop development of new
web applications that must work on new HTML5 browsers and on old IE8.
A year ago we had developed an
UIUpload AngularJS directive and service that simplifies file uploading in web applications with AngularJS client. It works as expected on all HTML5 browsers.
But few days ago, we were asked to help with file uploading from
AngularJS web application that will work in IE8. We've spent few hours in order to investigate
existing third-party AngularJS directives and components. Here are few of them:
All of these directives for IE8 degrade down to <form> and <iframe> and then track the uploading progress. These solutions don't allow to select files for old browsers. At the same time, our aim was to implement an AngularJS directive that allows selecting a file and perform uploading, which will work for IE8 and for new browsers too.
Since IE8 neither supports FormData nor File API, thus, the directive must work with DOM elements directly. In order to open file selection dialog we need to hide <input type="file"/> element and then to route client-side event to it. When a file is selected it is sent to a server as multipart/form-data message. The server's result will be caught by hidden <iframe> element and passed to the directive's controller.
After few attempts we've implemented the desired directive. The small VS2015 solution that demonstrates this directive and server-side file handler you may download here.
The key feature of this directive is emulation of replace and template directive's definition properties:
var directive =
{
restrict: "AE",
scope:
{
id: "@",
serverUrl: "@",
accept: "@?",
onSuccess: "&",
onError: "&?",
},
link: function (scope, element, attrs, controller)
{
var id = scope.id || ("fileUpload" + scope.$id);
var template = "<iframe name='%id%$iframe' id='%id%$iframe' style='display: none;'>" +
"</iframe><form name='%id%$form' enctype='multipart/form-data' " +
"method='post' action='%action%' target='%id%$iframe'>" +
"<span style='position:relative;display:inline-block;overflow:hidden;padding:0;'>" +
"%html%<input type='file' name='%id%$file' id='%id%$file' " +
"style='position:absolute;height:100%;width:100%;left:-10px;top:-1px;z-index:100;" +
"font-size:50px;opacity:0;filter:alpha(opacity=0);'/></span></form>".
replace("%action%", scope.serverUrl).
replace("%html%", element.html()).
replace(/%id%/g, id);
element.replaceWith(template);
...
}
}
We used such emulation, since each directive instance (an element) must have unique name and ID in order to work properly. On the one hand template that returned by function should have a root element when you use replace. On the other hand, IE8 doesn't like such root element (e.g. we've not succeeded to dispatch the click javascript event to the <input> element).
The usage of the directive looks like as our previous example (see UIUpload):
<a file-upload=""
class="btn btn-primary"
accept=".*"
server-url="api/upload"
on-success="controller.uploadSucceed(data, fileName)"
on-error="controller.uploadFailed(e)">Click here to upload file</a>
Where:
- accept
- is a comma separated list of acceptable file extensions.
- server-url
-
is server URL where to upload the selected file.
In case when there is no "server-url" attribute the content of selected file will be
passed to success handler as a data URI.
- on-success
- A "success" handler, which is called when upload is finished successfully.
- on-error
- An "error" handler, which is called when upload is failed.
We hope this simple directive may help to keep calm for those of you who is forced to deal with IE8 and more advanced browsers at the same time.
Angular 2 is already available though there are a lot of code and libraries that are still in Angular 1.x.
Here we outline how to write AngularJS 1.x in the modern javascript.
Prerequisites: EcmaScript 2015, javascript decorators, AngularJS 1.x. No knowledge of Angular 2.0 is required.
Please note that decorators we have introduced, while resemble those from Angular 2, do not match them exactly.
A sample uses nodejs, npm and gulp as a building pipeline. In addition we have added Visual Studio Nodejs project, and maven project.
Build pipeline uses Babel with ES 2015 and decorator plugins to transpile sources into javascript that today's browsers do support. Babel can be replaced or augmented with Typescript compiler to support Microsoft's javascript extensions. Sources are combinded and optionally minified into one or more javascript bundles. In addition html template files are transformed into javascript modules that export a content of html body as a string literals. In general all sources are in src folder and the build's output is assembled in the dist folder. Details of build process are in gulpfile.js
So, let's introduce an API we have defined in angular-decorators.js module:
-
Class decorators:
Component(name, options?) - a decorator to register angular component.
Controller(name) - a decorator to register angular controller.
Directive(name, options?) - a decorator to register angular directive.
Injectable(name) - a decorator to register angular service.
Module(name, ...require) - a decorator to declare an angular module;
Pipe(name, pure?) - a decorator to register angular filter.
Component's and Directive's options is the same object passed into Module.component(), Module.directive() calls with difference that no
options.bindings , options.scope , options.require is specified.
Instead @Attribute(), @Input(), @Output(), @TwoWay(), @Collection(), @Optional() are used to describe options.bindings , and
@Host(), Self(), SkipSelf(), @Optional() are used to describe options.require
Every decorated class can use @Inject() member decorator to inject a service.
-
Member decorators:
Attribute(name?) - a decorator that binds attribute to the property.
BindThis() - a decorator that binds "this " of the function to the class instance.
Collection() - a decorator that binds a collection property to an expression in attribute in two directions.
Host(name?) - a decorator that binds a property to a host controller of a directive found on the element or its ancestors.
HostListener(name?) - a decorator that binds method to a host event.
Inject(name?) - an injection member decorator.
Input(name?) - a decorator that binds a property to an expression in attribute.
Optional() - a decorator that optionally binds a property.
Output(name?) - a decorator that provides a way to execute an expression in the context of the parent scope.
Self(name?) - a decorator that binds a property to a host controller of a directive found on the element.
SkipSelf(name?) - a decorator that binds a property to a host controller of a directive found on the ancestors of the element.
TwoWay() - a decorator that binds a property to an expression in attribute in two directions.
If optional name is omitted in the member decorator then property name is used as a name parameter.
@Host(), @Self(), @SkipSelf() accept class decorated with @Component() or @Directive() as a name parameter.
@Inject() accepts class decorated with @Injectable() or @Pipe() as a name parameter.
-
Other:
modules(...require) - converts an array of modules, possibly referred by module classes, to an array of module names.
Now we can start with samples. Please note that we used samples scattered here and there on the Anuglar site.
@Component(), @SkipSelf(), @Attribute()
-
In the Angular's component development guide there is a sample myTabs and myPane components.
Here its rewritten form
components/myTabs.js:
import { Component } from "../angular-decorators"; // Import decorators
import template from "../templates/my-tabs.html"; // Import template for my-tabs component
@Component("myTabs", { template, transclude: true }) // Decorate class as a component
export class MyTabs // Controller class for the component
{
panes = []; // List of active panes
select(pane) // Selects an active pane
{
this.panes.forEach(function(pane) { pane.selected = false; });
pane.selected = true;
}
addPane(pane) // Adds a new pane
{
if (this.panes.length === 0)
{
this.select(pane);
}
this.panes.push(pane);
}
}
components/myPane.js:
import { Component, Attribute, SkipSelf } "../angular-decorators"; // Import decorators
import { MyTabs } from "./myTabs"; // Import container's directive.
import template from "../templates/my-pane.html"; // Import template.
@Component("myPane", { template, transclude: true }) // Decorate class as a component
export class MyPane // Controller class for the component
{
@SkipSelf(MyTabs) tabsCtrl; //Inject ancestor MyTabs controller.
@Attribute() title; // Attribute "@" binding.
$onInit() // Angular's $onInit life-cycle hook.
{
this.tabsCtrl.addPane(this);
console.log(this);
};
}
- @Component(), @Input(), @Output()
-
In the Angular's component development guide there is a sample
myTabs component.
Here its rewritten form
components/heroDetail.js:
import { Component, Input, Output } from "../angular-decorators";
import template from "../templates/heroDetail.html";
@Component("heroDetail", { template }) // Decorate class as a component
export class HeroDetail // Controller class for the component
{
@Input() hero; // One way binding "<"
@Output() onDelete; // Bind expression in the context of the parent scope "&"
@Output() onUpdate; // Bind expression in the context of the parent scope "&"
delete()
{
this.onDelete({ hero: this.hero });
};
update(prop, value)
{
this.onUpdate({ hero: this.hero, prop, value });
};
}
@Directive(), @Inject(), @Input(), @BindThis()
-
import { Directive, Inject, Input, BindThis } from "../angular-decorators"; // Import decorators
@Directive("myCurrentTime") // Decorate MyCurrentTime class as a directive
export class MyCurrentTime // Controller class for the directive
{
@Inject() $interval; // "$interval" service is injected into $interval property
@Inject() dateFilter; // "date" filter service is injected into dateFilter property
@Inject() $element; // "$element" instance is injected into $element property.
@Input() myCurrentTime; // Input one way "<" property.
timeoutId;
// updateTime is adapted as following in the constructor:
// this.updateTime = this.updateTime.bind(this);
@BindThis() updateTime()
{
this.$element.text(this.dateFilter(new Date(), this.myCurrentTime));
}
$onInit() // Angular's $onInit life-cycle hook.
{
this.timeoutId = this.$interval(this.updateTime, 1000);
}
$onDestroy() // Angular's $onDestroys life-cycle hook.
{
this.$interval.cancel(this.timeoutId);
}
$onChanges(changes) // Angular's $onChanges life-cycle hook.
{
this.updateTime();
}
}
@Directive(), @Inject(), @HostListener(), @BindThis()
-
In the Angular's directive development guide there is a sample myDraggable directive.
Here its rewritten form. directives/myDraggable.js:
import { Directive, Inject, HostListener, BindThis } from "../angular-decorators"; // Import decorators
@Directive("myDraggable") // Decorate class as a directive
export class MyDraggable // Controller class for the directive
{
@Inject() $document; // "$document" instance is injected into $document property.
@Inject() $element;// "$element" instance is injected into $element property.
startX = 0;
startY = 0;
x = 0;
y = 0;
// Listen mousedown event over $element.
@HostListener() mousedown(event)
{
// Prevent default dragging of selected content
event.preventDefault();
this.startX = event.pageX - this.x;
this.startY = event.pageY - this.y;
this.$document.on('mousemove', this.mousemove);
this.$document.on('mouseup', this.mouseup);
}
@BindThis() mousemove(event) // bind mousemove() function to "this" instance.
{
this.y = event.pageY - this.startY;
this.x = event.pageX - this.startX;
this.$element.css({
top: this.y + 'px',
left: this.x + 'px'
});
}
@BindThis() mouseup() // bind mouseup() function to "this" instance.
{
this.$document.off('mousemove', this.mousemove);
this.$document.off('mouseup', this.mouseup);
}
$onInit() // Angular's $onInit life-cycle hook.
{
this.$element.css(
{
position: 'relative',
border: '1px solid red',
backgroundColor: 'lightgrey',
cursor: 'pointer'
});
}
}
@Injectable(), @Inject()
-
In the Angular's providers development guide there is a sample notifier service.
Here its rewritten form. services/notify.js:
import { Inject, Injectable } from "../angular-decorators"; // Import decorators
@Injectable("notifier") // Decorate class as a service
export class NotifierService
{
@Inject() $window; // Inject "$window" instance into the property
msgs = [];
notify(msg)
{
this.msgs.push(msg);
if (this.msgs.length === 3)
{
this.$window.alert(this.msgs.join('\n'));
this.msgs = [];
}
}
}
@Pipe()
-
In the Angular's filters development guide there is a sample reverse custom filter.
Here its rewritten form. filters/reverse.js:
import { Pipe } from "../angular-decorators"; // Import decorators
@Pipe("reverse") // Decorate class as a filter
export class ReverseFilter
{
transform(input, uppercase) // filter function.
{
input = input || '';
var out = '';
for(var i = 0; i < input.length; i++)
{
out = input.charAt(i) + out;
}
// conditional based on optional argument
if (uppercase)
{
out = out.toUpperCase();
}
return out;
}
}
- Module(), modules(), angular.bootstrap()
-
Here are an examples of a class representing angular module, and manual angular bootstrap:
import { angular, modules, Module } from "../angular-decorators"; // Import decorators
import { MyController } from "./controllers/myController"; // Import components.
import { HeroList } from "./components/heroList";
import { HeroDetail } from "./components/heroDetail";
import { EditableField } from "./components/editableField";
import { NotifierService } from "./services/notify";
import { MyTabs } from "./components/myTabs";
import { MyPane } from "./components/myPane";
import { ReverseFilter } from "./filters/reverse";
import { MyCurrentTime } from "./directives/myCurrentTime";
import { MyDraggable } from "./directives/myDraggable";
@Module( // Decorator to register angular module, and refer to other modules or module components.
"my-app",
[
MyController,
NotifierService,
HeroList,
HeroDetail,
EditableField,
MyTabs,
MyPane,
ReverseFilter,
MyCurrentTime,
MyDraggable
])
class MyApp { }
// Manual bootstrap, with modules() converting module classes into an array of module names.
angular.bootstrap(document, modules(MyApp));
Please see angular-decorators.js to get detailed help on decorators.
Some of our latest projects used file uploading feature. Whether this is an excel, an audio or an image file, the file uploading mechanism remains the same. In a web application an user selects a file and uploads it to the server. Browser sends this file as a multipart-form file attachment, which is then handled on server.
The default HTML way to upload file to server is to use <input type="file"> element on a form. The rendering of such element is different in different browsers and looks rather ugly. Thus, almost all well known javascript libraries like JQuery, Kendo UI etc. provide their own implementations of file upload component. The key statement here is "almost", since in AngularJS bootstrap we didn't find anything like that. It worth to say that we've found several third-party implementations for file upload, but they either have rather complex implementation for such simple task or don't provide file selection feature. This is the reason why we've decided to implement this task by ourselves.
Sources of our solution with upload-link directive and uiUploader service you may find here.
Their usage is rather simple.
E.g. for upload-ink directive:
<a upload-link
class="btn btn-primary"
accept=".*"
server-url="api/upload"
on-success="controller.uploadSucceed(data, file)"
on-error="controller.uploadFailed(e)">Click here to upload an image</a>
Where:
- accept
- is a comma separated list of acceptable file extensions.
- server-url
-
is server URL where to upload the selected file.
In case when there is no "server-url" attribute the content of selected file will be
passed to success handler as a data URI.
- on-success
- A "success" handler, which is called when upload is finished successfully.
- on-error
- An "error" handler, which is called when upload is failed.
Usage of uiUploader service is also easy:
uiUploader.selectAndUploadFile("api/upload", ".jpg,.png,.gif").
then(
function(result)
{
// TODO: handle the result.
// result.data - contains the server response
// result.file - contains uploaded File or Blob instance.
},
$log.error);
In case when the first parameter is null the result.data contains the selected file content as a
data URI.
In a web project we needed to provide a region selection tool.
This requirement is resulted in a javascript module selectionTool, and in an angularjs wrappers selection, and clip.
There are samples test.html, and angularjs-test.html.
The module is implemented through SVG manipulation. Selection paths are defined in terms of SVG.
The simplest way to start with this API is through test pages.
From the client perspective API allows to:
- create a new selection - click and drag path;
- select/unselect selection - click selection overlay or image area;
- move selected path - drag selected overlay, or click arrow keys;
- move selected edge - drag selected edge;
- move selected vertex - drag selected vertex;
- delete selected path - Delete button;
- add selection vertex - double click or ctrl + click a selection edge;
- remove selection vertex - double click or ctrl + click a selection vertex;
- scale selection - shift + drag selection, or shift + arrow keys;
- rotate selection - ctrl + drag selection, or ctrl + arrow keys.
Sources can be found at GitHub: nesterovsky-bros/selection.
Here we show two snall directives that help to build fixed menu bar in your angularjs application.
There are two ideas behind:
- Expose element's bounds into a scope for a manipulation (ui-bounds directive).
- Allow to react to scroll DOM event (ui-scroll directive).
Directive implementation is very simple. See bounds.html at GitHub.
The use cases are also trivial to unerstand and implement. Take a look at two of them.
- Fixed menu:
<div ng-style="{paddingTop: headerBounds.height.toFixed() + 'px' }">
<div style="position: fixed; z-index: 1; top: 0; width: 100%; background: menu"
ui-bounds="headerBounds">My header</div>
<div>
long content that produces a scroll bar.
</div>
</div>
- Synchronized scroll of table header
<div style="width: 50em; overflow: hidden; background: pink">
<div style="position: relative"
ng-style="{left: bodyBounds.left.toFixed() + 'px'}">header...<div>
</div>
<div style="width: 50em; height: 5em; overflow: auto; background: blue"
ui-scroll>
<div ui-bounds="bodyBounds">body...</div>
</div>
You can see the demo at: nesterovsky-bros/angularjs-api/master/angularjs/bounds.html.
In our angularjs projects we are often dealing with existing models that do not always fit to angularjs expectations.
Here is an example.
There is a model consisting of two arrays: for data, and for associated data. How to create an ng-repeat that displays data from both sources?
Consider a test controller (see a github sources, and a rawgit working sample):
model.controller(
"Test",
function()
{
this.records =
[
{ name: "record 1", state: "Draft" },
{ name: "record 2", state: "Public" },
{ name: "record 3", state: "Disabled" },
{ name: "record 4", state: "Public" },
{ name: "record 5", state: "Public" }
];
this.more =
[
{ value: 1, selected: true, visible: true },
{ value: 2, selected: false, visible: true },
{ value: 3, selected: true, visible: true },
{ value: 4, selected: false, visible: false },
{ value: 5, selected: false, visible: true }
];
this.delete = function(index)
{
this.records.splice(index, 1);
this.more.splice(index, 1);
};
});
Basically there are three approaches here:
- Change model.
- Adapt model to a single collection.
ng-repeat over first array and access the second array using $index scope variable.
We argued like this:
- It is often not an option to change a model, as it's how business data are described.
-
A model adaptation when we build a single collection from original two collections, and synchronize it back (if required) may unnecessary complicate things.
-
Thus let's get associated items by
$index variable.
This is an example of ng-repeat use:
<table border="1">
<tr>
<th>[x]</th>
<th>Name</th>
<th>Value</th>
<th>State</th>
<th>Actions</th>
</tr>
<tr ng-repeat="item in test.records track by $index"
ng-if="test.more[$index].visible">
<td>
<input type="checkbox" ng-model="test.more[$index].selected"/>
</td>
<td>{{item.name}}</td>
<td>{{test.more[$index].value}}</td>
<td>{{item.state}}</td>
<td>
<a href="#" ng-click="test.delete($index)">Delete</a>
</td>
</tr>
</table>
Look at how associated data is accessed: test.more[$index]... Our goal was to optimize that repeating parts, so we looked at ng-init directive.
Though docs warn about its use: "the only appropriate use of ngInit is for aliasing special properties of ngRepeat", we thought that our use of ng-init is rather close to what docs state, so we tried the following:
...
<tr ng-repeat="item in test.records track by $index"
ng-init="more = test.more[$index]"
ng-if="more.visible">
<td>
<input type="checkbox" ng-model="more.selected"/>
</td>
<td>{{item.name}}</td>
<td>{{more.value}}</td>
<td>{{item.state}}</td>
<td>
<a href="#" ng-click="test.delete($index)">Delete</a>
</td>
</tr>
...
This code just does not work, as it shows empty table, as if ng-if is always evaluated to false . From docs we found the reason:
- the priority of the directive
ng-if is higher than the prirority of the ng-init , and besides ng-if is a terminal directive;
-
as result
ng-if directive is bound, and ng-init is not;
- when
ng-if is evaluated no $scope.more is defined, so more.visible is evaluated to false .
To workaround ng-init /ng-if problem we refactored ng-if as ng-if-start /ng-if-end :
...
<tr ng-repeat="item in test.records track by $index"
ng-init="more = test.more[$index]">
<td ng-if-start="more.visible">
<input type="checkbox" ng-model="more.selected"/>
</td>
<td>{{item.name}}</td>
<td>{{more.value}}</td>
<td>{{item.state}}</td>
<td ng-if-end>
<a href="#" ng-click="test.delete($index)">Delete</a>
</td>
</tr>
...
This code works much better and shows a correct content. But then click "Delete" for a row with Name "record 2" and you will find that updated table is out of sync for all data that come from test.more array.
So, why the data goes out of sync? The reason is in the way how the ng-init is implemented: its expression is evaluated just once at directive's pre-link phase. So, the value of $scope.more will persist for the whole ng-init 's life cycle, and it does not matter that test.mode[$index] may have changed at some point.
At this point we have decided to introduce a small directive named ui-eval that will act in a way similar to ng-init but that:
- will run before
ng-if ;
- will be re-evaluated when it's value is changed.
This is it:
module.directive(
"uiEval",
function()
{
var directive =
{
restrict: 'A',
priority: 700,
link:
{
pre: function(scope, element, attr)
{
scope.$watch(attr["uiEval"]);
}
}
};
return directive;
});
The ui-eval version of the markup is:
...
<tr ng-repeat="item in test.records track by $index"
ui-eval="more = test.more[$index]"
ng-if="more.visible">
<td>
<input type="checkbox" ng-model="more.selected"/>
</td>
<td>{{item.name}}</td>
<td>{{more.value}}</td>
<td>{{item.state}}</td>
<td>
<a href="#" ng-click="test.delete($index)">Delete</a>
</td>
</tr>
...
It works as expected both during initial rendering and when model is updated.
We consider ui-eval is a "better" ng-init as it solves ng-init's silent limitations. On the other hand it should not try to evaluate any complex logic, as it can be often re-evaluated, so its use case is to alias a sub-expression. It can be used in any context and is not limited to items of ng-repeat .
Source code can be found at github, and a working sample at rawgit.
Stackoverfow shows that people are searching How to intercept $resource requests.
Recently we have written about the way to cancel angularjs $resource requests (see Cancel angularjs resource request).
Here we apply the same technique to intercept resource request.
Consider a sample (nesterovsky-bros/angularjs-api/master/angularjs/transform-request.html):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Intercept resource request</title>
<style type="text/css">.ng-cloak { display: none; }</style>
<script src="angular.js"></script>
<script src="angular-resource.js"></script>
<script>
angular.module("app", ["ngResource"]).
factory(
"services",
["$resource", function ($resource)
{
return $resource(
"http://md5.jsontest.com/",
{},
{
MD5:
{
method: "GET",
params: { text: null },
then: function (resolve)
{
this.params.text = "***" + this.params.text + "***";
this.then = null;
resolve(this);
}
},
});
}]).
controller(
"Test",
["services", function (services)
{
this.value = "Sample text";
this.call = function()
{
this.result = services.MD5({ text: this.value });
}
}]);
</script>
</head>
<body ng-app="app" ng-controller="Test as test">
<label>Text: <input type="text" ng-model="test.value" /></label>
<input type="button" value="call" ng-click="test.call()"/>
<div ng-bind="test.result.md5"></div>
</body>
</html>
How it works.
$resource merges action definition, request params and data to build a config parameter for an $http request.
- a
config parameter passed into an $http request is treated as a promise like object, so it may contain then function to initialize config .
- action's
then function may transform request as it wishes.
The demo can be found at transform-request.html
Having a strong experience in ASP.NET and JSF, we found angular's transclusion concept is obscure and counterintuitive. It took a while for both of us to grasp the transclude's ideas described the Developer Guide. We suspect that this is due to the bad design: a bad design leads to a bad wording.
The other consequence of the bad design is that the transclusion is limited to one template per directive, which limits the use of the feature.
Consider:
- A directive
my-page that encapsulates a page with menu and content.
my-page uses templateUrl: my-page.html to render the page.
my-page.html defines two sites where menu and page content have to be embedded.
- Two content fragments are passed to
my-page to fill content sites.
Unfortunately, you cannot immediately implement this design in angularjs. On the other hand ASP.NET's Master Pages, and JSF's ui:composition readily solve this task.
Here is one of JSF's approaches:
-
Define page template my-page.xhtml:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html">
<h:body>
<table>
<tr>
<td><ui:insert name="menu"/></td>
</tr>
<tr>
<td><ui:insert name="content"/></td>
</tr>
</table>
</h:body>
</html>
-
Use
ui:composition tag to pass parts to the template:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html">
<h:body>
<ui:composition template="my-page.xhtml">
<ui:define name="content">
My Content
<ui:define>
<ui:define name="menu">
<a href="#file">File</a>
<a href="#edit">Edit</a>
<a href="#view">View</a>
<ui:define>
</ui:composition>
</h:body>
</html>
We have decided to model angular directives after JSF, and have defined three simple directives: ui-template, ui-insert, ui-define (see angularjs-api/template/ui-lib.js).
To define a template one writes the following markup (see angularjs-api/template/my-page.html):
<table ui-template>
<tr>
<td ui-insert="menu"></td>
</tr>
<tr>
<td ui-insert="content"></td>
</tr>
</table>
and declares a directive (see angularjs-api/template/my-page.js):
var myPage =
{
templateUrl: "my-page.html",
transclude: true
};
angular.module("app").
directive("myPage", function() { return myPage; });
and finally, to instantiate the directive one needs to write (see angularjs-api/template/sample.html):
<my-page>
<div ui-define="content">
My content
</div>
<div ui-define="menu">
<a href="#file">File</a>
<a href="#edit">Edit</a>
<a href="#view">View</a>
</div>
</my-page>
The working sample can be seen through rawgit: sample.html
The other sample that integrates with routing can be found at sample-routing.html
Two years ago, when we were still actively using KendoUI we had published our approach on how to introduce custom widgets, which we called User Controls.
We even suggested to introduce UserControl widget into KendoUI core through feedback page.
At that time we got a response from KendoUI team:
Our recommended approach for building reusable app building blocks is expressed via the Kendo UI SPA features delivered earlier this year. Here’s a getting started resource if you’ve not already seen it: http://www.kendoui.com/blogs/teamblog/posts/13-05-16/kendo-ui-spa-screencast-and-getting-started.aspx
It was not clear how the response is related to the suggestion, but we decided not to rebuke the team, and to proceed with our user controls.
Nowdays we use KendoUI no more, and have completely switched to angularjs. Ocasionally, however, we need to support old projects, and peek into docs. Today we've discovered that the team has changed their mind, and allowed custom widgets: "Create Your Own Kendo UI Widget by Inheriting from the Base Widget Class". That's good news!
But the most interesting thing is that the design of their custom widget is very close to what we have suggested then.
Angularjs is reasonaly good library but as many other big frameworks it tries to absorb every role in itself. This leads to code bloat of the framework and often limits developers with API available.
This problem repeats in angularjs on different levels. The top manifistation is module system.
Authors defined notion of module and registry for controllers, providers, directives, and so on. This is an example from Angularjs doc:
var myModule = angular.module('myModule', []);
// add some directives and services
myModule.service('myService', ...);
myModule.directive('myDirective', ...);
This API directs developers to build angularjs centric applications, because it's where module and registry is defined.
At the same time there exists de-facto standard API called Asynchronous Module Definition (AMD) to specify a mechanism for defining modules and their dependencies. This API has several implementations, where requirejs is, probably, the best known one. This is an AMD example:
define(["./cart", "./inventory"], function(cart, inventory) {
return {
color: "blue",
size: "large",
addToCart: function() {
inventory.decrement(this);
cart.add(this);
}
}
});
Every angularjs artifact can be mapped to AMD module. This could work equally well both at run time, and during unit tests. Without custom module implementation angularjs would be smaller, and more modular, so developer could pick up only required components. At the same time with AMD angularjs could treat many existing javascript classes as controllers and services without specific adaptation. Again, being more modular angularjs could provide multiple implementations of the same feature like jqLite vs jQuery, or $q vs native promises, for a developer to select.
So, we think modules in angularjs is a flaw, whose correction would improve it in many ways.
We needed to cancel angularjs $resource requests but have found that it's not trivial, though $http service has timeout property that allows to pass a promise that aborts a request when resolved.
So, we took a little time and divised a code to be able to cancel such requests.
Consider a sample (nesterovsky-bros/angularjs-api/master/angularjs/cancel-resource.html):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Cancel resource</title>
<style type="text/css">
.ng-cloak { display: none; }
</style>
<script src="angular.js"></script>
<script src="angular-resource.js"></script>
<script>
angular.module("app", ["ngResource"]).
factory(
"services",
["$resource", function($resource)
{
function resolveAction(resolve) // #1
{
if (this.params)
{
this.timeout = this.params.timeout;
this.params.timeout = null;
}
this.then = null;
resolve(this);
}
return $resource(
"http://md5.jsontest.com/",
{},
{
MD5:
{
method: "GET",
params: { text: null },
then: resolveAction // #2
},
});
}]).
controller(
"Test",
["services", "$q", "$timeout", function(services, $q, $timeout)
{
this.value = "Sample text";
this.requestTimeout = 100;
this.call = function()
{
var self = this;
self.result = services.MD5(
{
text: self.value,
timeout: $q(function(resolve) // #3
{
$timeout(resolve, self.requestTimeout);
})
});
}
}]);
</script>
</head>
<body ng-app="app" ng-controller="Test as test">
<label>Text: <input type="text" ng-model="test.value" /></label><br/>
<label>Timeout: <input type="text" ng-model="test.requestTimeout" /></label><br/>
<input type="button" value="call" ng-click="test.call()"/>
<div ng-bind="test.result.md5"></div>
</body>
</html>
How it works.
$resource merges action definition, request params and data to build a config parameter for an $http request.
- a
config parameter passed into an $http request is treated as a promise like object, so it may contain then function to initialize config .
- action's
then function may pass timeout promise from params into the config .
In code it looks like this:
- #2 - here we attach
then function to the action MD5;
- #1 -
then function implementation:
- move
timeout promise , if any, from params to config ;
- reset
then function from config to prevent resolve recursion (remember that we deal with merged config object, which is different from action definition);
- call
resolve function.
- #3 - While calling
services.MD5() we pass a timeout parameter as a promise that should be resolved to abort the request.
That's all. The demo can be found at cancel-resource.html
Often we need to keep a client session state in our angularjs application.
This state should survive page refresh and navigations within the application.
Earlier we used ngStorage module but lately have changed our opinion, as we think it's over-engineered and is too heavy at runtime.
We have replaced it with a simple service that synchronizes sessionStorage once during initialization, and once before page unload.
Look at an example (session.html):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Session</title>
<style type="text/css">
.ng-cloak { display: none; }
</style>
<script src="angular.js"></script>
<script>
angular.module("app", []).
factory(
"session",
["$window", function($window)
{
var session =
angular.fromJson($window.sessionStorage.getItem("app")) || {};
$window.addEventListener(
"beforeunload",
function()
{
$window.sessionStorage.setItem("app", angular.toJson(session));
})
return session;
}]).
controller(
"Test",
["session",
function(session)
{
this.state = session;
}]);
</script>
</head>
<body ng-app="app" ng-controller="Test as test">
<input type="text" ng-model="test.state.value"/>
<a href="session.html?p=1">Page 1</a>
<a href="session.html?p=2">Page 2</a>
</body>
</html>
Source can be found at nesterovsky-bros/angularjs-api/services/session.html.
At first we have found that Typeahead (ui.bootstrap.typeahead)
directive fits our needs, but later we run into its limitations.
These are tasks we required to solve:
- How to inform typeahead directive that it should update its list based on some event?
- How to implement an array of typeahead sources, where it's assumed that each next source delivers more data in cost of a longer working time?
The second task allows to show some data in popup almost immediately, while to provide more hints lately.
It took us a couple of days to answer both questions. The solution was either to write "typeahead " directive anew, or to write some additional "typeahead " directive to implement missing functionality. We have selected the later.
In the additional directive we:
- Handle a scople level event named "
updateSource ". Once the event is triggered the popup content is updated.
- Introduces scope.sources = function(value, sourcesFn) to build a source promise that knows how to update popup with more accurate data, when available.
Here is the code with small sample:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Typeahead</title>
<link type="text/css" rel="stylesheet" href="bootstrap.css" />
<style type="text/css">
.ng-cloak { display: none; }
</style>
<script src="angular.js"></script>
<script src="ui-bootstrap-tpls-0.12.0.js"></script>
<script>
angular.module("app", ["ui.bootstrap"]).
directive(
"typeahead",
["$q", function ($q)
{
return (
{
require: 'ngModel',
link: function(scope, element, attrs, controller)
{
/**
* @description
* Emits "updateSource" event on this scope.
* @returns an event.
*/
scope.updateSource = function()
{
return scope.$emit("updateSource");
};
scope.$on(
"updateSource",
function(event)
{
if (scope != event.targetScope)
{
return;
}
if (element.attr("aria-expanded") !== "true")
{
event.preventDefault();
return;
}
var value = controller.$modelValue;
for(var i = 0; i < controller.$parsers.length; i++)
{
if (value === undefined)
{
break;
}
value = controller.$parsers[i](value);
}
});
var typeaheadSources = null;
var typeaheadValue = null;
var typeaheadResult = null;
/**
* A wrapper of array of sources, where it's assumed that each next
* source delivers more data in cost of a longer working time.
*
* @param {object} value a typeahead hint.
*
* @param {function(object)} sourcesFn Function receiving typeahead
* hint, and returning an array of {Object|Promise}.
* If Object is passed rather than Promise then it should contain
* following properties:
*
* - `promise` - `{Promise}` - a result promise.
* - `cancel` - `{function()}` - optional function to cancel
* the promise.
*/
scope.sources = function(value, sourcesFn)
{
var result = typeaheadResult;
var prevValue = typeaheadValue;
typeaheadValue = controller.$modelValue;
typeaheadResult = null;
if (result && (prevValue === typeaheadValue))
{
return result;
}
var sources = typeaheadSources;
function cancel(count)
{
for (var i = 0; i < count; ++i)
{
var source = sources[i];
source.cancel && source.cancel();
}
}
sources && cancel(sources.length);
typeaheadSources = sources = sourcesFn(value);
return $q(function(resolve)
{
sources.forEach(function(source, index)
{
var promise = source.then ? source :
source.promise ? source.promise :
$q.when(source);
promise.then(function(result)
{
if (sources != typeaheadSources)
{
cancel(sources.length);
return;
}
if (element[0] != document.activeElement)
{
cancel(sources.length);
typeaheadSources = null;
return;
}
cancel(index);
if (!result || !result.length)
{
return;
}
if (resolve)
{
resolve(result);
resolve = null;
}
else
{
typeaheadResult = result;
if (scope.updateSource().defaultPrevented)
{
cancel(sources.length);
typeaheadSources = null;
typeaheadResult = null;
typeaheadValue = null;
}
}
});
});
});
}
}
});
}]).
controller(
"Test",
["$timeout",
function ($timeout)
{
this.text = null;
this.input = null;
function source(timeout, count, value)
{
return (
{
promise: $timeout(
function()
{
var result = [];
for(var i = 0; i < count; ++i)
{
result.push({ text: value + " " + i, id: i });
}
return result;
},
timeout),
cancel: function() { $timeout.cancel(this.promise); }
});
}
// Gets an array of objects in format:
// { promise: Promise, cancel: Function }
this.suggest = function(value)
{
return [source(500, 5, value), source(2000, 10, value)];
}
}]);
</script>
</head>
<body ng-app="app" ng-controller="Test as test">
<input type="text"
ng-model="test.text"
typeahead="items.text for items in sources($viewValue, test.suggest)"
typeahead-wait-ms="250"/><br />
<input type="text" ng-model="test.input" />
</body>
</html>
Demo can be found at typeahead.html, and source at nesterovsky-bros/angularjs-api/bootstrap/typeahead.html.
Earlier this year Mike Wasson has published a post: "Dependency Injection in ASP.NET Web API 2" that describes Web API's approach to the Dependency Injection design pattern.
In short it goes like this:
- Web API provides a primary integration point through
HttpConfiguration.DependencyResolver property, and tries to obtain many services through this resolver;
- Web API suggests to use your favorite Dependecy Injection library through the integration point. Author lists following libraries: Unity (by Microsoft), Castle Windsor, Spring.Net, Autofac, Ninject, and StructureMap.
The Unity Container (Unity) is a lightweight, extensible dependency injection container. There are Nugets both for Unity library and for Web API integration.
Now to the point of this post.
Unity defines a hierarchy of injection scopes. In Web API they are usually mapped to application and request scopes. This way a developer can inject application singletons, create request level, or transient objects.
Everything looks reasonable. The only problem we have found is that there is no way you to inject Web API objects like HttpConfiguration , HttpControllerContext or request's CancellationToken , as they are never registered for injection.
To workaround this we have created a small class called UnityControllerActivator that perfroms required registration:
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
using System.Web.Http.Dispatcher;
using Microsoft.Practices.Unity;
/// <summary>
/// Unity controller activator.
/// </summary>
public class UnityControllerActivator: IHttpControllerActivator
{
/// <summary>
/// Creates an UnityControllerActivator instance.
/// </summary>
/// <param name="activator">Base activator.</param>
public UnityControllerActivator(IHttpControllerActivator activator)
{
if (activator == null)
{
throw new ArgumentException("activator");
}
this.activator = activator;
}
/// <summary>
/// Creates a controller wrapper.
/// </summary>
/// <param name="request">A http request.</param>
/// <param name="controllerDescriptor">Controller descriptor.</param>
/// <param name="controllerType">Controller type.</param>
/// <returns>A controller wrapper.</returns>
public IHttpController Create(
HttpRequestMessage request,
HttpControllerDescriptor controllerDescriptor,
Type controllerType)
{
return new Controller
{
activator = activator,
controllerType = controllerType
};
}
/// <summary>
/// Base controller activator.
/// </summary>
private readonly IHttpControllerActivator activator;
/// <summary>
/// A controller wrapper.
/// </summary>
private class Controller: IHttpController, IDisposable
{
/// <summary>
/// Base controller activator.
/// </summary>
public IHttpControllerActivator activator;
/// <summary>
/// Controller type.
/// </summary>
public Type controllerType;
/// <summary>
/// A controller instance.
/// </summary>
public IHttpController controller;
/// <summary>
/// Disposes controller.
/// </summary>
public void Dispose()
{
var disposable = controller as IDisposable;
if (disposable != null)
{
disposable.Dispose();
}
}
/// <summary>
/// Executes an action.
/// </summary>
/// <param name="controllerContext">Controller context.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Response message.</returns>
public Task<HttpResponseMessage> ExecuteAsync(
HttpControllerContext controllerContext,
CancellationToken cancellationToken)
{
if (controller == null)
{
var request = controllerContext.Request;
var container = request.GetDependencyScope().
GetServices(typeof(IUnityContainer)) as IUnityContainer;
if (container != null)
{
container.RegisterInstance<HttpControllerContext>(controllerContext);
container.RegisterInstance<HttpRequestMessage>(request);
container.RegisterInstance<CancellationToken>(cancellationToken);
}
controller = activator.Create(
request,
controllerContext.ControllerDescriptor,
controllerType);
}
controllerContext.Controller = controller;
return controller.ExecuteAsync(controllerContext, cancellationToken);
}
}
}
Note on how it works.
IHttpControllerActivator is a controller factory, which Web API uses to create new controller instances using IHttpControllerActivator.Create() . Later controller's IHttpController.ExecuteAsync() is called to run the logic.
-
UnityControllerActivator replaces original controller activator with a wrapper that delays creation (injection) of real controller untill request objects are registered in the scope
To register this class one need to update code in the UnityWebApiActivator.cs (file added with nuget Unity.AspNet.WebApi )
public static class UnityWebApiActivator
{
/// <summary>Integrates Unity when the application starts.<summary>
public static void Start()
{
var config = GlobalConfiguration.Configuration;
var container = UnityConfig.GetConfiguredContainer();
container.RegisterInstance<HttpConfiguration>(config);
container.RegisterInstance<IHttpControllerActivator>(
new UnityControllerActivator(config.Services.GetHttpControllerActivator()));
config.DependencyResolver = UnityHierarchicalDependencyResolver(container);
}
...
}
With this addition we have simplified the boring problem with passing of CancellationToken all around the code, as controller (and other classes) just declared a property to inject:
public class MyController: ApiController
{
[Dependency]
public CancellationToken CancellationToken { get; set; }
[Dependency]
public IModelContext Model { get; set; }
public async Task<IEnumerable<Products>> GetProducts(...)
{
...
}
public async Task<IEnumerable<Customer>> GetCustomer(...)
{
...
}
...
}
...
public class ModelContext: IModelContext
{
[Dependency]
public CancellationToken CancellationToken { get; set; }
...
}
And finally to perform unit tests for controllers with Depenency Injection you can use a code like this:
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Dependencies;
using System.Net.Http;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.WebApi;
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class MyControllerTest
{
[ClassInitialize]
public static void Initialize(TestContext context)
{
config = new HttpConfiguration();
Register(config);
}
[ClassCleanup]
public static void Cleanup()
{
config.Dispose();
}
[TestMethod]
public async Task GetProducts()
{
var controller = CreateController<MyController>();
//...
}
public static T CreateController<T>(HttpRequestMessage request = null)
where T: ApiController
{
if (request == null)
{
request = new HttpRequestMessage();
}
request.SetConfiguration(config);
var controllerContext = new HttpControllerContext()
{
Configuration = config,
Request = request
};
var scope = request.GetDependencyScope();
var container = scope.GetService(typeof(IUnityContainer))
as IUnityContainer;
if (container != null)
{
container.RegisterInstance<HttpControllerContext>(controllerContext);
container.RegisterInstance<HttpRequestMessage>(request);
container.RegisterInstance<CancellationToken>(CancellationToken.None);
}
T controller = scope.GetService(typeof(T)) as T;
controller.Configuration = config;
controller.Request = request;
controller.ControllerContext = controllerContext;
return controller;
}
public static void Register(HttpConfiguration config)
{
config.DependencyResolver = CreateDependencyResolver(config);
}
public static IDependencyResolver CreateDependencyResolver(HttpConfiguration config)
{
var container = new UnityContainer();
container.RegisterInstance<HttpConfiguration>(config);
// TODO: configure Unity contaiener.
return new UnityHierarchicalDependencyResolver(container);
}
public static HttpConfiguration config;
}
P.S. To those who think Dependency Injection is an universal tool, please read the article: Dependency Injection is Evil.
We needed to have oauth2 authorization in angularjs project.
Internet search on the subject immediately brings large amout of solutions like:
But unfortunatelly:
- provider specific libraries have too different set of APIs, which requires another umbrella library to allow for application to accept several providers;
- angular-oauth - supports Google only, and does not work in IE 11 with default security settings;
- oauth.io looks attractive but adds additional level of indirection server, and is free for a basic plan only.
However there is a problem with all those approaches.
Let's assume that you have properly implemented client side authorization, and finally have gotten an access_token .
Now, you access your server with that access_token . What is your first step on the server?
Right! Your should validate it against oauth2 provider.
So, while client side authorization, among other things, included a validation of your token, you have to perform the validation on the server once more.
At this point we felt that we need to implement our oauth2 API.
It can be found at nesterovsky-bros/oauth2.
This is the readme from that project:
Here we implement oauth2 authorization within angularjs.
Authentication is done as follows:
- Open oauth2 provider login/grant screen.
- Redirect to the oauth2 callback screen with access token.
- Verify of the access token against provider.
- Get some basic profile.
A base javascript class OAuth2 implements these steps.
There are following implementations that authorize against specific providers:
OAuth2Server - implements authorization through known providers, but calls server side to validate access token. This way, the server side can establish a user's session.
The file Config.json contains endpoints and request parameters per supported provider.
Note: You should register a client_id for each provider.
Note: user_id and access_tokens are unique only in a scope of access provider, thus a session is identified by Provider + access_token, and a user is identified by Provider + user_id.
The use case can be found in test.js E.g. authorization against OAuth2Server is done like this:
var login = new OAuth2Server(provider);
token = login.authorize();
token.$promise.then(
function()
{
// token contains populated data.
},
function(error)
{
if (error)
{
// handle an error
}
});
Authorization token contains:
- a promise to handle authorization outcome.
- cancelToken (a Deferred object) to cancel authorization in progress.
Whole sample is implemented as VS project. All scripts are build with app.tt, that combines content of Scripts/app int app.js.
Server side is implemented with ASP.NET Web API. Authorization controllers are:
After several years of experience with KendoUI we turned our attention to AngularJS. As many other libraries it has its strong and weak sides. There are many resources describing what AngularJS is, and what it is not. Our approach to study AngularJS was through an attempt to integrate it into an existing KendoUI web application.
It's rather straightforward to convert model from KendoUI into AngularJS, as logically both frameworks are equal in this regard. But tactically KendoUI implements model-view binding very differently than AngularJS does. KendoUI binds model to view immediately per each model field, where AngularJS delays a binding of each model field and performs whole model binding in one go. Angular's approach is more performant, and even more appealing to a developer, though the problem is that the time it takes to make whole model binding is proportional to a size (number of objects and properties) of model. This means that if you have a relatively big model you will experience tangible halts in browser's UI while a javascript updating view/model is running.
AngularJS advices some workaround, which in essence is to avoid big model. The problem is that a couple of thousands or even several hundrends of objects and properties are already considered big model. So, you should immediately plan your model, and view to avoid any potential impact. This seriously distracts from the task your're solving.
The idea that your UI will halt for the time proportional to the size of your whole model looks flawed in our opinion. KendoUI knows no such a problem. That's the reason why our KendoUI to AngularJS transition experience was not smooth.
Our analysis of AngularJS sources shows that the issue could be resolved provided model to view binding (it's called digest in that library) was asynchronous.
To verify our ideas we have created a branch nesterovsky-bros/angular.js where we implemented required refactorings. It includes:
-
API based on existing deferred/promise to write algorithms in async way, and
- refactored digest logic.
At the end we have proposed to integrate our changes into the main branch: Make $digest async.
We're not sure whether our proposition will be integrated (rather no than yes). Nevertheless what we have come with is an interesting extension of deferred object that we neither have seen in AngularJS nor in JQuery, so later we will quote that API from q.js and scheduler.js.
|