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