how angular2 can improve your angularjs apps today!

90
HOW ANGULAR2 CAN IMPROVE YOUR ANGULARJS APPS TODAY! Nir Kaufman

Upload: nir-kaufman

Post on 15-Apr-2017

1.345 views

Category:

Technology


1 download

TRANSCRIPT

HOW ANGULAR2 CAN IMPROVE YOUR ANGULARJS APPS TODAY!

Nir Kaufman

NIR KUFMN

Nir Kaufman

- Doing Angular for years - Wrote a book about Angular2 - Play the electric Bass

Head of Angular Development @ 500Tech

*This picture have been retouched the actual speaker may look different

ARE YOU READY FOR MIGRATION?

UNAGENDA

I AM NOT GOING TO TALK ABOUT- TypeScript

- Angular2 upgrade module

- Ng-forward library

- Data flow (Redux, RxJs)

- Angular 1.5 new Component API

BE REALISTIC.

GET READY FOR THE MIGRATION

BUT…

DON’T FORCE ANGULAR1 TO BEHAVE LIKE ANGULAR2

IT WON’T WORK.

WHAT SHOULD YOU DO?

APPLY SOME ANGULAR2 IDEAS

IT WILL IMPROVE YOUR APP DESIGN

AND GET YOU READY FOR THE FUTURE

WINWIN SITUATION

I WILL TALK ABOUT- Embracing Modules

- Using Classes

- Decoupling from Framework API

- Components as UI building blocks

WAIT A SECOND…

THESE ARE NOT ANGULAR2 IDEAS!

THAT’S TRUE.

THESE ARE GOOD PRACTICES

THAT YOU CAN COMPLETE IN ONE SPRINT

https://github.com/nirkaufman/ng1-coffee-shop

GRAB THE CODE

http://tinyurl.com/hdmqrem

REVIEW THE APP

EVERYTHING IS A MODULE

$ git checkout 01_modules

USE A MODULE LOADERIntegrate a module bundler and an ES6 compiler to use javaScript modules.

http://webpack.github.io/

MODULE LOADER

module.exports = { entry: 'index.js', // configure loaders module: { loaders: [ { test: /\.js$/, exclude: /node_modules/, loader: "babel", query: {presets: ['es2015', 'stage-1']} }, // load css as inline styles {test: /\.css$/, loader: "style!css"} ] } };

new webpack.config.js

<script src="bundle.js"></script>

old index.html

<!-- load core scripts --> <script src="../node_modules/angular/angular.js"></script> <script src="../node_modules/angular-ui-router/release/angular-ui-router.js"></script> <script src="index.js"></script> <!-- load services --> <script src="services/logger.factory.js"></script> <script src="services/storage.service.js"></script> <script src="services/orders.service.js"></script> <!-- load filters --> <script src="filters/paid-orders.filter.js"></script> <!-- load directives --> <script src="directives/simple-table/simple-table.js"></script> <!-- load controllers --> <script src="states/home.controller.js"></script> <script src="states/log.controller.js"></script>

<body><script src=“bundle.js"></script></body>

new index.html

import 'angular'; import 'angular-ui-router'; import './assets/index.css'; import services from './services/services.module'; import filters from './filters/filters.module' import directives from './directives/directives.module'; import states from './states/state.module';

new index.js

BUNDLE != CONTACT

old log.controller.js

(function (angular) { angular.module('app') .controller('LogController', function ($scope, $window, Orders, Storage) { $scope.orders = Orders.getOrders(); $scope.clearHistory = function () { if ($scope.orders.length === 0) { return; } var result = $window.confirm("Clear History?"); if (result) { Storage.clear(); $scope.orders = []; } } }) }(window.angular));

new log.controller.js

export function LogController ($scope, $window, Orders, Storage) { $scope.orders = Orders.getOrders(); $scope.clearHistory = function () { if ($scope.orders.length === 0) { return; } var result = $window.confirm("Clear History?"); if (result) { Storage.clear(); $scope.orders = []; } } }

MODULAR APPROACH

old storage.service.js

(function (angular) { angular.module('app') .service('Storage', function () { var ORDERS_KEY = "ORDERS"; this.store = localStorage; this.getOrders = function () { return JSON.parse(this.store.getItem(ORDERS_KEY)) || []; }; this.saveOrders = function (orders) { this.store.setItem(ORDERS_KEY, JSON.stringify(orders)) }; this.clear = function () { this.store.clear(); } })}(window.angular));

new storage.service.js

export function Storage () { var ORDERS_KEY = "ORDERS"; this.store = localStorage; this.getOrders = function () { return JSON.parse(this.store.getItem(ORDERS_KEY)) || []; }; this.saveOrders = function (orders) { this.store.setItem(ORDERS_KEY, JSON.stringify(orders)) }; }

import {Storage} from './storage.service'; export default angular.module('services', []) .service('Storage', Storage)

new service.module.js

old index.js

(function (angular) { angular.module('app', ['ui.router']) .config(function ($stateProvider, $urlRouterProvider) { $urlRouterProvider.otherwise("/"); $stateProvider .state('home', { url: '/', templateUrl: 'templates/home.html', controller: 'HomeController' }) .state('log', { url: '/log', templateUrl: 'templates/log.html', controller: 'LogController' }) });}(window.angular)

new index.js

import 'angular'; import 'angular-ui-router'; import ‘./assets/index.css';import services from './services/services.module'; import filters from './filters/filters.module' import directives from './directives/directives.module'; import states from './states/state.module'; import {routes} from './config/routes'; angular.module('app', [ 'ui.router', services.name, directives.name, filters.name, states.name]).config(routes); // bootstrap angular angular.element(document).ready(function () { angular.bootstrap(document, ['app']); });

new routes.js

export function routes($stateProvider, $urlRouterProvider) { $urlRouterProvider.otherwise("/"); // configure application routes $stateProvider .state('home', { url: '/', templateUrl: 'templates/home.html', controller: 'HomeController' }) .state('log', { url: '/log', templateUrl: 'templates/log.html', controller: 'LogController' }) }

EVERYTHING IS A CLASS

$ git checkout 02_classes

MOVE FORWARD TO ES6You can adopt the new syntax slowly. Start by using classes instead of functions.

http://babeljs.io/

PLAIN SERVICES

old storage.service.js

export function Storage () { var ORDERS_KEY = "ORDERS"; this.store = localStorage; this.getOrders = function () { return JSON.parse(this.store.getItem(ORDERS_KEY)) || []; }; this.saveOrders = function (orders) { this.store.setItem(ORDERS_KEY, JSON.stringify(orders)) }; }

new storage.service.js

export class Storage { constructor() { this.ORDERS_KEY = "ORDERS"; this.store = localStorage; } getOrders() { return JSON.parse(this.store.getItem(this.ORDERS_KEY)) || []; }; saveOrders(orders) { this.store.setItem(this.ORDERS_KEY, JSON.stringify(orders)) }; }

ANGULAR FILTERS

old paid-orders.filter.js

export function paidOrders() { return function (orders) { return orders.filter(order => order.paid === false) }}

import {paidOrders} from './paid-orders.filter'; export default angular.module('filters', []) .filter('paidOrders',paidOrders);

old filters.module.js

new paid-orders.filter.js

export class PaidOrders { static transform () { return (orders) => orders.filter(order => order.paid === false) } }

import {PaidOrders} from './paid-orders.filter'; export default angular.module('filters', []) .filter('paidOrders', PaidOrders.transform);

new filters.module.js

VIEW CONTROLLERS

old log.controller.js

export function LogController ($scope, $window, Orders, Storage) { $scope.orders = Orders.getOrders(); $scope.clearHistory = function () { if ($scope.orders.length === 0) { return; } var result = $window.confirm("Clear History?"); if (result) { Storage.clear(); $scope.orders = []; } } }

new log.controller.js

export class LogController { constructor($scope, $window, Orders, Storage) { $scope.orders = Orders.getOrders(); $scope.clearHistory = function () { if ($scope.orders.length === 0) { return; } const result = $window.confirm("Clear History?"); if (result) { Storage.clear(); $scope.orders = []; } } } }

DROP THE$SCOPE

MORE JAVASCRIPT LESS ANGULARBind to the controller instance, use setters for watching changes, free yourself from $scope events

CLASS SYNTAX

$ git checkout 03_drop_the_scope

old log.controller.js

export class LogController { constructor($scope, $window, Orders, Storage) { $scope.orders = Orders.getOrders(); $scope.clearHistory = function () { if ($scope.orders.length === 0) { return; } const result = $window.confirm("Clear History?"); if (result) { Storage.clear(); $scope.orders = []; } } }

old routes.js

export function routes($stateProvider, $urlRouterProvider) { $urlRouterProvider.otherwise("/"); $stateProvider .state('log', { url: '/log', templateUrl: 'templates/log.html', controller: 'LogController' }) }

<div class="container-fluid"> <div class="row"> <div class="container-fluid"> <span ng-click="clearHistory()"><i>clear history</i></span> <br/> <simple-table orders="orders"></simple-table> </div> </div> </div>

old log.html

new log.controller.js

export class LogController { constructor( $window, Orders, Storage) { this.window = $window; this.store = Storage; this.orders = Orders.getOrders(); } clearHistory = function () { if (this.orders.length === 0) { return; } const result = this.window.confirm("Clear History?"); if (result) { this.store.clear(); this.orders = []; } } }

new routes.js

export function routes($stateProvider, $urlRouterProvider) { $urlRouterProvider.otherwise("/"); $stateProvider .state('log', { url: '/log', templateUrl: 'templates/log.html', controller: ‘LogController as Logs' }) }

<div class="container-fluid"> <div class="row"> <div class="container-fluid"> <span ng-click="Logs.clearHistory()"><i>clear history</i></span> <br/> <simple-table orders="Logs.orders"></simple-table> </div> </div> </div>

new log.html

WATCH CHANGES

$ git checkout 04_watch_for_changes

SHALLOW WATCH? JUST USE SETTERS.

$ git checkout 05_getters_setters

old home.controller.js

export class HomeController { constructor(Orders, $scope) { this.Orders = Orders; this.menu = this.Orders.getMenuItems(); this.orders = this.Orders.getOrders(); this.selectedOrder = null; $scope.$watch(()=>this.selectedOrder, this.changeHandler) } changeHandler(newVal,oldVal){ console.log('New order Was selected!'); console.log(newVal, oldVal); } }

new home.controller.js

export class HomeController { constructor(Orders) { this.Orders = Orders; this.menu = this.Orders.getMenuItems(); this.orders = this.Orders.getOrders(); this._selectedOrder = null; } set selectedOrder(order){ this.changeHandler(order); this._selectedOrder = order; } changeHandler(newVal){ console.log('New order Was selected!'); console.log(newVal); } }

DEEP WATCHING? THINK IMMUTABLE.

$ git checkout 06_be_immutable

old home.controller.js

export class HomeController { constructor(Orders) { this.Orders = Orders; this.menu = this.Orders.getMenuItems(); this.orders = this.Orders.getOrders(); this.selectedOrder = null; } createOrder(clientName) { const order = this.Orders.createOrder(clientName); this.clientName = ''; this.selectOrder(order); };}

new home.controller.js

export class HomeController { constructor(Orders) { this.Orders = Orders; this.menu = this.Orders.getMenuItems(); this._orders = this.Orders.getOrders(); this.selectedOrder = null; } set orders(orders) { console.log('orders changed!', orders); this._orders = orders; } createOrder(clientName) { const order = this.Orders.createOrder(clientName); this.orders = this.Orders.getOrders(); this.clientName = ''; this.selectOrder(order); };}

EMITTING EVENTS

$ git checkout 07_event_emitter

new dispatcher.js

export class Dispatcher { constructor() { this.subjects = {}; } emit(event, payload){ this.validate(event); this.subjects[event].forEach( callback => callback.apply(null, payload)) } on(event, callback){ this.validate(event); this.subjects[event].push(callback); } validate(event){ if(!this.subjects[event]) { this.subjects[event] = []; } } }

new storage.service.js

export class Storage { constructor(Dispatcher) { this.ORDERS_KEY = "ORDERS"; this.store = localStorage; this.dispatcher = Dispatcher; } getOrders() { return JSON.parse(this.store.getItem(this.ORDERS_KEY)) || []; }; saveOrders(orders) { this.store.setItem(this.ORDERS_KEY, JSON.stringify(orders)) this.dispatcher.emit('ORDERS_SAVED', orders); }; clear() { this.store.clear(); } }

new logger.factory.js

export class Logger { constructor($log, Dispatcher) { this.$log = $log; this.timeStamp = new Date().toString(); Dispatcher.on('ORDERS_SAVED', function (data) { this.debug(`storage saved the orders!`); console.log(data); }) } debug(msg) { this.$log.debug(this.timeStamp + ": " + msg) } log(msg) { this.$log.log(msg) } }

BREAK IT TO COMPONENTS

COMPOSE COMPONENTSBreak the UI layer into small, maintainable and reusable building blocks.

HOME

ORDERS LIST ORDER EDITOR

NEW ORDER FORM

ORDER LIST ORDER FORM

NO ORDER

EDIT ORDER FORM

ORDER ITEM LIST

EDITOR FOOTER

PROJECT SRTUCTURE

$ git checkout 08_group_by_feature

old structure new structure

new home.module.js

import {HomeController} from './home.controller'; function routes($stateProvider) { $stateProvider .state('home', { url: '/', templateUrl: 'home/home.html', controller: 'HomeController as Home' }) } export default angular.module('home', []) .config(routes) .controller({HomeController});

DIRECTIVES AS COMPONENTS

$ git checkout 09_directives_as_components

new folder structure

new home.module.js

function routes($stateProvider) { $stateProvider .state('home', { url: '/', template: '<home></home>' }) } export default angular.module('home', []) .config(routes) .filter('paidOrders', PaidOrders.transform) .directive({ home, newOrderForm, orderList, ordersList, noSelectedOrder, editOrderForm, orderItemList, orderEditorFooter, orderEditor });

new home.js

export function home () { return { template: ` <div class="container-fluid"> <div class="row"> <orders-list></orders-list> <order-editor></order-editor> </div> </div> `, controller: HomeController, controllerAs: 'Home' } }

new order-list.js

export function ordersList() { return { template: ` <div class="col-sm-6"> <new-order-form></new-order-form> <br/> <order-list></order-list> </div> ` } }

new order-editor.js

export function orderEditor() { return { template: ` <div class="col-sm-6"> <no-selected-order></no-selected-order> <div class="card" ng-if="Home.selectedOrder"> <div class="card-block"> <edit-order-form></edit-order-form> <order-item-list></order-item-list> <order-editor-footer></order-editor-footer> </div> </div> </div> ` } }

new no-selected-order.js

export function noSelectedOrder () { return { template: ` <div class="card" ng-if="!Home.selectedOrder"> <div class="card-block"> <h4 class="card-title">No order selected.</h4> </div> </div> ` } }

new order-list-item.js

export function orderItemList () { return { template:` <ul class="list-group list-group-flush"> <li class="list-group-item" ng-repeat="menuItem in Home.selectedOrder.items"> <span class="label label-default label-pill pull-xs-right"> {{menuItem.itemPrice}}</span> {{menuItem.itemName}} </li> </ul> ` } }

FINAL WORDS

MODULAR AND CLEAN CODE IS ALWAYS BETTER

MIGRATION WON’T BE EASY

START WITH SMALL IMPORTANT IMPROVEMENTS

THAT YOU CAN APPLY TODAY

https://github.com/nirkaufman/ng1-coffee-shop

GRAB THE CODE

http://tinyurl.com/hdmqrem

THANKS

ROME 18-19 MARCH 2016

[email protected]@nirkaufman on twitter slideshare.net/nirkaufman/ github.com/nirkaufman

All pictures belong to their respective authors