Angular, http et services
Pour cet article, je vous explique comment je vais faire interagir mon âfrontâ Angular avec des services Json fournis par Express.
Prérequis
Avoir lu les articles précédents :
ou la version âagrĂ©gĂ©eâ : http://e-books.github.io/je.decouvre.angular/
Rapidement, la partie serveur
Normalement (si vous avez suivi) vous devez avoir un projet qui ressemble à ça :
votre_projet/
âââ public/
| âââ bower_components/ /* angular est par ici */
| âââ js/
| | âââ main.js
| âââ index.html
âââ bower.json
âââ .bowerrc
Nous avons besoin dâinstaller Express, pour la base de donnĂ©es nous utiliserons NeDB qui est une petite base de donnĂ©es NoSQL lĂ©gĂšre Ă base de âfichiersâ qui fonctionne avec la mĂȘme logique que MongoDb, vous pourrez donc porter votre code facilement par la suite.
Pour plus dâinformations, vous pouvez lire mon article: http://k33g.github.io/2014/05/11/NEDB.html
Installation Express + NeDB
A la racine de votre projet, créez un fichier package.json avec ce contenu:
{
"name": "books",
"description" : "books",
"version": "0.0.0",
"dependencies": {
"express": "4.1.x",
"body-parser": "1.0.2",
"nedb": "0.10.5"
}
}
Tapez dans une console la commande (Ă la racine de votre projet) :
npm install
Maintenant vous devriez avoir cette structure :
votre_projet/
âââ node_modules/ /* <-- Express et NeDB sont ici */
âââ public/
| âââ bower_components/ /* angular est par ici */
| âââ js/
| | âââ main.js
| âââ index.html
âââ package.json
âââ bower.json
âââ .bowerrc
Services JSON
Pour le moment je nâai prĂ©vu que 3 types de services possibles :
- Créer un livre
- Retrouver un livre par son id
- Récupérer la liste de tous les livres
Donc, créer à la racine de votre projet, un fichier app.js avec le contenu suivant:
var express = require('express')
, http = require('http')
, bodyParser = require('body-parser')
, DataStore = require('nedb')
, app = express()
, http_port = 3000
, booksDb = new DataStore({ filename: 'booksDb.nedb' });
app.use(express.static(__dirname + '/public'));
app.use(bodyParser());
// Liste de tous les livres
app.get("/books", function(req, res) {
booksDb.find({}, function (err, docs) {
res.send(docs);
});
});
// Obtenir un livre par son id
app.get("/books/:id", function(req, res) {
booksDb.findOne({ _id: req.params.id }, function (err, doc) {
res.send(doc)
});
});
// Ajouter un livre
app.post("/books", function(req, res) {
var book = req.body;
booksDb.insert(book, function (err, newDoc) {
res.statusCode = 301;
res.header("location", "/books/"+newDoc._id).end();
});
});
// Lancer l'application une fois la base chargée
booksDb.loadDatabase(function (err) {
app.listen(http_port);
console.log("Listening on " + http_port);
});Nous avons donc ceci:
votre_projet/
âââ node_modules/ /* <-- Express et NeDB sont ici */
âââ public/
| âââ bower_components/ /* angular est par ici */
| âââ js/
| | âââ main.js
| âââ index.html
âââ package.json
âââ bower.json
âââ .bowerrc
âââ app.js
Et nous sommes donc prĂȘts pour la suite.
API $http
Angular propose une API âajaxâ par le biais du âhelperâ $http qui permet donc de faire des requĂȘtes vers votre serveur.
Nous avons vu dans la partie précédente comment sortir nos modÚles du contrÎleur avec une factory, nous avions ceci:
/*--- factory ---*/
booksApp.factory("Models", function() {
var books = function() {
return [
{title:"Backbone c'est de la balle", description:"tutorial bb", level:"trĂšs bon"}
, {title:"React ça dépote", description:"se perfectionner avec React", level:"bon"}
, {title:"J'apprends Angular", description:"from scratch", level:"débutant"}
]
};
var levels = function() {
return [
"trÚs bon", "bon", "débutant"
];
}
return {
books : books, levels : levels
}
});Par la suite, cela va ĂȘtre le serveur qui va nous fournir les livres (pour le moment je laisse les niveaux (levels) âen durâ cĂŽtĂ© front). Donc, modifions notre factory Models de la façon suivante:
/*--- factory ---*/
booksApp.factory("Models", function() {
var levels = function() {
return [
"trÚs bon", "bon", "débutant"
];
}
return {
levels : levels
}
});Et modifiez le contrĂŽleur de cette maniĂšre:
- modifier la propriété
$scope.books = Models.books(); - remplacer la méthode
$scope.createBook - ajouter la méthode
$scope.getAllBooks - ajouter dans le constructeur du contrĂŽleur un appel Ă
getAllBooks - ajouter la rĂ©fĂ©rence Ă lâapi
$httpdans les paramĂštres du contrĂŽleur:booksApp.controller("MainCtrl", function($scope, $http, Models) {}) - supprimez
$scope.selectedBooketselectBook()(nous nâen avons plus besoin pour le moment)
var MainCtrl = booksApp.controller("MainCtrl", function($scope, $http, Models) {
$scope.books = null;
$scope.levels = Models.levels();
$scope.getAllBooks = function() {
$http.get("books").success(function(data) {
$scope.books = data;
})
}
$scope.createBook = function(book) {
$http.post("books", book).success(function(data){
$scope.getAllBooks();
$scope.book = null;
});
}
// charger les livres
$scope.getAllBooks()
});Nous utilisons lâAPI $http dans la mĂ©thode $scope.getAllBooks pour faire une requĂȘte de type GET sur lâuri books, ce qui aura pour effet dâappeler la route Express dĂ©finie plus haut par :
// Liste de tous les livres
app.get("/books", function(req, res) {
booksDb.find({}, function (err, docs) {
res.send(docs);
});
});Et nous utilisons lâAPI $http dans la mĂ©thode $scope.createBook pour faire une requĂȘte de type POST sur lâuri books, ce qui aura pour effet dâappeler la route Express dĂ©finie plus haut par :
// Ajouter un livre
app.post("/books", function(req, res) {
var book = req.body;
booksDb.insert(book, function (err, newDoc) {
res.statusCode = 301;
res.header("location", "/books/"+newDoc._id).end();
});
});Notez bien que la méthode $scope.createBook prend maintenant book en paramÚtre.
Avant dâessayer, nous allons apporter une petite modification Ă notre vue HTML, souvenez vous nous avions un bouton pour crĂ©er un livre âviergeâ:
<hr>
<a href="# " ng-click="createBook()">Ajouter un livre</a>
<hr>Modifiez comme ceci: (nous passons le modÚle book en paramÚtre de la méthode)
<hr>
<a href="# " ng-click="createBook(book)">Ajouter un livre</a>
<hr>Remonter cette partie, juste en dessous du formulaire de saisie (câest plus ergonomique) et modifiez les directives model en remplaçant selectedBook par book:
<div>
<input ng-model="book.title">
<input ng-model="book.description">
<div>
<select id="inputType"
ng-model="book.level"
ng-options="level for level in levels"></select>
</div>
<!-- j'ai mis mon bouton ici, c'est plus ergonomique -->
<hr>
<a href="# " ng-click="createBook(book)">Ajouter un livre</a>
<hr>
</div>Essayons
Donc, pour cela, en mode commande, à la racine, lancez la commande node app.js puis ouvrez dans votre navigateur http://localhost:3000. Cette fois-ci, vous allez obtenir une page vous permettant de saisir un nouveau livre, avec une liste vide (normal), maintenant essayez de créer des livres.
Si tout va bien ça fonctionne (sinon âpinguezâ moi).
Toujours plus propre : on découple
Nous avons dĂ©jĂ vu comment sĂ©parer les responsabilitĂ©s avec la factory et les Models, voyons maintenant comment utiliser la notion de services dâAngular, pour enlever la ânotionâ dâAPI $http du contrĂŽleur (on dĂ©couple).
Création du service BooksServices
Pour crĂ©er un service, il suffit dâutiliser la mĂ©thode service de notre module angular booksApp, de la maniĂšre suivante:
booksApp.service("BooksServices", function($http) {
this.getAll = function(callback) {
$http.get("books").success(function(data) {
callback(data);
})
}
this.create = function(book, callback) {
$http.post("books", book).success(function(data){
callback(data);
});
}
})Le raisonnement est un peu diffĂ©rent de la factory qui retournait une instance dâobjet/fonction, lĂ pensez un peu comme si vous aviez une classe avec des mĂ©thodes statiques.
Nous avons donc créé BooksServices qui propose 2 méthodes: getAll et create, remarquez le passage de $http en paramÚtre: booksApp.service("BooksServices", function($http) {})
Modification du contrĂŽleur MainCtrl
Apportons quelques modifications Ă notre contrĂŽleur:
- on ne passe plus
$httpen paramĂštre maisBooksServices: dites vous que si vous voyez$httpdans un contrĂŽleur câest quâil y a peut-ĂȘtre un souci ;) - nous modifions
getAllBooksetcreateBookpour utiliserBooksServices
var MainCtrl = booksApp.controller("MainCtrl", function($scope, Models, BooksServices) {
$scope.books = null;
$scope.levels = Models.levels();
$scope.getAllBooks = function() {
BooksServices.getAll(function(data) {
$scope.books = data;
})
}
$scope.createBook = function(book) {
BooksServices.create(book, function(data) {
$scope.getAllBooks();
})
}
$scope.getAllBooks()
});Il nây a plus quâĂ tester pour vĂ©rifier ⊠Et bien sĂ»r cela fonctionne \o/
Nous avons donc pu voir comment rendre notre code plus propre et plus facilement maintenable.
Demain nous verrons la factory $resource pour ĂȘtre complĂštement âRESTfulâ