2/2/17

Using Angular2 Components Inside Angular1 Project

Dreaming About Upgrade

If you working on project that built with angular1 for historical reasons and dreaming about move to angular2 - this article is for you.
Yes, it is possible to move to angular2 make little changes step by step, without breaking the running code of your application. In this article i will take this angular-seed repo and try to make it run with some angular2 component included.

Step 1 - Bootstrap Angular Different Way

Usually the angular is attached to HTML using ng-app directive but there is another way to bootstrap angular to your application - using angular.bootstrap utility:


angular.bootstrap(document.body, ['myApp'], {strictDi: true});

You can view source at this branch

Step 2 - Migrating to Typescript

To move project to typescript you need :
1. rename all the files to end with ts instead of js (meanwhile leave files finishing with 'test' and not rename them to ts)
2. install typescript compiler:

npm i typescript -D
3.create tsconfig.json file at root directory

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "removeComments": false,
    "noImplicitAny": false,
    "suppressImplicitAnyIndexErrors": true
  }
}

4. install type definitions for angular and other dependencies
npm install @types/angular -D
If all the installations succeed - after running
tsc -p .
command all ts files should have their ".js" and "map.js" copies - appear near them.


You can view source at this branch

Step 3 - Move application to load dependencies from node modules

Why? Mostly from reason that angular2 recommend to load dependencies this way instead of using bower. So, since node_modules directory located at project root we need to modify index.html so all the urls will stay correct:
Insert base tag inside head tag of your index.html


<base href="/app/">

Also you will need to change "start" script inside package.json:

"start": "http-server -a localhost -p 8000 -c-1 ./",

Now you can install all the dependencies like angular through NPM:

    "angular": "^1.6.1",
    "angular-route":"^1.6.1",

Don't forget to change the paths inside the index.html:

  <script src="/node_modules/angular/angular.js"></script>
  <script src="/node_modules/angular-route/angular-route.js"></script>

Now make sure that after you run
tsc -p ./
and then:
npm start
command you can see the project running:

You can view the full code on this branch

Step 4 - Install Systemjs Module Loader

Module Loader - a way to make javascript to import or export different javascript types (like classes) from one file to other. There are several javascript module loaders such as Webpack, Browserify, Systemjs - for our tutorial we will use systemjs.

npm install systemjs -D
After installation lets try to include the systems script to "index" page:

  <script src="/node_modules/systemjs/dist/system.src.js"></script>

Important: systemsjs uses its own configuration file - systems.config You may place it in the "app" directory:


/**
 * System configuration for Angular samples
 * Adjust as necessary for your application needs.
 */
(function (global) {
  System.config({
    paths: {
      // paths serve as alias
      'npm:': '/node_modules/'
    },
    // map tells the System loader where to look for things
    map: {
      // our app is within the app folder
      app: '/app',

      // angular bundles
      '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
      '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
      '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
      '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
      '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
      '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
      '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
      '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',      
      '@angular/upgrade/static':'npm:@angular/upgrade/bundles/upgrade-static.umd.js',
      // other libraries
      'rxjs':                      'npm:rxjs',
      'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js'
    },
    // packages tells the System loader how to load when no filename and/or no extension
    packages: {
      app: {
        main: './main.js',
        defaultExtension: 'js'
      },
      rxjs: {
        defaultExtension: 'js'
      }
    }
  });
})(this);

This files points to system module loader where the scripts located. Lets see if it loaded now in the chrome developer tools:
After we can see everything is still working lets add angular2 dependencies to package.json:

    "@angular/common": "~2.4.0",
    "@angular/compiler": "~2.4.0",
    "@angular/core": "~2.4.0",
    "@angular/forms": "~2.4.0",
    "@angular/http": "~2.4.0",
    "@angular/platform-browser": "~2.4.0",
    "@angular/platform-browser-dynamic": "~2.4.0",
    "@angular/router": "~3.4.0",

    "angular-in-memory-web-api": "~0.2.4",
    "systemjs": "^0.20.4",
    "core-js": "^2.4.1",
    "rxjs": "5.0.1",
    "zone.js": "^0.7.4"
You also must point to systems that you main module is inside "app" directory:
index.html

    <script src="systemjs.config.js"></script>
    <script>
      System.import('/app').catch(function(err){ console.error(err); });
    </script>

Important: you must remove main.js reference from index.html
And run again:npm install and after :tsc -p .

If everything is OK you should refresh and see the app running as usual.

Little Experiment

Now lets try to import some file into main.ts file using import command:
main.ts


import './app'
declare var angular: ng.IAngularStatic;//must not override global angular
angular.bootstrap(document.body, ['myApp'], { strictDi: true });

Now you can remove app.js reference from index.html and see that app still running (since the code imported by systemjs!)

You can view full code on this branch

Step 05 - Install Angular Upgrade

Now we about to enter cherry-in-the-cake zone of this article - start using angular2 and angular1 in the same project

 npm i @angular/upgrade -D
This angular2 module makes you to be able to include angular2 modules into your angular1 project.
Lets add angular2 module named AppModule
app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { UpgradeModule } from '@angular/upgrade/static'
@NgModule({
  imports: [
    BrowserModule,
    UpgradeModule,
  ],
})
export class AppModule {
  ngDoBootstrap() {}
}

Now the only thing left is to inject your old myApp module angular2 way:
main.ts

/*angular.bootstrap(document.body, ['myApp'], {strictDi: true});*/
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { UpgradeModule } from '@angular/upgrade/static';
import { AppModule } from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => {
  const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule;
  upgrade.bootstrap(document.body, ['myApp']);
});

Voula! you running angular2 and angular1 together now!
You can view source code on this branch

Thanks for reading

1/25/17

Creating My First Type Definition

Typescript Main Point

One of reasons the typescript was invented is to get javascript errors to be detected in compilation step.
For example:
If you defined function myFunc and it suppose to be called without any params, and your code use its call in multipple places:


function myFunc() {
  alert('my name is Shlomo');
}
//call
myFunc()

And after some time you decided to change function code to use the parameter:

function myFunc(nm) {
  alert('my name is ' + nm);
}
//call
myFunc('Shlomo')

In case you using javascript and you forgot to change call in some place to its new shape - you will got 'nm is undefined' error when code will try to run following call.

//call
myFunc()

in typescript After you changed myFunc definition to use parameter:

myFunc(nm:string) {
  alert('my name is ' + nm);
}
//call
myFunc()//this will not compile

If there are some calls which not using parameter left- Typescript compiler will fail.

What are type definitions?

So if you use typescript and want to use some javascript library or plugin in your code - this plugin and its methods need to be defined in a way that typescript will "recognize" it.

Experiment With Selectize

For example purposes lets take our old angular-seed project which uses typescript. To demonstrate what happen when you try to use some javascript plugin inside typescript project lets install selectize.js jquery plugin

bower install selectize --save-dev

And place the call to the plugin inside View2Ctrl controller:
As you can see - trying to use the plugin as it is made vscode angry
You can see also compilation error:

Create Starter Definition File

As we can see in other definitions - the selective definition must be hosted inside node_modules/@types directory. lets add selectize directory and add index.d.ts file inside it. I will not write here about create real definition - since guys much wiser than me already created it here,
My purpose now is only to create something that make the typescript compiler calm.


/// <reference types="jquery"/>

declare namespace Selectize {
 
}
interface JQuery {
    selectize(options?: any): JQuery;
}


As you can see - i only added selectize method to Jquery interface
That is enough for get the source compiled -

Thanks For reading

1/18/17

Unit Tests In Angular2 - part 3 (Component That Uses Service)

In previous post we've learned how to test the angular2 component.
That was very nice and we have a lot of fun, but everybody knows that in real life many components may use services to bring data from server.
Lets demonstrate it on our user component:
Lets make it to display friends list of this user:



@Component({
  selector: 'my-app',
  template: `<span>name: <strong>Victor Savkin</strong></span>
   <hr/>
   <strong>Friends:</strong>
   <ul>
     <li *ngFor="let friend of friends">
       {{friend}}
     </li>
   </ul>`
})
export class App {
   
    public friends:string[]=[]
    constructor(private usersService:UsersService) {
    }
    ngOnInit(): void {
       this.usersService.getUsers().subscribe(
         users => {
           this.friends=users;
         },
         error=>console.log(error) 
       );
    }
}


And the code of UsersSerivice:

import {Injectable} from '@angular/core';
import {Http} from '@angular/http';
import 'rxjs/add/operator/map';

@Injectable()
export class UsersService {
  constructor(private http:Http) { }
  
  getUsers(){
    return this.http.get('api/users.json')
      .map(response => response.json());
  }
}


So How To Test This Thing?

Lets say we want to test that request populates the "friends" section with friends names.
Since it is bad practice to make real http request for testing purposes - we need to create a mock of UsersService:


import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
  
  class MockUsersService {
    public get() {return  Observable.of(['John', 'Steve']);}
  }

Once we have the mock of UsersService -lets inject it into the module instead of usersService:

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [UserComponent], 
      providers:[// <--HERE IS THE MAGIC!!!
       { provide: UsersService, useClass: MockUsersService}
      ],
      imports: [ HttpModule,BrowserModule ]
    })
    .compileComponents();
  }));

Great, inst it?
Now we can test the component and see if friends are displayed as planned:

  beforeEach(() => {
    fixture = TestBed.createComponent(UserComponent);
    comp = fixture.componentInstance;
    comp.user = new UserModel('Vasia','Kozlov')
    de = fixture.debugElement.query(By.css('span'));
    element = fixture.nativeElement;// to access DOM element
  });

  it('should have list of friends displayed inside UL',  async(() => {
    fixture.detectChanges();
    fixture.whenStable().then(() => { 
      expect(element.querySelector('li:first-child').innerText).toBe('John');
      expect(element.querySelector('li:last-child').innerText).toBe('Steve');
    })
  }));

you can see the full code on this plunker

Getting started with docker

It is very simple to get started usig docker. All you need to do-is download the docker desktop for your system Once you get docker syste...