Thursday, September 22, 2016

Gradual migration of .net mvc application to Angular 2 - Part 2

Ok, so it wasn't quite as simple as I thought. Although it was possible as mentioned in the previous post to boot an Angular 2 root component within each individual page of an existing .Net MVC app there were two problems:

  1.  On each MVC page the Angular 2 root component needs to have a different child component in order to instantiate just the component for the given page - so this component needs to be dynamically set at run time.
  2. It is not possible to bind an attribute to the Angular 2 root component in order to use @import to pass in the child component.
Point number 2 was easily fixed by getting the raw element attribute on the root app component and binding this to the input of a nested <dynamic-component>.


Then the new dynamic component can take the name of the component passed in and insert this into its own template


This component needs to import all of the possible components that could be passed in as top level components (I have just included a couple of examples of a user-list and site-list component). I add these to a lookup in order to avoid a nasty switch statement in the DynamicComponent class.

This component sets up a view child at the #container div, and then uses ComponentFactoryResolver to create and insert the component at this point.

One important thing to note, is that any top level components that you are going to pass in this way need to be added to the entryComponents: setting of the module to which they belong. This is because these components will be loaded imperatively rather than declarativley.

In each MVC page the root component can be added and the first child component passed in as <app componentName="user-list"></app>

Wednesday, September 21, 2016

Gradual migration of .net mvc application to Angular 2 - Part 1

I have just been investigation the options for gradually migrating an existing .NET MVC application to Angular 2, as apposed to a complete rewrite.

I managed to get Angular 2 running within the app by building an Angular 2 app in a sub folder in the solution, building with Webpack, and then adding the generated polyfill.js, vendor.js and app.js files to the existing apps _Layout.cshtml template file.

This worked fine, and I was able to also add a <my-app></myapp> component tag in the same template file, and Angular 2 bootstrapped fine and instantiated the component.

The problem was that Angular 2 requires a single root level component to bootstrap the app, so the key to a slow migration of the app is to bootstrap the root angular component at the individual page view level, rather than at the top level _Layout.cshtml. This means adding the script links for polyfill.js, vendor.js and app.js as well at the <myapp></myapp> root component at the individual view level.

This way each page within the app can be moved to Angular 2 components one at a time, with all the existing MVC routing still in place. At a point when all pages have been migrated the overall MVC app architecture and routing can then be replaced with Angular 2 SPA architecture and routing. That is the plan anyway - we will see how it works out in practice.

Edit: Ok, so it was not quite that easy - see part 2.

Wednesday, June 29, 2016

Custom scrollbar for variable height flex items

We recently moved our single page app layout to a fully flex based grid. As part of this we locked the app into 100vh, and in a number of places we use flex to create panels 100% high with a fixed header and footer and a flexible height scrollable panel in the centre.

See https://plnkr.co/edit/TX1iIKpVktnDjw6k8Bvc for a basic example of this. When targeting modern browsers the amount of css needed to achieve this sort of layout is refreshingly trivial - once you get your head around flex.

Once we had this layout in place we wanted to get rid of the ugly browser scrollbars, particularly on windows, and apply a custom scrollbar. We wanted preferably to do this in a way that made use of the native OS scrolling. John Kurlak at Microsoft discovered an easy way of hiding the native scrollbars across browsers a couple of years ago, and this still works well. However, in order to use this approach, and overlay a custom scrollbar,  the scrollable section needs to have a fixed height and width .... but in our fancy new flex panels the scrollable section has a variable height .... what to do, what to do ....?

Thanks to some help from simple-scrollbar for the initial scroll and drag handling setup I was able to set up an angular directive that detects the current size of the variable height flex panel and wraps it in a fixed size container and applies the custom scrollbar. The directive also sets up a window resize watcher and mutation observer to detect changes in the size of the scrollable section. If a change is detected the fixed size is recalculated.

I also added drop shadows to the top and bottom of the scrollable section to indicate to the user that there is scrollable content above or below.

A basic working example can be seen at https://plnkr.co/edit/p1ps6Nx0vyldiXVv4xlf - if viewed on Firefox on Mac you may notice some native scrollbar still - there is a fix for this, but for some reason plunkr seems to identify Chrome on a mac as mozilla so had to comment this fix out.

Todo:

The initial proof of concept custom scroller is working, but there is still some tweaking to do:
  • Tidy up the methods and move bulk of functionality into vanilla ES6 module and make the angular directive a wrapper only
  • Add horizontal scroll support
  • Remove dependency on ng-lodash
  • Add tests
The proof of concept directive can be found at https://github.com/glendaviesnz/variable-flex-scroll.

If there is another custom scroll module out there that uses native OS scrolling and works perfectly with variable height flex items let me know. I had a good look around at the options but couldn't find anything that met both these criteria.




Tuesday, September 15, 2015

RxJs with jspm and Typescript

I just had to add RxJs to an Angular project that is using jspm, typescript and ES6 modules. This all went pretty smoothly with a simple


jspm install rx



However, when I came to use it I initially tried the same style import as with the jspm angular module, eg.


import 'rx';

var eventsStream = new Rx.Subject();



This gives an 'Rx is not defined' error as rx is a commonjs module, so not brashly exposing itself in the global namespace. This was easily fixed by changing to


import Rx from 'rx';



Everything then worked as expected in the browser - except the Typescript compiler was now complaining that "Module '"rx"' has no default export."

Changing the above to "import {Rx} from 'rx'" didn't help. The only workaround I could find to keep the typescript compiler happy was to change the last line of the rx.d.ts file  from


declare module "rx" { export = Rx; }



to


declare module "rx" { export default Rx; }



This change made everybody happy for now!

If there is a way to use ES6 import syntax with the jspm RxJs commonjs module without having to edit the rx.d.ts file please let me know.

EDIT: Thanks to Robert's comment below - turns out a better approach is to use the following import syntax, and this keeps the typescript compiler happy without having to edit rx.d.ts file.

import * as Rx from 'rx';

Monday, September 7, 2015

combineLatest Trap for RxJs newbies

I have been enjoying some of the features of RxJs on the refactor of a current project that needs to combine and react to changes in a number of different data streams.

One function I have made heavy use of is combineLatest, which combines the latest output from two observable sequences using a selector function.

I was initially under the impression that the selector function would only run when either of the combined streams emitted. However, while tracking down an obscure bug I discovered that in fact the selector function is run each time an observer subscribes, eg.

var combined  = Rx.Observable.combineLatest(obsOne, obsTwo, function(){

    console.log('I am combining streams');

});
    
var sub1 = combined.subscribe();
var sub2 = combined.subscribe();

//output
I am combining streams
I am combining streams

If the selector function is resource intensive, or has side effects that should only happen if the parent stream outputs change, then this is obviously not the most desirable outcome.

Luckily this is easily fixed by adding .shareReplay(1). This will cause the combineLatest to replay the last output from the selector function, unless the parent streams change., eg.

var combined  = Rx.Observable.combineLatest(obsOne, obsTwo, function(){{

    console.log('I am combining streams');

}).shareReplay(1);
    
var sub1 = combined.subscribe();
var sub2 = combined.subscribe();

//output
I am combining streams

Let me know if there is a better way to do this.

Monday, August 31, 2015

Angular 1.* + Typescript + ES6 Modules + jspm

We have just started work on a shiny new Angular project so I thought it would be worth reviewing the current state of the art as far as project structure and workflows go.

After much faffing about with various options including requirejs and browserify I think I have settled on the following setup:

  • Typescript (still not 100% sold on this, but given the direction of Angular 2 I can see the benefits!)
  • External modules using ES6 module sytnax (I didn't like Typescript internal modules and the messy IIFEs it compiled down to)
  • Jspm - to run ES6 modules in the browser for development and bundling for production
  • Gulp for automating build processes


It was a bit of work to get set up and running, but the above combination gives a development workflow that is very future focused. Things I like about it so far are:

  • It is easy to put in place an Angular 2 like component architecture in order to ease the upgrade path
  • All the file/module dependencies are handled by the explicit ES6 module imports, so no need to worry about making sure Angular files are loaded in the correct order
  • Angular controllers can be easily tested as standalone classes instead of having to mock the whole framework
  • Reasonably seamless flow from Typescript coding to debugging ES6 in the browser with jspm and sourcemaps

Things I still need to sort out:

  • Code coverage of the Typescript files
  • Some funny path problems with Karma (I have temporarily fixed by adding a special jspm conf file for Karma with baseURL: "/base")
  • Skip compiling to JS step in develop and use the new jspm Typescript compiler option
There is a very basic example of the initial setup at https://github.com/glendaviesnz/angular1-Typescript-ES6modules-jspm-gulp - this is very much a work in progress experimental playground, and not yet another angular boilerplate/seed project - but may be of interest to others exploring this sort of workflow.

Thursday, July 24, 2014

Getting Angular scope variable into Sigma.js and back out again

I have been playing around with setting up a Sigma.js directive to integrate a network graph into a single page Angular JS app.

The basic directive setup was reasonably straight forward, but I wanted to add an onClick function to the nodes in the graph that would update the Angular controller model - but of course the Sigma node click event is outside the Angular world. Thanks to the joys of javascript closures though I found one reasonably easy way to do it.

The onClick binding is added in the directive link: using a function reference, rather than enclosing the actual function, eg.

s.bind('clickNode', clickNode);


instead of

s.bind('clickNode', function(e){ onClick.detail.here;});


The actual clickNode function is then also included in the directive link: where it has access to the directive's isolated scope. In this case I have an object on the scope called 'tweeter' with a two way data bind.

function clickNode(e) {
    
    scope.$apply(function(){
        scope.tweeter.handle = e.data.node.label;
    });
}


When a node is clicked the 'handle' field of the scope.tweeter object is updated with the label of the node, and this is all wrapped in an Angular scope.$apply function which ensures that the change is noticed by Angular.  The corresponding value in the controller is then updated ...... then just like magic the node label from Sigma.js appears in my Angular view template.

Seems to work a treat - but let me know if there is a better way to be doing this?