2

Nell’ultimo periodo ho dovuto implementare un pannello di amministrazione parallelo a quello di WordPress per il sito internet di un cliente. Ho deciso di approfittarne per studiare AngularJs ma nel processo mi sono scontrato con la carenza di documentazione nell’affrontare situazioni che, date per scontate, vengono gestite in maniera un pò diversa dal framework javascript di casa Google.

Da dove iniziare?

Come per la maggior parte dei framework anche per AngularJs sono stati creati boilerplate per iniziare i vostri progetti al meglio. Ve ne posso segnalare almeno due che sono sicuro vi renderanno la vita molto più facile. Decidete da voi quale si adatta meglio al vostro stile di programmazione.

Angular Seed Angular Kickstart

E’ meglio usare this o $scope per comunicare con la view?

Una delle prime problematiche che ho dovuto affrontare nell’approcciare AngularJs è stata che nella sia nella documentazione ufficiale che in tutorials e guide varie venivano usati sia $scope che this per comunicare con la view.

L’errore, se si può definire tale, dipende dal fatto che dalla versione 1.2 di Angular è stata aggiunta la possibilità di accedere al controller attraverso l’operatore as direttamente dalle view senza dover interpellare lo $scope. Tolta questa limitazione l’uso onnicomprensivo di $scope è stato limitato a casistiche particolari ($wath, $broadcast ecc…) evitando inoltre che si creassero problemi di inheritance (ad esempio quando proprietà o metodi all’interno di child scope avevano lo stesso nome di quelle del parent scope). Senza contare poi che l’uso di $scope è decisamente più emblematico quando usato all’interno di applicazioni complesse.

Se la teoria è difficile da spiegare risulterà senz’altro chiaro il codice che segue. Nelle versioni precedenti di AngularJs per fare comunicare il controller con la view si sarebbe proceduto come segue:

Come avrete intuito finché non viene istanziato myVar nel controller figlio esso prende il valore da quello del controller padre creando non poca confusione quando il codice comincia a diventare complesso. Ora vediamo lo stesso esempio con this.

Chiaramente non stiamo parlando di abolire l’uso di $scope, uno dei punti di forza di AngularJs ma piuttosto di farne un uso coscienzioso e dettato dalla necessità, per tutti gli altri casi continuiamo ad usare this con cui, se state leggendo questo articolo, presumo avrete già familiarità.

Come effettuare una richiesta form POST con AngularJs

Il problema successivo è nato nel cercare di effettuare una richiesta Ajax POST a admin-ajax.php con il server che continuava a rispondere 0 ad ogni richiesta, come se non venisse letta l’action specificata. Ed in effetti non poteva essere letta in quanto $http.post di default invia i dati in formato Json mentre admin-ajax.php si aspetta una richiesta form post.

Per fortuna AngularJs prevede di poter modificare il comportamento di default di $http attraverso l’opzione transformRequest. Nel nostro caso le operazione da effettuare sono due: Modificare il content type della richiesta negli headers in x-www-form-urlencoded e serializzare i dati, per farlo è sufficiente procedere come segue:

/**
 * Controller
 */
function myController($http) {
  var data = {
    action: 'do_something'
  };
  $http({
    method: 'POST',
    url: ajaxurl,
    transformRequest: transformRequest,
    data: params
  }).success(function (response) {
    console.log(response);
  });
}

/**
 * Transform angularPost to formPost
 */
function transformRequest(data, getHeaders) {
  var headers = getHeaders();
  headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8';
  return serializeData(data);
}

/**
 * Serialize data
 */
function serializeData( data ) {
  if (!angular.isObject(data)) {
    return( ( data === null ) ? '' : data.toString() );
  }
  var buffer = [];
  for ( var name in data ) {
    if (!data.hasOwnProperty(name)) { continue; }
    var value = data[name];
    buffer.push(encodeURIComponent(name) + '=' + encodeURIComponent((value === null)? '' : value));
  }
  var source = buffer.join('&').replace(/%20/g, '+');
  return source;
}

Come attendere la risposta di chiamate $http multiple per eseguire un’azione

Altra problematica piuttosto comune è quella di dover attendere la risposta di chiamate $http multiple per eseguire in azione. Parliamo di Promises, che in jQuery gestiremmo con $.when mentre in angular andiamo ad usare $q.all che accetta come parametri un’array di promises e restituisce un metodo then che accetta una funzione con un paramentro contenente un’array di tutte le risposte. Andiamo direttamente al codice:

function MyController ($http, $q) {
  var promiseA = $http.get('/someUrl'),
      promiseB = $http.get('/someOtherUrl');

  $q.all([promiseA, promiseB]).then(function(responses){
    console.log(responses[0]); // Response to promiseA
    console.log(responses[1]); // Response to promiseB
  });
}

Come effettuare il $watch di una variabile locale

Altro problema annoso, se non si sa come approcciarlo, è quello di dover effettuare il watch di una variabile fuori dallo $scope della classe.

function MyController ($scope) {
  var vm = this;

  vm.myVar = 'Ciao!'; // Var to watch changes on

  $scope.$watch(function() {
    return vm.myVar;
  }, function(newData, oldData){
    console.log(newData, oldData);
  });

}

Qual’è il modo migliore di gestire le injection

L’injection delle dipendenze come descritto nell’esempio che segue viene effettuato in modo che quando si va a minificare il codice angular possa far risalire i nomi delle dipendenze minificate (tipicamente descritte da singole lettere: a, b, c, ecc…) al nome reale.

app.controller('MyController', ['$scope', 'greeter', function($scope, greeter) {
// ...
}]);

Ci sono davvero molti modi di effettuare l’injection delle dipendenze in AngularJs come possiamo dedurre dalla documentazione ufficiale, quello che invece non viene indicato è che è possibile utilizzare l’Implicit Annotation (ovvero senza dover effettuare l’injection “duplicata” delle dipendenze) senza doversi preoccupare della minificazione utilizzando un task runner come Gulp assieme all’ottimo modulo ngAnnotate che partendo dal codice nel prossimo esempio effettua automaticamente l’annotazione delle dipendenze risparmiandoci un sacco di lavoro e migliorando la leggibilità del codice.

app.controller('MyController', MyController);

function MyController ($scope, greeter) {
  // ...
}

Come visualizzare un indicatore di caricamento durante le chiamate $http

Un’altra esigenza piuttosto comune è quella di visualizzare un indicatore di caricamento in attesa dei risultati di una chiamata $http. Chiaramente l’operazione può essere gestita nelle modalità più svariate ma una delle possibilità è quella di intercettare tutte le chiamate di http e fare il broadcast di un evento nelle varie fasi della chiamata. Ma poche righe di codice valgono più di mille parole…

app.factory('myController', myController);
app.factory('httpInterceptor', httpInterceptorController);
app.config(interceptHttpProvide);
/**
 * My Controller
 */
function myController($http) {
  $http.get('/someUrl');
}
/**
 * Http interceptor to handle loading
 */
function httpInterceptorController ($q, $rootScope) {
  var numLoadings = 0;

  return {
    request: function (config) {
      numLoadings++;
      $rootScope.$broadcast('loader_show');
      return config || $q.when(config);
    },
    response: function (response) {
      if ((--numLoadings) === 0) {
        $rootScope.$broadcast('loader_hide');
      }
      return response || $q.when(response);
    },
    responseError: function (response) {
      if (!(--numLoadings)) {
          $rootScope.$broadcast('loader_hide');
      }
      return $q.reject(response);
    }
  };
}
/**
 * Push the interceptor in $httpProvider
 */
function interceptHttpProvide ($httpProvider) {
  $httpProvider.interceptors.push('httpInterceptor');
}

Come gestire routes con view annidate

Quando progettiamo la struttura di un sito o di un’applicazione otteniamo spesso una struttura ad albero del tipo:

Pagina Login ->
   Bacheca con menu laterale
   Pagina 1 con menu laterale
   Pagina 2 con menu laterale

Risulta subito evidente la ripetizione del menu laterale. Potremmo ottimizzare lo schema come segue:

Pagina Login ->
    Layout con menu laterale ->
        Bacheca
        Pagina 1
        Pagina 2

Abbiamo semplificato molto ma il concetto è chiaro. Purtroppo ngRoute non supporta di default view annidate per cui dovremo ricaricare il menu ad ogni cambio pagina oppure creare qualche condizione nel layout principale rendendo il codice poco chiaro. Per fortuna qualche sviluppatore indipendente ha pensato a un’elegante soluzione. Parliamo del modulo AngularUI Router che ci permette di annidare view e relative route. Date un’occhiata alla sezione domande frequenti, sono sicuro vi tornerà utile (stati persistenti, default view, ecc…).

Spero di avervi dato qualche informazione utile. Cercherò di tenere aggiornato l’articolo nel tempo con ulteriori considerazioni circa l’uso di AngularJs.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *