AngularJS

Formateur
27ans
Techlead
Formateur Angular
Membre de la communauté architecture B.U. Banque&Finance
Tour de table
Noms prénoms
Parcours
Division et site
Expérience en dev. et JS/AngularJs
Vos attentes
Avant d'aller plus loin
-
Javascript (ES5, ES6)
- Typescript
Les languages supporter par AngularJS :
EcmaScript: Une spécification de javascript
Javascript
- Langage objet mais sans classes
- Faiblement typé
- Fonctionnel
Ecmascript
- EcmaScript 5.1
- Mode strict
- JSON
EcmaScript 6
- classes
- modules
- promises
- arrow functions
Javascript (ES6)
-
Arrow functions -
Classes -
Collections -
get / set transparents -
let / const -
ES6 modules - ....
// ES6
class Shape {
constructor (id, x, y) {
this.id = id
this.move(x, y)
}
move (x, y) {
this.x = x
this.y = y
}
}
// ES5
var Shape = function (id, x, y) {
this.id = id;
this.move(x, y);
};
Shape.prototype.move = function (x, y) {
this.x = x;
this.y = y;
};Javascript (ES5/ES6)
(function(){
'use strict';
//code here
})();Strict mode
- Toujours utiliser le mode strict en début de fichier ou en début de fonction.
-
Permet de signaler des erreurs courantes immédiatement (fail-fast) :
- variable non déclarée
- unicité des propriétés dans les littéraux unicité des noms d'arguments
- etc ...
Javascript/ Typescript
Déroulement de la formation
Installation des outils
Git
NodeJs
Un IDE




Gulp, Bower, Npm, Yeoman, JsLint, BrowserSync
Gulp
- Gulp est un lanceur de tâches concurrent de Grunt.
- Contrairement à Grunt, Gulp utilise les streams NodeJS réduisant ainsi les écritures et lecture disque.
- Les fichiers de configuration Gulp sont également moins verbeux.
-
Gulp comme Grunt utilise 2 fichiers de configurations :
- package.json : définissant les plugins et metadata
- gulple.js : déclarant la configuration des tâches.
Bower
- Bower est un gestionnaire de dépendances client.
- Bower est configuré à l'aide de 2 fichiers :
- .bowerrc permet de renseigner le répertoire de destination, le proxy, etc...
- bower.json permet de gérer les dépendances
Yeoman
- Yeoman est un outil permettant de générer un squelette de webapp en utilisant les conventions (comme les artifacts Maven).
- Yeoman utilise Bower et Grunt lors de la génération du squelette de l'application.
- Un ensemble de générateurs de webapps sont . disponibles (http://yeoman.io/generators/)

UI frameworks
CSS preprocessor (extension du CSS)



Implémentation en Ruby et C++
Concurrente de SASS
Utilisé par Bootstrap,
implémentation en NodeJs
Pareil que SASS
mais implémentation en NodeJS et sans accolades



Web frameworks


Ecrit en Ruby
Développé par Facebook

Ecrit en Python.

Ressemble à AngularJs

Popularité
ngular 1.x
Text


Misko Hevery et Adam Abronsw
- Première version sortie le 30 juin 2011
- Aujourd'hui elle est en 1.6.x
ngular 1.x features


MVW
- Model: des objets javascripts conteants des données (POJO)
- View: templates HTML accédant aux données exposées
-
What ever
- Controller : fonction JS exposant le modèle aux vues via l'objet $scope
- Directive: concept spécique Angular permettant d'ajouter du comportement aux applications
MVW

Angular styleguide
Expression

Interpolation
<h1>{{ 1+1 }}</h1> // 2
<h1>{{ "Hello " + "world !!!" }}</h1> // Hello world
<h1>{{ "jaafar" | uppercase }}</h1> // JAAFARIgnore les null et undefined => affiche une chaine vide.
Règles:
Les variables des expressions sont évaluées depuis l'objet scope et non l'objet window.
Pour respecter la séparation du code entre les vues et les contrôlleurs, il est impératif de garder les expressions simples.
Il est préférable de faire appel à une fonction exposée par un contrôlleur.
Binding

Binding

Binding
<input type="text" ng-model="ctrl.hello"/> <h1>{{ ctrl.hello }} world !!!</h1>
<input type="text" ng-model="ctrl.myVar"/>
<h1 class="{{ ctrl.myVar }}">Hello world !!!</h1>Le double data-binding d'AngularJS permet de maintenir le modèle en accord avec la vue et vice-versa.
Binding
SCOPE
<input type="text" ng-model="ctrl.hello"/>
<h1>{{ ctrl.hello }} world !!!</h1> -
AngularJS va chercher un attribut nommé 'hello' dans l'objet scope, si celui-ci est trouvé alors la valeur de l'attribut sera afchée sinon une chaîne de caractères vide. -
Le scope est ainsi utilisé pour initialiser des valeurs ou récupérer les valeurs modifiées par un utilisateur
Binding
SCOPE

Module

Une application est un arbre de modules et de composants


Un arbre de modules
AppModule
CoreModule
SharedModule
FeaturesModule


Module
(function () {
'use strict';
angular
.module('app', []);
})();
-
AngularJS utilise une logique modulaire pour déclarer les différents composants de l'application -
Les librairies externes sont souvent récupérées sous forme de module - Les crochets sont indispensable pour déclarer un module
(function () {
'use strict';
angular
.module('app')
.controller()
.directive()
.pipe();
})();
Déclaration du module
Déclaration dans le module
Module
- ngCookies, pour la gestion des cookies
- ngTouch, pour les événement liés au mobile
- ngRoute, pour le routage
- ngResource, pour intéragir avec des ressources REST
- ngMessages, pour l'afchage de messages notamment pour les champs de formulaires
- ngAnimate, pour les animations
- ngSanitize, pour vérier le HTML
facultatifs
Module
- AngularUI Bootstrap, adaptation du module JS de Bootstrap en directives Angular
- Angular Material, implémentation de la spécification Material Design de Google en Angular.
- AngularUI Router, module de gestion de routes beaucoup plus puissant que le module officiel (ngRoute)
- Angular Translate, permet une gestion plus poussée de I18n.
3rd party dependencies
Controller

(function () {
'use strict';
angular
.module('app')
.controller('AppController', AppController);
function AppController() {
var vm = this;
}
})();
Controller
Bonne pratiques : StyleGuide
vm (ViewModel) : une capture de this.
Le this est contextuelle, il peut changer quand on l'appel à l'intérieur d'une fonction.
angular
.module('app')
.component('appComponent', {
templateUrl: 'app/app-component/app.component.html',
controller: AppController,
controllerAs: 'appCtrl'
});
function AppController() {
var vm = this;
vm.hello = 'Hello component World!';
}
Controller in component
Bonne pratiques : StyleGuide

1.5 =<
controllerAs : sucre syntaxique
qui nous évite l'utilisation direct du $scope
Déroulement du TP
Github de la formation
# git config --global http.proxy http://lill.proxy.corp.sopra:8080/
# git clone -b step-00 https://github.com/Jmadih/ng1-tp
# cd ng1-tp
# npm install && npm -g install gulp-cli && npm -g install bower && bower install
# npm install -g gulp
# gulp serve
●une branche d’exercice : step-00
●une branche de solution : step-00-solution
Un concept => une branche
Exercice
1
git checkout step-00
-
Déclarer pour le composant home sous le module ng1tp
- un module home
- un controller home
-
Ajouter la variable hello et l'afficher sur le template home.html
- Ajouter un input pour modifier la variable hello à l'aide de ng-model
Solution
1
git checkout -f step-00-solution
Route

AngularJS permet de créer des single page applications même si l'application ne contient qu'une page.
- Il est possible de recharger une partie du contenu en utilisant le mécanisme de routage d'Angular.
- Angular propose un module de routage nommé ngRoute.
- Néanmoins, en entreprise on lui préfère le module uiRouter.
Routes
Routes
Structurer son application :
Header
Body
Footer
//Etat
$stateProvider.state('contact.detail', {
url: '/contacts/:id',
templateUrl: 'contacts.html',
controller: 'ContactsCtrl as contacts',
resolve: {...}
});
//Route
$routeProvider.when('/contacts/:id', {
templateUrl: 'contacts.html',
controller: 'ContactsCtrl as contacts',
resolve: {...}
});
// Activer un etat dans un controlleur
$state.go('mystate');
// Activer un etat dans un template
<a ui-sref="mystate">Lien</a>STATE VS ROUTE
// home.route.js
(function () {
'use strict';
angular
.module('app.home').config(configState);
function configState($stateProvider) {
$stateProvider.state('main.home', {
url: '/home',
views: {
'content@layout': {
templateUrl: 'app/home/home.html',
controller: 'HomeController as homeCtrl'
}
}
});
}
})();
// home.module.js
(function () {
'use strict';
angular
.module('app.home', ['ui.router']);
})();Routes définition
Déclaration
Routes: structurer l'application
(function () {
'use strict';
angular
.module('app.layout').config(configState);
function configState($stateProvider) {
$stateProvider
.state('layout', {
templateUrl: 'app/layout/layout.html'
}).state('main', {
parent: 'layout',
abstract: true,
views: {
menubar: {
templateUrl: 'app/layout/header/menubar/menubar.html',
controller: 'MenubarController as menubar'
},
footer: {
templateUrl: 'app/layout/footer/footer.html'
}
}
});
}
})();
menubar
content
footer
Routes: structurer l'application
<div class="container-fluid">
<header>
<nav class="menubar navbar navbar-inverse navbar-default" ui-view="menubar">
<i>menubar</i>
</nav>
</header>
<div class="main-content" role="main" ui-view="content"><i>content</i></div>
</div>
<footer class="footer minimal" ui-view="footer"><i>footer</i></footer>
menubar
content
footer
Exercice
2
git checkout step-01
Suivre l'exemple de l'état ng1tp.homec
- Créer un nouveau composant "participants"
- Créer un nouvel état pour la page participants
- Déplacer le contenu du template home dans la page participants
- Ajouter l’accès à la page dans le menubar
Solution
2
git checkout -f step-01-solution
Resolver

- Permet de résoudre des dépendances et de les injecter dans un contrôleur.
- Si resolve est une promesse, elle sera résolue avant l'appel du contrôleur.
Resolver
//Etat
$stateProvider.state('contact.detail', {
url: '/contacts/:id',
templateUrl: 'contacts.html',
controller: 'ContactsCtrl as contacts',
resolve: {...}
});
//Route
$routeProvider.when('/contacts/:id', {
templateUrl: 'contacts.html',
controller: 'ContactsCtrl as contacts',
resolve: {...}
});
Resolver
//Etat
$stateProvider.state('contact.detail', {
url: '/contacts/:id',
templateUrl: 'contacts.html',
controller: 'ContactsCtrl as contacts',
resolve: {
contacts: function($http){
return $http.get('/api/v1/contacts'); // promise
},
simple: function(){
return 'simple';
}
}
});
// Controller
function contactDetailController($log, contacts, simple) {
var vm = this;
$log.log(contacts); // promise resolved
$log.log(simple); // simple
}
Angular 1.4.X
Resolver with component
//Component
angular
.module('app.contacts')
.component('contactsComponent', {
bindings: {
contacts: '<',
simple: '@'
},
templateUrl: 'app/contacts/contacts.html',
controller: contactsController,
controllerAs: 'contactsCtrl'
});
function contactsController($log) {
var vm = this;
this.$onInit = function () {
$log.log(vm.contacts);
$log.log(vm.simple);
};
}Angular 1.5 <
//Etat
$stateProvider.state('contact', {
url: '/contacts/:id',
templateUrl: 'contacts.html',
component: 'contactsComponent',
resolve: {
contacts: function($http){
return $http.get('/api/v1/contacts'); // promise
},
simple: function(){
return 'simple';
}
}
});
Exercice
3
git checkout step-02
-
Ajouter deux resolvers (dans home.route.js et participants.route.js) qui résoudront la variable 'simple' puis afficher les avec $log. -
Déplacer le tableau des participants du composant au resolver sur la route participants.route.js puis l'afficher avec $log.
Solution
3
git checkout -f step-02-solution
Service

Service
- Permet de stocker la logique métier de l'application
- De nombreux services sont fournis par le framework
- Les services fournis par Angular commencent toujours par $ Les services sont récupérées via l'injection de dépendance Angular ($log, $http, $animate)
- Les services Angular sont des singletons
Bonne pratiques : StyleGuide
Injection de dépendances (DI)
angular
.module('app.contacts')
.component('contactsComponent', {
bindings: {
simple: '@',
contacts: '<'
},
templateUrl: 'app/participants/participants.html',
controller: contactsController,
controllerAs: 'contactsCtrl'
});
function contactsController($log, $http, $scope) {
var vm = this;
this.$onInit = function () {
$log.log(vm.simple);
};
}AngularJs va reconnaître les paramètres de la fonction (en appelant toString() sur le contrôleur) puis récupérer les objets auprès des providers (ici $httpProvider)
$http
function ContactsService() {
return {
getContacts: getContacts
}
function handleSuccess(data, status, headers, config) {
// use data, status, headers, config
return data;
}
function handleError(data, status, headers, config) {
// handle errors
}
getContacts() {
return $http
.get(`/api/contacts`)
.success(handleSuccess)
.error(handleError);
}
}Angular 1.4.x
function ContactsService() {
return {
getContacts: getContacts
}
function handleSuccess(data, status, headers, config) {
// use data, status, headers, config
return data;
}
function handleError(data, status, headers, config) {
// handle errors
}
getContacts() {
return $http
.get(`/api/contacts`)
.then(handleSuccess)
.catch(handleError);
}
}Angular 1.6.x
Deprecated
Service vs Factory
(function () {
'use strict';
angular
.module('app.contacts')
.factory('contactsService', contactsService);
function contactsService() {
return { //revealing module pattern
read: read,
write: write
}
function read { }
function write { }
}
})();
// Controller
angular
.module('app')
.controller('contactsController',
function (contactsService) {
contactsService.read();
contactsService.write();
});(function () {
'use strict';
angular
.module('app.contacts')
.service('contactsService', contactsService);
function contactsService() {
this.read = function read {}
this.write = function write {}
}
})();
// Controller
angular
.module('app')
.controller('contactsController',
function (contactsService) {
contactsService.read();
contactsService.write();
});Service vs Factory
Si vous voulez que votre fonction soit appelée comme une fonction normale, utiliser le factory.
Si vous voulez que votre fonction soit instanciée avec le new, utilisez le service.
Exercice
4
git checkout step-03
// Démarrer l'application REST
# npm start
node _server/server.js
✔Express server listening on http://localhost:9000/
- Créer un service 'participants.service.js'
- Créer une fonction qui utilise $http pour appeler la liste des participants '/api/v1/participants'
- Appeler la fonction dans le resolver
Solution
4
git checkout -f step-03-solution
Directive

Directives
Une directive est un comportement que vous voulez modifier ou transformer un composant réutilisable de l'application
<html>
<head>
</head>
<body ng-app="myApp">
<section id="todoapp" ng-controller="TodoCtrl">
</section>
</body>
</html>ng-app, ng-controller
framework
Directives
<input name="country" ng-model="ctrl.country" />
template
ng-model : controlleur du model
$pristine : permet de savoir si le champ n'a pas été touché
$dirty : inverse de $pristine $valid : savoir si le champ est valide
$invalid : savoir si le champ est invalide
$error : map contenant les erreurs
$untouched : si le champ n'a pas perdu le focus
$touched : si le champ a perdu le focus
Directives
template
ng-if, ng-show, ng-hide
<div ng-if="false"> // enleve la div du DOM
Content
</div>
<div ng-if="true"> // remet la div dans le DOM
Content
</div>Directives
template
ng-repeat
<li ng-repeat="todo in todos> </li>
<div ng-repeat="todo in todos track by $index">{{ todo }}</div>Chaque élément aura son propre scope exposant les propriétés :
$index : position dans l'itérateur
$first : vrai s'il s'agit du premier élément
$last : vrai s'il s'agit du dernier
$middle : s'il s'agit ni du premier ni dernier élément $even/$odd : vrai si l'élément est pair/impair
Directives
template
ng-switch
<div ng-switch="profil">
<span ng-switch-when="ADMIN">Administrateur</span>
<span ng-switch-when="USER">Utilisateur</span>
<span ng-switch-default>Anonyme</span>
</div>Permet d'inclure un template après l'avoir compilé dans un élément du DOM
Directives
style
ng-show / ng-hide
<div ng-show="false"> // cache la div
Content
</div>
<div ng-hide="true"> // cache la div
Content
</div>Permet de cacher les éléments avec le style display:none
Directives
style
ng-class
.green { color: green;}
.bold { font-weight: bold;}
<input type="checkbox" ng-model="greenChecked"/>
<input type="checkbox" ng-model="boldChecked"/>
<div ng-class="{green: greenChecked, bold: boldChecked }">I'm classy</div>Permet d'appliquer un style dynamiquement
Directives
style
ng-disable
<input type="checkbox" ng-model="disable"/>
<button ng-disabled="disable">Disable</button>
{{disable}}
Permet de désactivé un élément dynamiquement
Directives
style
ng-readonly
<input type="checkbox" ng-model="isReadOnly"/><br/>
<input type="text" ng-readonly="isReadOnly" />
{{isReadOnly}}Permet de passer l'input en readOnly
Directives
Custum
Il existe 4 types :
- élément
- attribut
- classe CSS
- commentaire
Directives
Custum
<my-directive>load myDirective</my-directive> //Some titleElement
app.directive('myDirective', function() {
return {
template: '<h1>Some title</h1>'
// OR templateUrl: '/my-directive.html'
};
});Directives
Custum
<div my-directive></div> //Some titleAttribut
app.directive('myDirective', function() {
return {
template: '<h1>Some title</h1>'
// OR templateUrl: '/my-directive.html'
};
});Directives
Custum
<div class=”my-directive: expression;”>My Text</div> // Red when expression = true Class Css
app.directive('myDirective', function() {
return {
restrict: 'C',
link: function (scope, element, attrs) {
element.css('color', 'red');
}
};
});Directives
Restrict
Définition de l'implémentation de la directive
restrict : ‘A’ – attribute (default one) | <div my-directive></div>
restrict : ‘C’ – Class | <div class=”my-directive: expression;”></div>
restrict : ‘E’ – element | <my-directive></my-directive>
restrict : ‘M’ – Comment | <!– Directive : my-directive expression; –>Directives
Custum
Scope
Pour faire des composants réutilisables, il est possible de les isoler du scope.
app.directive('myDir', function() {
return {
scope: {},
template: '<h1>{{color}} Wont work</h1>'
};
});3 types :
@
=
&
Directives
Custum
Scope: @
<my-dir color="red"></my-dir>
app.directive('myDir', function() {
return {
scope: {
myColor: '@color'
},
template: '<h1>{{myColor}}</h1>' //red
};
});Directives
Custum
Scope: =
<my-dir obj="obj"></my-dir> // obj.color="red"
app.directive('myDir', function() {
return {
scope: {
myObj: '=obj'
},
template: '<h1>{{myObj.color}}</h1>' // red
};
});Par référence
Directives
Custum
Scope: &
// template
<my-dir showAlert="ctrl.showAlert()"></my-dir>
// controller
vm.showAlert = function(){
alert('red');
}
//directive
app.directive('myDir', function() {
return {
scope: {
showAlert: '&'
},
template: '<h1 ng-click="showAlert()>Show alert</h1>' // alert red
};
});Fonction par référence
Exercice
5
git checkout step-04
2 nouveaux attributs ont été ajouter au model :
- absent: boolean
- mood: {SMILE, MEH, FROWN, HANDPEACE}
En se basant sur les directives :
- Dupliquer le tableau et ajouter une checkbox pour l'afficher ou le cacher.
- Afficher sur la colonne Mood pour chaque valeur "mood" son icone
- Créer une custum directive "moodDirective" pour factoriser le code


Solution
5
git checkout -f step-04-solution
Filter

Les filtres permettent de transformer les valeurs d'affichage, de filtrer ou réordonner les éléments d'une collection.
Filtre
// UNIX syntaxe
{{ expression | uppercase }}
{{ expression | uppercase | trim }}
{{ array | orderBy:'name':true }}
// With JS
function myController($filter, $log){
var toUpper = $filter('uppercase');
$log.log(toUpper('hello')); // HELLO
}Dynamique
Filtre
<input type="text" ng-model="mysearch"/>
<tr ng-repeat="person in persons | filter:mysearch">
// Sur une propriété spécifique
<input type="text" ng-model="mysearch.name"/>
<tr ng-repeat="person in persons | filter:mysearch" />
<tr ng-repeat="person in persons | filter:{name: 'jaafar'}" />app.filter('makeUppercase', function () {
return function (item) {
return item.toUpperCase();
};
});
{{ 'jaafar' | makeUppercase }} // JAAFARCustum filter
Filtre
app.filter('startsWithLetter', function () {
return function (items, letter) {
var filtered = [];
return filtered;
};
});
<li ng-repeat="friend in person.friends | startsWithLetter:letter">
{{ friend }}
</li>Custum filter
app.filter('complexFiltre', function () {
return function (items, letter, something, somethingElse) {
var filtered = [];
return filtered;
};
});
<li ng-repeat="friend in person.friends | complexFiltre:letter:something:somethingElse">
{{ friend }}
</li>Exercice
6
git checkout step-05
-
Filtrer les tableaux pour séparer les personnes absentes et présentes (utiliser la propriété absent) -
Créer un custum filtre 'UndefinedFilter' qui permet d'afficher 'N/A' si aucune valeur n'est renseigner
- Appliquer ce filtre sur le champs birthDate et email
Solution
6
git checkout -f step-05-solution
Formulaire

Directive de formulaire
<form name="loginForm">
<input type="text" name="user" />
<input type="text" name="password" />
<button>connexion</button>
</form>La directive form instancie un contrôleur FormController : loginForm
Qui est le parent des ngModelControllers : user et password
Pour accéder à un champs :
- loginForm.user
- loginForm.password
Directive de formulaire
Chaque controlleur expose les méthodes:
$pristine : si l'utilisateur n'a pas touché le formulaire
$dirty : si l'utilisateur a touché le formulaire
$valid : si l'ensemble des champs du formulaire est valide
$invalid : si un des champs du formulaire est invalide
$error: représente le dictionnaire des erreurs
$submitted : indique si le formulaire
Directive de formulaire
<form name="loginForm">
<input type="email" name="login" required ng-model="ctrlName.login"/>
<span ng-show="loginForm.login.$dirty && loginForm.login.$error.required">
Login is required
</span>
<span ng-show="loginForm.login.$dirty && loginForm.login.$error.email">
Invalid email form
</span>
<input type="password" name="password" ng-model="ctrlName.password"/>
<span ng-show="loginForm.login.$dirty && loginForm.login.$error.password">
Password is required
</span>
<button ng-disabled="loginForm.$invalid">connexion</button>
</form>Validation
Directive de formulaire
Attributs de controles d'input
- ngMinLength : valider la longueur min du champ
- ngMaxLength : valider la longueur max du champ
- ngChange : appeler une fonction lorsque le champ est modifié
- ngList : convertit un chaine de caractères en tableau
- ngTrueValue : valeur de la checkbox si true
- ngFalseValue : valeur de la checkbox si false
- ngValue : valeur du radio bouton si coché
Directive de formulaire
ng-message
<form name="loginForm" ng-submit="ctrlName.login()">
<input type="email" name="login" required ng-model="ctrlName.login"/>
<div ng-if="loginForm.login.$touched" ng-messages="loginForm.login.$error">
<span ng-message="required">
Login is required
</span>
<span ng-message="email">
Invalid email form
</span>
</div>
<button ng-disabled="loginForm.$invalid">connexion</button>
</form><form name="loginForm" ng-submit="ctrlName.login()">
<input type="email" name="login" required ng-model="ctrlName.login"/>
<span ng-show="loginForm.login.$dirty && loginForm.login.$error.required">
Login is required
</span>
<span ng-show="loginForm.login.$dirty && loginForm.login.$error.email">
Invalid email form
</span>
<button ng-disabled="loginForm.$invalid">connexion</button>
</form>Exercice
7
git checkout step-06
-
Créer un sous composant editParticipantsComponent sous le répertoire participants
-
Configurer sa route avec l'url '/participants/edit/:id'
- Ajouter dans le boutton "edit" le lien vers cette route
-
Récupérer dans le resolver le participant en question à l'aide d'une fonction (
getParticipant(id)) qui appellera le endpoint'/api/v1/participants/id' sur l'api REST*
*Utiliser $stateParams pour récupérer l'id de la route
Exercice
7
git checkout step-06
- Instancier un formulaire dans le template editParticipant
- Peupler les champs avec le model récupérer du résolver
-
Ajouter les controles suivants :
- firstname & lastname: required , taille min de 3 caractères
- email: type email
- Ajouter les messages d'erreur + appliquer le style has-error sur les champs
- Disable le boutton edit quand le formulaire est invalide
- Créer et appeler la fonction d'edition du participant (PUT '/api/v1/participants/:id')
- Rediriger vers la page participants sur le bouton 'cancel'
En se basant sur le code html sur la route 'homec'
Solution
7
git checkout -f step-06-solution
TH NK YOU FOR COMING

AngularJS
By Sydelab - Shaping Tomorrow
AngularJS
#AngularJS
- 353
