AngularJS:

the next steps

Wilson Mendes

@willmendesneto

So, let's start?

Once upon a time...

Forget!

  
  function async() {
      setTimeout(function(){
          console.log(' async!');
      });
  }
  async();
  console.log('Think');

Async is more than this

Async is...

We have problems!

If we have to start, let's start with our code

    
    angular.module('betbuilder').directive 'sidePanel', (
      ... # dependency list
    ) ->
    
      # Scope
      scope:
        betBuilder: '='
        promotionsManager: '='
        bonusBetsUrl: '@'
    
      templateUrl: '/betbuilder/templates/side-panel.html'
      replace: true
      restrict: 'E'
    
      # Controller ?!?!
      controller: ($rootScope, $scope, $timeout) ->
        # Frontend logic

      # Link ?!?!
      link: ($scope, element, attrs) ->
        # events and a lot of stuff

Why ?

How can I test?

What I need to test

(behaviours, events, logic)?

Maintainability?

 

 

1 - Thin directives

Separate concepts

+ Maintain

+ Test

+ Debug


    angular.module('accountOperation')
    .controller 'accountOperation.pendingBetsController', (
      ...
    ) ->
      ...
      @statementLoaded = false
    
      @kindIsTriforecast = (betType) -> 
        ['tricast', 'forecast'].indexOf(betType.toLowerCase()) isnt -1
    
      @hasTransactions = -> @transactions?.length > 0



    angular.module('accountOperation')
    .directive 'pendingBets', ->
    
      restrict: 'E'
      replace: true
      templateUrl: '/accountOperation/templates/pending-bets.html'
      controller: 'accountOperation.pendingBetsController'
      controllerAs: 'vm'


    describe 'PendingBetsController', ->
    
      beforeEach ->
      
        ctrl = $controller 'accountOperation.pendingBetsController',
          $scope: scope
          $log: log
          ...
    
      describe 'Given the pending bets are displayed', ->
        
        beforeEach ->
          ... # setup for this case

        describe 'When transactions is displayed', ->

          beforeEach ->
            ... # setup for this case

          it 'should render transactions list', ->
            ... # assertion

Providers

===

Reusability

Example: Affiliate links



    angular.module('sunbets', [
      ...
    ])
    .config (affiliateLinksProvider) ->
      ...
      affiliateLinksProvider.register
        href: '/casino'
        alt: 'SunCasino'
        image: '/images/suncasino-logo.svg'
        current: isCasino




    angular.module('menuNavigation')
    
    .provider 'affiliateLinks', ->
      links = []
    
      register: (attributes) ->
        links.push attributes
    
      $get: ->
        hasAny: links.length > 0
        all: links


    <div class="affiliate-links">
      <div class="nav-items-group">
        <a
          class="nav-item"
          target="_self"
          ng-repeat="(indexLink, link) in links track by indexLink"
          ng-class="{ 'active': link.current }"
          ng-click="setActive(link)"
          ng-href="{{ ::link.href }}">
          <img
            ng-src="{{ ::link.image }}"
            alt="{{ ::link.alt }}">
        </a>
      </div>
    </div>

NOT BAD

2 - Component

Scope is always isolate: $ctrl

Control just your scope

Well defined lifecicle


    // Input
    bindings: {
      oneWay: '<', // One-way-time-binding
      twoWay: '@' // Two way data binding
    }

    // Output
    // EX: Callback for a external component
    bindings: {
      onDelete: '&', 
      onUpdate: '&'
    }

Inputs & outputs

    
    class MainController {
    
      $onInit() {}
      $onChanges() {}
      $onDestroy() {}

    }

    ...
    angular.module('heroApp')
    .component('mainApp', {
      templateUrl: 'main-app.html',
      controller: MainController,
      bindings: {
        hero: '<',
        onUpdate: '&'
      }
    });

Component lifecicle


    angular.module('myMod', ['ngRoute'])
    .component('home', {
      template: '<h1>Home</h1><p>Hello, {{ $ctrl.user.name }} !</p>'
    });
    
    ...
    angular.module('myMod')
    .config($routeProvider => {
      $routeProvider.when '/', template: '<home></home>'
    });

Route definitions

Why ?

Stateless components (tables, render data, etc)

Upgrade to Angular2

#codetime



    var Counter = ng
    .Component({
      selector: 'counter',
      template: [
        '<div class="todo">',
          '<input type="text" [(ng-model)]="count">',
          '<button type="button" (click)="decrement();">-</button>',
          '<button type="button" (click)="increment();">+</button>',
        '</div>'
      ].join('')
    })
    .Class({
      constructor: function () {
        this.count = 0;
      },
      increment: function () {
        this.count++;
      },
      decrement: function () {
        this.count--;
      }
    });

Component  => NG 2

NGUpgrade

Upgrade module

// messages/index.ts

// Loading modules
import * as angular from 'angular';
import {NgModule} from '@angular/core';
import {UpgradeModule, downgradeComponent} from '@angular/upgrade/static';

// Importing project content
import {Repository} from './repository';
...

// Creating module
export const MessagesModule = angular.module('MessagesModule', ['ngRoute']);
MessagesModule.service('repository', Repository);
MessagesModule.config(($routeProvider) => {
  $routeProvider
    .when('/messages/:folder/:id', {template : '<message></message>'});
});
...
@NgModule({
  // components migrated to Angular 2 should be registered here
  declarations: [MessageTextCmp],
  entryComponents: [MessageTextCmp],
})
export class MessagesNgModule {}

// components migrated to angular 2 should be downgraded here
MessagesModule.directive('messageText', <any>downgradeComponent({
  component: MessageTextCmp,
  inputs: ['text']
}));


// external-file.ts
...
import {MessagesNgModule} from MessagesNgModule;
...

Routers

Divide

to

Conquer

// settings/index.ts

// This module was fully migrated to Angular 2
import * as angular from 'angular';

import {NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
import {SettingsCmp} from './settings_cmp';

// Since the whole module has been migrated to Angular 2, 
// there is nothing you need to do here anymore.
export const SettingsModule = angular.module('SettingsModule', []);

@NgModule({
  imports: [
    ...
    // migrated routes
    RouterModule.forChild([
      { path: 'settings', children: [
        { path: '', component: SettingsCmp },
        { path: 'pagesize', component: PageSizeCmp }
      ] },
    ])
  ],
  declarations: [SettingsCmp]
});

export class SettingsNgModule {}
// ng2_app.ts

@NgModule({
  imports: [
    BrowserModule,
    UpgradeModule,

    // importing modules
    SettingsNgModule,

    // Is not required to provide any routes.
    // The router will collect all routes from all the registered modules.
    RouterModule.forRoot([], {useHash: true, initialNavigation: false})
  ],
  providers: [
    // Providing a custom url handling strategy to tell the Angular 2 router
    // which routes it is responsible for.
    { provide: UrlHandlingStrategy, useClass: Ng1Ng2UrlHandlingStrategy }
  ],

  bootstrap: [RootCmp],
  declarations: [RootCmp]
});

export class Ng2AppModule {
  constructor(public upgrade: UpgradeModule){}
}
class Ng1Ng2UrlHandlingStrategy implements UrlHandlingStrategy {
  shouldProcessUrl(url) { return url.toString().startsWith("/settings"); }
  extract(url) { return url; }
  merge(url, whole) { return url; }
}

LESSONS LEARNED

Know about it 

!==

use all



    angular.module('myExample').provider 'myExampleService', ->
      apiHost = null
    
      setApiHostUrl: (value) ->
        apiHost = "#{value}/v1/"
    
      $get: ($http, user) ->
        request = (url) ->
          opts =
            url: "#{apiHost}#{url}"
            headers:
              authenticationAuthToken: user.getApiAuth()
    
          $http(opts).then (response) -> response.data
    
        
        ...
        getToken: -> request '/token'
        ...

A simple example

What happens right now if we have to update all our

API endpoints in frontend?

Think about it ...

The

Strangler

Pattern

The micro* way

Microservices, microframeworks, micro-whatever...

About how easy is to upgrade your app

Microservice +

- Resource

- Monilithic apps

++ Decoupled Apps

 

Modularizing everything

Next step: Agnostic Apps *

 

Angular? React?

We don't care!

* = Or we can reuse everything that is possible

Be lazy, guy

... And don't forget this, ok? Or ...

So easy, not?

#thanks

Wilson Mendes

@willmendesneto

AngularJS: the next steps

By willmendesneto

AngularJS: the next steps

When you started your application with AngularJS 1 and knows about the Angular 2 your reaction was panic? So this talk is for you! What’s the approach that I can use? In this talk I will share my project experience and decisions about the migration: What is being good, patterns that can be helpful, solved problems, tests, maintainability and other aspects to create a agnostic frontend application. Know more about the news that comes with Angular 2, concepts, facilities, new challenges and the reason for AngularJS be an excellent framework for all platforms

  • 3,658