How-to become an Angular.js Guru?

Angular.js tricks to impress your colleagues and family

Created by Jonathan Petitcolas (@Sethpolma)

Jonathan Petitcolas

Twitter: @Sethpolma

Blog: www.jonathan-petitcolas.com

marmelab developer:
Node.js, Angular, React, [any exciting technology]

2 years of Angular.js

Angular Dev Cycle
Source: https://daveceddia.com/

Regular contributor on ng-admin

ng-admin

Angular Guru

Angular Guru

Logging

Easy way: console.log


console.debug(`Fetching API data for user #${user.id}`);

Restangular.one('user', 1337)
    .then(
        () => { /* ... */ },
        err => {
            console.error(err);
        }
    );

Better way: $log


$log.debug(`Fetching API data for user #${user.id}`);

Restangular.one('user', 1337)
    .then(
        () => { /* ... */ },
        err => {
            $log.error(err);
        }
    )
);

Better testing


it('should log API error if user API errs', () => {
    $log.error = jasmine.createSpy();
    Restangular.one = jasmine.createSpy().and
        .returns($q.reject('An error occured'));

    // call the service

    expect($log.error).toHaveBeenCalledWith('An error occured');
});

Log Decoration

app.config(['$provide', $provide => {
    const say = msg => {
        if (!window.speechSynthesis) return;

        const preparedMessage = new SpeechSynthesisUtterance(msg);
        window.speechSynthesis.speak(preparedMessage);
    };

    $provide.decorator('$log', ['$delegate', $delegate => ({
        error: message => {
            say(message);
            $delegate.error(message);
        }
    })]);
}]);

Live Demo

More fine-grained configuration

app.config($logProvider => {
  $logProvider.debugEnabled(false);
});

More options implementable using decorators

Better Module Organisation

Tree Folder of more DDD Angular app

// faq/clQuestionsTabs.js
require('./clQuestionsTabs.scss');

const questionsList = () => ({
    restrict: 'E',
    template: require('./clQuestionsTabs.html'),
    replace: true,
    scope: { questions: '=' },
    link: $scope => { /* ... */ },
});

questionsList.$inject = [];

export default questionsList;
// faq/index.js
const module = angular.module('clHelpFaq', [
    require('angular-ui-router'),
]);

module
  .config(require('./routing'))
  .constant('FAQ_KEYWORDS', require('../constants/keywords'))
  .service('clQuestionsFilterer', require('./clQuestionsFilterer'))
  .directive('clAccordion', require('./clAccordion'))
  .directive('clQuestionsTabs', require('./clQuestionsTabs'));

export default module.name;
const helpApp = angular.module('helpApp', [
    require('angular-ui-router'),
    // ...

    require('./contact'),
    require('./faq'),
]);

Modernize your framesets!

Concrete sample of ui-views implementation

<!-- layout.html -->
<div>
  <cl-navigation-bar></cl-navigation-bar>
  <div class="wrapper">
    <div class="content" ui-view></div>
  </div>
</div>
const routing = $stateProvider => {
    $stateProvider.state('app', {
        abstract: true,
        url: '',
        template: require('./layout.html'),
    });
};

routing.$inject = ['$stateProvider'];

export default routing;
<!-- list.html -->
<div>
    <cl-list-filters></cl-list-filters>
    <div ui-view></div>
<div>
const routing = $stateProvider => {
$stateProvider.state('app.list', {
    abstract: true,
    url: '/list',
    template: require('./list.html'),
});

routing.$inject = ['$stateProvider'];

export default routing;
<!-- table.html -->
<table>
  <!-- [...] -->
</table>
const routing = $stateProvider => {
$stateProvider.state('app.list.table', {
    url: '/list/table',
    template: require('./table.html'),
});

routing.$inject = ['$stateProvider'];

export default routing;

`resolve` your async calls

const routing = $stateProvider => {
    $stateProvider.state('app.users', {
        url: '/users',
        template: require('./users.html'),
        controller: require('./UserCtrl'),
        resolve: {
            users: ['UserService', () => UserService.getAll()]
        }
    });
};
const controller = ($scope, users) => {
    $scope.users = users;
};

controller.$inject = ['$scope', 'users'];

export default controller;

Self-including directive

const loader = $document => ({
  show: message => {
    const $scope = $rootScope.$new();
    $scope.message = message;

    const body = $document.find('body').eq(0);
    const usage = $compile(`<loader message="message"></loader>`);

    const directive = $compile(usage)($scope);
    body.append(directive);
  });

  hide: () => {
    $document.find('cl-loader').remove();
  },
});
const controller = (loader, UserService) => {
  loader.show('Loading users...');
  UserService.getAll()
    .then(users => { /* ... */ })
    .catch(err => { /* ... */ })
    .finally(() => {
        loader.hide();
    })
};

Automatic Loader

app.run(($rootScope, clLoader) => {
  $rootScope.$on('$stateChangeStart', () => {
    clLoader.show('Loading page...');
  });

  $rootScope.$on('$stateChangeSuccess', () => {
    clLoader.hide();
  });

  $rootScope.$on('$stateChangeError', (event, toState) => {
    console.error(`Error while changing to ${toState.name} state`);
    clLoader.hide();
  });
});

Magic $timeout

link: ($scope, element) => {
    // width = 0
    const width = element.find('.my-div').width();
}
link: ($scope, element) => {
    $timeout(() => {
        // width != 0
        const width = element.find('.my-div').width();
    })
}

Invoke services from config

app.config($http => {
    const token = angular.injector(['ng', 'ngCookies'])
        .get('$cookies')
        .get('token');`

    $http.defaults.headers.common['X-Auth-Token'] = token;
});

Performances

Track By

<ul>
	<li
	    ng-repeat="member in members"
	    class="member">
	        {{ member.firstName }} {{ member.lastName }}
	    </li>
	</ul>
<ul>
	<li
	    ng-repeat="member in members track by member.id"
	    class="member">
	        {{ member.firstName }} {{ member.lastName }}
	    </li>
	</ul>

JSFiddle by CodeLord

Disable Debug Mode

config(['$compileProvider', $compileProvider => {
  $compileProvider.debugInfoEnabled(false);
}]);

$digest Bomb

(() => {
  const $rootScope = angular.injector(['ng']).get('$rootScope');
  $digest = () => {
    $rootScope.$$postDigest($digest);
    $rootScope.$digest();
  }
  $digest();
})();

Kudos to @RobinBressan!

Any questions?

THE END

@Sethpolmawww.jonathan-petitcolas.com