Code written for Angular 1.x can be converted to Angular 5, through a line by line migration process (aka a hard slog)
ng generate ..
This will create the associated test tooMigration the existing ONOS Gui can be done through a process of taking an existing JavaScript file and looking at its angular.module statement. Note:
So taking the onos.js file:
angular.module('onosApp', moduleDependencies) .controller('OnosCtrl', [ '$log', '$scope', '$route', '$routeParams', '$location', 'LionService', 'KeyService', 'ThemeService', 'GlyphService', 'VeilService', 'PanelService', 'FlashService', 'QuickHelpService', 'EeService', 'WebSocketService', 'SpriteService', function (_$log_, $scope, $route, $routeParams, $location, lion, ks, ts, gs, vs, ps, flash, qhs, ee, wss, ss) { .... .directive('detectBrowser', ['$log', 'FnService', function ($log, fs) { return function (scope) {
There is clearly a module (onosApp) here containing a controller (OnosCtrl) and a directive (detectBrowser)
onosDetectBrowser
If documentation has been created (using npm run compodoc
) this module can be inspected at OnosModule
The Angular CLI tool has many handy modes to help you along the way. From the onos/web/gui folder you should be able to run
ng lint
which will scan all the .ts files in the src folder and check for errors.
The ONOS GUI can be run using the
ng serve
command and can be left running as code changes are made. Once this is running a browser attached to http://localhost:4200 will display the application and any changes made to the code will be visible immediately as the page will refresh
Watch out for any errors thrown in 'ng serve' - they usually point to something fairly bad in your code.
Another place to look is in the browsers own console Ctrl-Shift-I
usually brings this up.
This is where things get really interesting. A good place to start is on a Service which does not have dependencies on other services.
Two services have been setup in the onos.module.ts that are new to this migration
There is a fair bit of refactoring has to take place. An important thing to understand is that DOM manipulations from inside JavaScript code is not the Angular 6 way of doing things - there was a lot of this in the old ONOS GUI, using d3.append(..) and so on. The Angular 6 way of doing things is to define DOM objects (elements) in the html template of a component, and use the Component Java Script code as a base for logic that can influence the display of these objects in the template. What this means is that what were previously defined as services (e.g. VeilService or LoadingService) should now become Components in Angular 6 (e.g. VeilComponent or LoadingComponent).
Similarly a directive might be trying to do DOM manipulation and have a CSS - this should be made in to a component instead (see IconComponent)
The general rule to follow is "if a service in the old GUI has an associated CSS file or two then is should be a component in the new GUI".
The implication of this is that all of the d3 DOM Manipulations that happened in the old service should now be represented in the template of this new component. If it's not clear to you what the template should look like, then run the old GUI and inspect the element and its children to see the structure.
Components (unlike services) have limited scope (that's the magic of them really - no more DOM is loaded at any time than is necessary). This means that they are self contained modules, and any CSS associated with them is private to that component and not accessible globally.
Components are graphical elements and should not be injected in to Services. Services should be injected in to components, but not the other way round. Components can be added in to other components by putting the selector of the child component e.g. in to the html template of the parent.
Take for instance the WebSocketService - this should remain a service, but I want to display the LoadingComponent while it's waiting and the VeilComponent if it disconnects. I should not go injecting these in to WebSocketService - instead there is a setLoadingDelegate() and a setVeilDelegate() function on WSS that I can pass in a reference to these two components. When they need to be displayed a method call is made on the delegate and the component gets enabled and displays. Also note inside WSS any time we call a method on this LoadingComponent delegate we check that it the delegate had actually been set.
The WSS was passed in to the LoadingComponent and VeilComponent to set the delegate on it.
Any component that needs to use WSS for data should inject the WSS service AND needs to include the components in its template by adding and .
Or does it just support a few functions. See the TableBase class. This now replaces the old TableBuilderService - that was just on function that manipulated the scope of a view component. Instead view components now extend this class.
Also sometimes directive are always used together e.g. icon directive and tooltip directive and they can be merged in to one
Taking for a really simple example the fw/remote/WSockService, this was originally defined in the app/fw/remote/wsock.js file and is now redefined in onos/web/gui2/src/main/webapp/app/fw/remote/wsock.service.ts.
First of all this should remain a Service, since it does not do any DOM manipulation and does not have an associated CSS.
This has one method that's called to establish the WebSocketService
1 (function () { 2 'use strict'; 3 4 angular.module('onosRemote') 5 .factory('WSock', ['$log', function ($log) { 6 7 function newWebSocket(url) { 8 var ws = null; 9 try { 10 ws = new WebSocket(url); 11 } catch (e) { 12 $log.error('Unable to create web socket:', e); 13 } 14 return ws; 15 } 16 17 return { 18 newWebSocket: newWebSocket, 19 }; 20 }]); 21 }());
Converting this to TypeScript requires a total change in mindset away from functions and towards more object oriented programming. This file is located in onos/web/gui2/src/main/webapp/app/fw/remote/wsock.service.ts
101 import { Injectable } from '@angular/core'; 102 import { LogService } from '../../log.service'; 103 104 @Injectable() 105 export class WSock { 106 107 constructor( 108 private log: LogService, 109 ) { 110 this.log.debug('WSockService constructed'); 111 } 112 113 newWebSocket(url: string): WebSocket { 114 let ws = null; 115 try { 116 ws = new WebSocket(url); 117 } catch (e) { 118 this.log.error('Unable to create web socket:', e); 119 } 120 return ws; 121 } 122 }
There are several things worth noting here:
##Cheatsheet
The following services are partially migrated:
This is now a Component, whose class extends the TableBase - this is where it gets most of its functionality. As before all data comes from the WebSocket. There is still a bit of work to go on this - scrolling of long lists, device details panel etc
The major change in the template (html) is that there used to be 2 tables and these are now brought together in to a header and body. This simplifies trying to keep the widths of both in sync.
For CSS the old device view CSS is included and a link is made across to the common table CSS
The Details Panel is made visible when a row is selected - it is a component, and is embedded in to the repeated row. There are base classes for common details panel behaviour
This is a Component too, again extending TableBase. Apps view has much more functionality though because it has controls for upload and download of applications.