1

I built an Angular 1 application built using Linenanjs. I'm in the process to migrate it to Angular 2. I've integrated Typescript and removed ng-app. The next step is to integrate the Angular 2 libraries and start the actual migration.

Before I do I would like to see if anyone has done so and has recommendations / recipes on best practices to integrate the Angular 2 modules into an Angular 1 application.

1 Answers1

0

Introduction

I started this journey with the intent to integrate the new NG2 with the existing NG1 build process; this seemed reasonable since my goal was to build an environment for the development of hybrid NG1 / NG2 applications.

There are fundamental differences between how NG1 and NG2 are built:

  • TypeScript: NG2 added TypeScript to the existing JavaScript and DART language options. TypeScript is a huge improvement over JavaScript and I have no intention of using DART.
  • Module Management: NG2 adopts ES5 module management and abandons NG1 own module management. Application and vendor files are now made available as modules which are loaded by the module manager (I'm using SystemJS but will soon upgrade to Webpack) as needed by the application.
  • Components: NG2 component architecture strongly suggests that all artifacts (css, html, less, png, etc) ought to be placed in the same module folder, with the module manager, with a little help from the developer, taking on the role of loading these files as necessary.

Approach

Well into the process to build the NG1 / NG2 integrated build environment, I came to the conclusion that the correct course of action would be to build the NG2 environment to be completely separate from the NG1 environment, but to co-exist. Once I reached this conclusion, it became obvious that this was the correct approach since once we migrate all of our NG1 software to NG2 we want to also seamlessly remove all of the NG1 build artifacts and software, which would be hard to do should the two be tightly intertwined.

With this in mind, the following changes were made:

  • config/files - Created definitions calling out the NG2 files; the idea was that all NG2 tasks would be made to operate on these file definitions while avoiding the existing file definitions, hence leaving the option to remove not used NG1 file definitions.
  • config/application - There are two fundamental set of changes: ** tasks - Again, I strived to create tasks doing specific NG2 work. ** workflow - Here is the place where the NG1 and NG2 tasks were intermingled to coexist.

files

See below for my config/files.js code and some ensuing comments:

module.exports = function(lineman) {
    //Override file patterns here
    return {
        dlabs: {
            targetIq: "../../../target/iq"
        },
        js: {
            vendor: [
                "vendor/js/hmac-md5.js",
                "vendor/js/hmac-sha1.js",
                "vendor/js/hmac-core-min.js",
                "vendor/js/hmac-enc-utf16-min.js",
                "vendor/js/hmac-enc-base64-min.js",
                "vendor/js/jquery.js",
                "vendor/js/bootstrap.js",
                "vendor/js/angular.js",
                "vendor/js/**/*.js"
            ],
            app: [
                "app/js/app.js",
                "app/js/**/*.js"
            ],
            concatenatedVendor: "generated/js/appVendor.js",
            "minifiedVendor": "dist/js/appVendor.js",
        },

        less: {
            compile: {
                options: {
                    paths: [
                        "vendor/css/angular-strap-docs.min.css",
                        "vendor/css/angular-strap-libs.min.css",
                        "vendor/css/bootstrap.css",
                        "vendor/css/**/*.css",
                        "vendor/components/**/*.css",
                        "app/ng2/styles.less",
                        "app/css/**/*.less"
                    ]
                }
            }
        },

        ng2: {
            libs: [
                "systemjs.config.js",
                "node_modules/@angular/**",
                "node_modules/systemjs/**",
                "node_modules/core-js/**",
                "node_modules/reflect-metadata/**",
                "node_modules/rxjs/**",
                "node_modules/zone.js/**",
                "node_modules/angular2-in-memory-web-api/**"
            ],
            css: [
                // used in conjunction with the CWD option
                "**/*.css"
            ],
            html: [
                // used in conjunction with the CWD option
                "**/*.html"
            ],
            "systemjs": {
                generated: "generated/",
                dist: "dist/"
            },
            ts: [
                // used in conjunction with the CWD option
                "**/*.ts"
            ],
            generated: "generated/ng2",
            dist: "dist/ng2"
        },

        webfonts: {
            root: "fonts"
        }
    };
};

Notes:

  • files.ng2 - is an obect containing all the NG2 related file definitions
  • files.ng2.libs - is an array including the source location of the support files (non-application) that need to be copied to the server target folder; as indicated, NG2 requires vendor modules to be available for loading by the module manager.
  • files.ng2.css: defines the CSS fils to be watched for change; note that I chose not to handle less files; should you be interested in doing so, use the pattern used to handle ts file.
  • files.ng2.html: defines the html fils to be watched for change.
  • files.ng2.generated: the target folder where to place files during development; the copy:dl-deploy-dev task (see the config/application.js file) is used to copy these files to my server (wildfly) target folder.
  • files.ng2.dist: the target folder where to place files when running a buid.

application

See below for my config/application.js code and some ensuing comments:

/* Exports a function which returns an object that overrides the default &
 *   plugin grunt configuration object.
 *
 * You can familiarize yourself with Lineman"s defaults by checking out:
 *
 *   - https://github.com/linemanjs/lineman/blob/master/config/application.coffee
 *   - https://github.com/linemanjs/lineman/blob/master/config/plugins
 *
 * You can also ask Lineman"s about config from the command line:
 *
 *   $ lineman config #=> to print the entire config
 *   $ lineman config concat.js #=> to see the JS config for the concat task.
 */
module.exports = function(lineman) {
    // DO NOT REMOVE
    var app = lineman.config.application;


    //Override application configuration here. Common examples follow in the comments.
    return {
        // grunt-angular-templates assumes your module is named "app", but
        // you can override it like so:
        //
        // ngtemplates: {
        //   options: {
        //     module: "myModuleName"
        //   }
        // }

        server: {
            pushState: true
            // API Proxying
            //
            // During development, you"ll likely want to make XHR (AJAX) requests to an API on the same
            // port as your lineman development server. By enabling the API proxy and setting the port, all
            // requests for paths that don"t match a static asset in ./generated will be forwarded to
            // whatever service might be running on the specified port.
            //
            // apiProxy: {
            //   enabled: true,
            //   host: "localhost",
            //   port: 3000
            // }
        },

        loadNpmTasks: lineman.config.application.loadNpmTasks.concat("grunt-contrib-copy", "grunt-exec", "grunt-contrib-clean", "grunt-ts"),

        /* *************************************************************************************************************
         * Task Definition
         ************************************************************************************************************ */

        clean: {
            "vendor": ["vendor"],
            "generated": {
                src: "<%= files.ng2.generated %>" + "/*",
                options: {
                    force: true
                }
            },
            "targetIq": {
                src: "<%= files.dlabs.targetIq %>" + "/*",
                options: {
                    force: true
                }
            }
        },

        "concat_sourcemap": {
            "js": {
                "src": [
                    "<%= files.js.app %>",
                    "<%= files.coffee.generated %>",
                    "<%= files.template.generated %>"
                ],
                "dest": "<%= files.js.concatenated %>"
            },
            "vendor": {
                "src": [
                    "<%= files.js.vendor %>"
                ],
                "dest": "<%= files.js.concatenatedVendor %>"
            }
        },

        copy: {
            "dl-deploy-dev": {
                files: [
                    {
                        expand: true,
                        cwd: "generated/",
                        src: ["**"],
                        dest: "<%= files.dlabs.targetIq %>"
                    },
                    {
                        expand: true,
                        flatten: true,
                        dest: "../../../target/iq/css",
                        src: [
                            "vendor/static/fonts/ui-grid.ttf",
                            "vendor/static/fonts/ui-grid.woff"
                        ]
                    }
                ]
            },
            // Copies the ng2 libraries to the target folder, instead of generated.
            // Copying to generated and then to target is too expensive while watching.
            "ng2-libs-to-target": {
                files: [
                    {expand: true, src: "<%= files.ng2.libs %>", dest: "../../../target/iq/" }
                ]
            },
            "ng2-css-files-to-generated": {
                // See Copy files to different directory (https://github.com/gruntjs/grunt-contrib-copy/issues/58)
                // I used to ensure that the compiled ts files (js / map) and their companions (ts / html / css) landed on the same folder
                expand: true,
                cwd: "app/ng2",
                src: "<%= files.ng2.css %>",
                dest: "<%= files.ng2.generated %>"
             },
            "ng2-files-to-generated": {
                // See Copy files to different directory (https://github.com/gruntjs/grunt-contrib-copy/issues/58)
                // I used to ensure that the compiled ts files (js / map) and their companions (ts / html / css) landed on the same folder
                expand: true,
                cwd: "app/ng2",
                src: ["<%= files.ng2.css %>", "<%= files.ng2.html %>"],
                dest: "<%= files.ng2.generated %>"
            },
            "ng2-html-files-to-generated": {
                // See Copy files to different directory (https://github.com/gruntjs/grunt-contrib-copy/issues/58)
                // I used to ensure that the compiled ts files (js / map) and their companions (ts / html / css) landed on the same folder
                expand: true,
                cwd: "app/ng2",
                src: "<%= files.ng2.html %>",
                dest: "<%= files.ng2.generated %>"
            },
            "ng2-ts-files-to-generated": {
                // See Copy files to different directory (https://github.com/gruntjs/grunt-contrib-copy/issues/58)
                // I used to ensure that the compiled ts files (js / map) and their companions (ts / html / css) landed on the same folder
                expand: true,
                cwd: "app/ng2",
                src: "<%= files.ng2.ts %>",
                dest: "<%= files.ng2.generated %>"
            },
            "ng2-files-to-dist": {
                // See Copy files to different directory (https://github.com/gruntjs/grunt-contrib-copy/issues/58)
                // I used to ensure that the compiled ts files (js / map) and their companions (ts / html / css) landed on the same folder
                expand: true,
                cwd: "app/ng2",
                src: ["<%= files.ng2.css %>", "<%= files.ng2.html %>"],
                dest: "<%= files.ng2.dist %>"
            },
            // Copies the angular 2 libraries to the dist folder.
            // Executed by the "lineman build" command
            "ng2-libs-to-dist": {
                files: [
                    {expand: true, src: "<%= files.ng2.libs %>", dest: "dist/"}
                ]
            },
            "systemjs-to-dist": {
                src: "systemjs.config.js",
                dest: "<%= files.ng2.systemjs.dist %>"
            },
            "systemjs-to-generated": {
                src: "systemjs.config.js",
                dest: "<%= files.ng2.systemjs.generated %>"
            }
        },

        // Added this to fix the following error:
        // Warning: Path must be a string. Received null Use --force to continue.
        // found out about this error here: https://github.com/jshint/jshint/issues/2922
        jshint: {
            options: {
                reporterOutput: ""
            }
        },

        // Task to compile typescript files
        // Look here for config options: https://www.npmjs.com/package/grunt-ts
        ts: {
            development: {
                "src": "app/ng2/**/*.ts",
                "outDir": "<%= files.ng2.generated %>",
                "options": {
                    "emitDecoratorMetadata": true,
                    "module": "system",
                    "moduleResolution": "node",
                    "noImplicitAny": false,
                    "removeComments": false,
                    "sourceMap": true,
                    // using es5 is problematic with NG2-beta
                    // http://stackoverflow.com/questions/33332394/angular-2-typescript-cant-find-names
                    "target": "es6"
                }
            },
            production: {
                "src": "app/ng2/**/*.ts",
                "outDir": "<%= files.ng2.dist %>",
                "options": {
                    "emitDecoratorMetadata": true,
                    "module": "system",
                    "moduleResolution": "node",
                    "noImplicitAny": false,
                    "removeComments": false,
                    "sourceMap": false,
                    // using es5 is problematic with NG2-beta
                    // http://stackoverflow.com/questions/33332394/angular-2-typescript-cant-find-names
                    "target": "es6"
                }
            }
        },

        uglify: {
            vendor: {
                files: {
                    "<%= files.js.minifiedVendor %>": "<%= files.js.concatenatedVendor %>"
                }
            }
        },

        /*
         Custom watch to copy changed files to the target folder based on suggestion by justin@testdouble.com
         */
        watch: {
            "systemjs-config-js": {
                "files": "systemjs.config.js",
                "tasks": ["copy:systemjs-to-generated"]
            },
            // renamed & deleted files remain in place, restarting lineman run will fix it
            "ng2-css": {
                "files": "<%= files.ng2.css %>",
                "tasks": ["copy:ng2-css-files-to-generated"]
            },
            // renamed & deleted files remain in place, restarting lineman run will fix it
            "ng2-html": {
                "files": "<%= files.ng2.html %>",
                "tasks": ["copy:ng2-html-files-to-generated"]
            },
            // renamed & deleted files remain in place, restarting lineman run will fix it
            "ng2-ts": {
                "files": "<%= files.ng2.ts %>",
                "tasks": ["ts:development"]
            },
            target: {
                "files": [
                    "systemjs.config.js",
                    "app/**/*",
                    "spec/**/*",
                    "spec-e2e/**/*"],
                "tasks": "copy:dl-deploy-dev"
            }
        },

        webfonts: {
            files: {
                "vendor/components/FontAwesome/fonts/": "vendor/static/fonts/FontAwesome.*",
                "vendor/components/fontawesome-webfont/fonts/": "vendor/static/fonts/fontawesome-webfont.*",
                "vendor/components/glypicons-halflings-regular/fonts/": "vendor/static/fonts/glypicons-halflings-regular.*",
                "vendor/components/ui-grid/fonts/": "vendor/static/fonts/ui-grid.*"
            }
        },

        /* *************************************************************************************************************
         * Workflow Definition
         ************************************************************************************************************ */

        /*
         I struggled with getting this right. I got it to work after carefully reading:
         - Creating Lineman Plugins http://linemanjs.com/#creating-lineman-plugins
         - https://github.com/linemanjs/lineman-dogescript/blob/master/config/plugins/dogescript.coffee#L13-L14
         */
        prependTasks: {
            dev: [
                "clean:targetIq",
                "clean:generated",
                "copy:ng2-libs-to-target",
                "ts:development",
                "copy:ng2-files-to-generated",
                "copy:dl-deploy-dev"].concat(app.prependTasks.dev),
            common: ["concat_sourcemap:vendor", "dl-install-libs"].concat(app.prependTasks.common),
            dist: [
                "copy:ng2-libs-to-dist",
                "ts:production"].concat(app.prependTasks.dist)
        },
        appendTasks: {
            dist: [
                "uglify:vendor",
                "copy:systemjs-to-dist",
                "copy:ng2-files-to-dist"].concat(app.appendTasks.dist)
        },
        removeTasks: {
            dev: ["server"]
        }
    };
};

Notes:

  • copy:dl-deploy-dev: used to copy these files to my server (wildfly) target folder.
  • copy:ng2-libs-to-target: used to copy 3rd party library files to my server (wildfly) target folder. Note that I chose not to copy them to the generated folder to avoid the expense of copying a huge number of files to the target folder every time a single simple application file changes.
  • copy:ng2-css-files-to-generated: copies changed CSS files to the generated folder.
  • copy:ng2-html-files-to-generated: copies changed html files to the generated folder.
  • copy:ng2-files-to-generated: used to ensure that the transpiled ts files (js / map) and their companions (html / css) landed on the same folder.
  • copy:ng2-ts-files-to-generated: : copies changed html files to the generated folder.
  • copy:systemjs-to-generated: used to copy the system.config.js file to the generated folder.
  • copy:ng2-libs-to-dist: used to copy 3rd party library files to dist folder.
  • copy:systemjs-to-dist: used to copy the system.config.js file to the dist folder.
  • ts:development: used to transpile changed ts files into the generated folder.
  • ts:dist: used to transpile changed ts files into the dist folder.
  • watch:systemjs-config-js: watches for changes in the system.config.js and executes copy:systemjs-to-generated when changes are detected.
  • watch:ng2-css: watches for css files changes and executes copy:ng2-css-files-to-generated when changes are detected.
  • watch:ng2-html: watches for html files changes and executes copy:ng2-css-files-to-generated when changes are detected.
  • watch:ng2-ts: watches for ts files changes and executes ts:development when changes are detected.
  • watch:target: watches for app files changes and executes copy:dl-deploy-dev when changes are detected.
  • The workflow changes are self explanatory, particularly in lieu of the task explanations above, except for the dl-install-libs task used to replace the fetch task provided our Test Double friends; its explanation is outside the scope of this document but feel free to ask in case you are interested.