Commit c81b0092 authored by Álex Cortiñas's avatar Álex Cortiñas
Browse files

Primera versión

parent 15fa423f
<!DOCTYPE html>
<html>
<head>
<title>Curso de Angular</title>
<meta charset="UTF-8">
<!-- <meta name="viewport" content="width=device-width, initial-scale=1"> -->
<!-- <link href='http://fonts.googleapis.com/css?family=Ubuntu:400,700,400italic' rel='stylesheet' type='text/css'> -->
<!-- SlidePack basic stylesheets and theme -->
<!-- <link rel="stylesheet" type="text/css" href="lib/slide-pack.min.css"> -->
<link rel="stylesheet" type="text/css" href="main.css">
</head>
<body>
<!-- <textarea data-slide-pack> -->
<textarea id="source">
name: inverse
layout: true
class: center, middle, inverse
---
template: inverse
# Curso de AngularJS (II)
## (versión 1.5x)
---
layout: false
# En anteriores episodios...
--
* Enfoque clásico: controllers + templates
???
* Scope, llamar directivas desde el HTML
--
* Directivas
--
* Componentes
--
* Servicios
???
* $http, promesas, $resource
--
* Filtros
--
* ui-router
---
template: inverse
# Directivas personalizadas
---
# Directivas personalizadas
* Directiva submenú:
```js
angular.module('app').directive('submenu', submenuDirective);
function submenuDirective() {
return {
restrict: 'E',
replace: true,
scope: {
menu: '='
},
templateUrl: 'app/components/menu/submenu.html'
};
}
```
```html
<ul class='dropdown-menu'>
<menu-item
ng-repeat='leaf in menu'
leaf='leaf'>
</menu-item>
</ul>
```
---
# Directivas personalizadas
* Se usaría como:
```html
<submenu menu="$ctrl.subtree"></submenu>
```
* Directiva *menuItem*:
```js
angular.module('app').directive('menuItem', menuItemDirective);
function menuItemDirective() {
return {
restrict: 'E',
replace: true,
templateUrl: 'app/components/menu/menu-item.html',
link: linkFunction,
scope: {
leaf: '='
}
};
}
```
---
# Directivas personalizadas
```js
function linkFunction(scope, element, attrs) {
if (angular.isArray(scope.leaf.subtree)) {
element.append('<submenu menu="leaf.subtree"></submenu>');
// find the parent of the element
var parent = element.parent();
var classFound = false;
// check if in the hierarchy of the element
// exists a dropdown with class navbar-right
while (parent.length > 0 && !classFound) {
// check if the dropdown has been push to right
if (parent.hasClass('navbar-right')) {
classFound = true;
}
parent = parent.parent();
}
// add a different class according to the position of the dropdown
if (classFound) {
element.addClass('dropdown-submenu-right');
} else {
element.addClass('dropdown-submenu');
}
$compile(element.contents())(scope);
}
}
```
---
# Directivas personalizadas
* Documentación en:
* [https://docs.angularjs.org/api/ng/service/$compile](https://docs.angularjs.org/api/ng/service/$compile)
* [https://docs.angularjs.org/guide/directive](https://docs.angularjs.org/guide/directive)
---
template: inverse
# Internacionalización
---
# Internacionalización
* Se usa principalmente la librería `angular-translate` ([https://angular-translate.github.io/](https://angular-translate.github.io/)).
* Hay varias opciones a la hora de internacionalizar:
* Definir las traducciones programáticamente.
* Tener un único fichero con todas las traducciones.
* Ficheros *json* de traducción parciales, por funcionalidad, y otros globales con mensajes típicos.
--
* En cada cambio de estado se cargan los ficheros que hagan falta, de no estar ya previamente cargados.
* Se configura la ruta de los *json* de internacionalización como:
```js
angular.module('app').config(config);
function config($translateProvider) {
$translateProvider.useLoader('$translatePartialLoader', {
urlTemplate: 'i18n/{lang}/{part}.json'
});
}
```
---
# Internacionalización
* En cada definición de estado indicamos qué *parts* deben cargarse.
```js
angular.module('app').config(config);
function config($stateProvider) {
$stateProvider
.state('car', {
parent: 'entity',
abstract: true,
resolve: {
translatePartialLoader: translatePartialLoader
}
});
}
function translatePartialLoader($translatePartialLoader) {
$translatePartialLoader.addPart('entities/car');
}
```
---
# Internacionalización
.pull-left[
* Un fichero JSON de ejemplo sería:
```json
{
"entity": {
"car": {
"name": "Car",
"prop": {
"id" : "id",
"brand" : "brand",
"model" : "model",
"color" : "color"
}
}
}
}
```]
.pull-right[
* Y la estructura:
```
i18n
* en
* entities
* car.json
* global.json
* es
* entities
* car.json
* global.json
```]
---
# Internacionalización
* En el HTML, con filtros o con **directivas**:
```html
<span>{{ 'entity.car.prop.brand' | translate }}</span>
<span translate>entity.car.prop.brand</span>
<span translate="entity.car.prop.brand">Brand</span>
```
* Puedes tener que obtener las claves con interpolación:
```html
<span translate>{{clave}}</span>
```
* Más info en: [https://angular-translate.github.io/docs/#/guide](https://angular-translate.github.io/docs/#/guide)
---
# Internacionalización
* También hay que tener en cuenta la internacionalización del propio Angular.
* Para ello se usan unos fichero JS extra, que se llaman `angular-i18n`.
* Hay uno por idioma. Se pueden cargar dinámicamente con el cambio de idioma:
```js
tmhDynamicLocaleProvider.localeLocationPattern(
'i18n/angular-locale/angular-locale_{{locale}}.js'
);
```
* Se cambia el idioma programáticamente:
```js
function changeLanguage(languageKey) {
$translate.use(languageKey);
tmhDynamicLocale.set(languageKey);
}
```
---
# Internacionalización
* Es importante controlar el cambio de idioma y la carga de páginas para refrescar los ficheros cuando sea necesario:
```js
function initialize() {
// refresh translate when new parts loaded
var deregistrationTranslateRefresh =
$rootScope.$on('$translatePartialLoaderStructureChanged',
function() {
$translate.refresh();
}
);
$rootScope.$on('$destroy', deregistrationTranslateRefresh);
// refresh title when translation changes
var deregistrationTranslateChange =
$rootScope.$on('$translateChangeSuccess',
function() {
updateTitle();
}
);
$rootScope.$on('$destroy', deregistrationTranslateChange);
}
```
---
template: inverse
# Interceptores
---
# Interceptores de $http
* Se define el interceptor como una función que devuelve cuatro propiedades (a su vez funciones), todas ellas opcionales: *request*, *requestError*, *response* y *responseError*.
* Se pueden definir tantos interceptores como se quiera.
* Para usarlos, se definen como factorías y se incluyen en la lista de interceptores de $http.
---
# Interceptores de $http
* Interceptor para incluir una cabecera HTTP en las peticiones:
```js
angular.module('app').factory('authInterceptor', authInterceptor);
function authInterceptor($localStorage) {
return {
request: function(config) {
config.headers = config.headers || {};
var token = $localStorage.token;
if (token && token.expires && token.expires > new Date().getTime()) {
config.headers['x-auth-token'] = token.token;
}
return config;
}
}
}
```
```js
angular.module('app').config(config);
function config($httpProvider) {
$httpProvider.interceptores.push('authInterceptor');
}
```
---
# Interceptores de $http
* Interceptor para controlar los errores HTTP:
```js
angular.module('app').factory('errorHandlerInterceptor', factory);
factory.$inject = [ '$q', '$rootScope' ];
function factory($q, $rootScope) {
var service = {
responseError: responseError
};
return service;
function responseError(response) {
if (response.status !== 401 || response.data !== '' &&
(!response.data.path ||
response.data.path.indexOf('/api/account/account') !== 0 )) {
$rootScope.$emit('app.httpError', response);
}
return $q.reject(response);
}
}
```
---
# Interceptores de *ui-router*
* En las versiones 0.x, *ui-router* emitía eventos cuando se producen cambios de estado, errores o similares. Simplemente había que controlarlos mismos y ejecutar el código necesario en cada caso. Por ejemplo:
```js
angular.module('app').run(function($rootScope, AuthService, $state) {
var deregistrationStateChangeStart = $rootScope.$on('$stateChangeStart',
function(evt, toState, toParams, fromState, fromParams) {
if (toState.name.indexOf("auth.") === 0) {
if (!AuthService.isAuthenticated()) {
evt.preventDefault();
$state.go('login');
}
}
}
);
$rootScope.$on('$destroy', deregistrationStateChangeStart);
})
```
* Hasta ahora, cuando nos fallaba un *resolve*, por ejemplo no pudiendo obtener el conjunto de elementos, nos saltaba un evento `$stateChangeError`.
---
# Interceptores de *ui-router*
* En las nuevas versiones, 1.x, se usa lo que llaman el *Transition Hook API*.
```js
angular.module('app').run(function($transitions) {
$transitions.onStart({ to: 'auth.**' }, function(trans) {
var auth = trans.injector().get('AuthService')
if (!auth.isAuthenticated()) {
// User isn't authenticated. Redirect to a new Target State
return trans.router.stateService.target('login');
}
});
})
```
---
template: inverse
# Guía de estilo
## Estructura de una aplicación Angular
---
# Guía de estilo de John Papa
La guía de estilo más conocida y común de Angular es la de John Papa, que se puede consultar en [https://github.com/johnpapa/angular-styleguide](https://github.com/johnpapa/angular-styleguide).
En esta sección haremos un resumen de las normas de estilo más importantes.
Hay que tener en cuenta que en JavaScript debemos ser especialmente ordenados, ya que no tenemos toda la potencia de los IDEs de Java como Eclipse para andar haciendo Control + Click.
---
# 1 componente = 1 fichero
* Cada componente va en un fichero diferente.
* Ningún fichero debe superar las 400 líneas.
* Ninguna función debe superar las 75 líneas.
.bad[
```javascript
angular
.module('app', [ 'ui-router' ])
.controller('SomeController', SomeController)
.factory('someFactory', someFactory);
function SomeController() { }
function someFactory() { }
```
]
---
# 1 componente = 1 fichero
.good[
```javascript
// app.module.js
angular
.module('app', ['ngRoute']);
// some.controller.js
angular
.module('app')
.controller('SomeController', SomeController);
function SomeController() { }
// some.service.js
angular
.module('app')
.factory('someFactory', someFactory);
function someFactory() { }
```
]
* Lo mismo se aplica para los *services*, *resources*, *filters* o cualquier elemento de Angular.
---
# Nombrado de ficheros
| tipo | nombre elemento | nombre fichero |
|------------|------------------|-------------------------------|
| componente | `carList` | `car-list.component.js` |
| plantilla | - | `car-list.template.html` |
| servicio | `LeafletMapUtil` | `leaflet-map-util.service.js` |
| filtro | `langFromKey` | `lang-from-key.filter.js` |
| recurso | `CarOwner` | `car-owner.resource.js` |
| estado | - | `car.state.js` |
---
# Principio LIFT
* `L`ocating our code is easy: agrupar ficheros por funcionalidad.
--
* `I`dentify code at a glance: al mirar el nombre de un fichero, tienes que saber qué te vas a encontrar.
--
* `F`lat structure **as long as we can**: ¿más de 7 ficheros en un directorio? igual va siendo hora de agrupar en subdirectorios.
--
* `T`ry to stay DRY (Don’t Repeat Yourself) or T-DRY: no repetir cosas obvias.
--
* Por ejemplo, un fichero *html* en una carpeta de un componente es sí o sí una plantilla. ¿Merece la pena añadirle el sufijo *template*?: `car-list.template.html` vs `car-list.html`.
--
* Caso contrario: directorio raíz con `app.module.js`, `app.state.js` y `app.constants.js`.
---
# IIFE (Immediately Invoked Function Expression)
* ¿Por qué? Colisiones entre nombres, variables "viviendo" fuera de donde se supone, problemas de minimizado.
.bad[
```js
// logger.service.js
angular
.module('app')
.factory('logger', logger);
function logger() { }
// storage.service.js
angular
.module('app')
.factory('storage', storage);
function storage() { }
```
]
---
# IIFE
.good[
```js
// logger.service.js
(function() {
'use strict';
angular
.module('app')
.factory('logger', logger);
function logger() { }
})();
// storage.service.js
(function() {
'use strict';
angular
.module('app')
.factory('storage', storage);
function storage() { }
})();
```
]
---
# IIFE
.good[
```js
// logger.service.js
(function(angular) {
'use strict';
angular
.module('app')
.factory('logger', logger);
function logger() { }
})(angular);
// storage.service.js
(function(angular) {
'use strict';
angular
.module('app')
.factory('storage', storage);
function storage() { }
})(angular);
```
]
---
# Evitar crear variables innecesarias
.bad[
```js
var app = angular.module('app', [
'ngAnimate',
'ngRoute',