Bonjour,

Je m'initie à Angular, j'ai commencé par des fonctionnalité simple pour commencer.

Ce que je fais

Décrivez ici votre code ou ce que vous cherchez à faire

Html

<main class="elevepage">
    <section class="eleve-col main" ng-app="appStudent" ng-controller="studentCtrl">

        <section class="student-form">
            <form class="form-student">
                <div class="eleve-prenom">
                    <div class="input-block">
                        <label for="fname">Prénom</label>
                        <input type="text" name="fname" id="fname" ng-model="fname">
                    </div>
                    <div class="alert"></div>
                </div>

                <div class="eleve-nom">
                    <div class="input-block">
                        <label for="lname">Nom</label>
                        <input type="text" name="lname" id="lname" ng-model="lname">
                    </div>
                    <div class="alert"></div>
                </div>

                <div class="eleve-formation">
                    <label for="formation">Formation</label>
                    <select name="formation" id="formation" ng-model="formation">
                        <option value="Développeur Multimédia">Développeur Multimédia</option>
                        <option value="Développeur Front End">Développeur Front End</option>
                    </select>
                </div>

                <div class="action-form">
                    <input type="submit" class="btn eleve-ajout" value="Ajouter" ng-click="addItem()">
                </div>
            </form>

            <form class="form">
                <div class="eleve-recherche">
                    <label for="searchStudent">Rechercher un élève</label>
                    <input type="text" id="searchStudent" ng-model="searchStudent">
                </div>
            </form>

        </section> <!-- student form -->

        <section class="resultat">

            <table>

                <thead>
                    <tr class="resultat-entete">
                        <th class="avatar">Avatar</th>
                        <th class="prenom">
                                Prénom
                        </th>
                        <th class="nom">
                            Nom
                        </th>
                        <th class="formation">
                            Formation

                        </th>
                        <th class="supprimer">Edition</th>
                    </tr>
                </thead>

                <tbody>
                    <tr class="resultat-ligne" ng-repeat="i in students | filter : searchStudent">
                        <td class="resultat-avatar">
                            <img src="assets/img/eleve/{{ i.avatar }}" alt="photo de {{ i.fname + ' ' + i.lname }}">
                        </td>
                        <td class="resultat-prenom">
                            {{ i.fname }}
                        </td>
                        <td class="resultat-nom">
                            {{ i.lname }}
                        </td>
                        <td class="resultat-formation">
                            {{ i.formation }}
                        </td>
                        <td class="resultat-supprimer">
                            <button ng-click="modifyItem(i)" class="btn">Modifier</button>
                            <button ng-click="removeItem(i)" class="btn">Supprimer</button>
                        </td>
                    </tr>
                </tbody>

            </table>

        </section> <!-- resultat -->

    </section> <!-- eleve -->

Js avec angular

var appStudent = angular.module('appStudent', []);
appStudent.controller('studentCtrl', function ($scope) {
    $scope.students = [
        {avatar: 'titi.jpg', fname: 'titi', lname: 'toto', formation: 'Développeur Front End'},
        {avatar: 'tutu.jpg', fname: 'tutu', lname: 'tata', formation: 'Développeur Front End'},
        {avatar: 'foo.jpg', fname: 'foo', lname: 'br', formation: 'Développeur Multimédia'},
    ];

    $scope.firstNameElement = document.querySelector('input[id=fname]');
    $scope.lastNameElement = document.querySelector('input[id=lname]');
    $scope.formationElement = document.querySelector('select[id=formation]');
    $scope.buttonElement = document.querySelector('input[type=submit]');

    $scope.searchStudent = '';

    /* fonction réutilisable */
    //fontion qui force la fenêtre a se place sur un element.
    $scope.scrollTo = function (element) {
        if (element === 'top') {
            window.scrollTo(0, 0);
        }
        else if (element === 'table') {
            var tableEl = document.querySelector('table');
            //getBoundingClientRect() est une methode qui retourne la taille d'un élément et la position relative par rapport au viewport.
            var positionTable = tableEl.getBoundingClientRect();
            window.scrollTo(positionTable.top, positionTable.left);
        }
    };

    //fonction qui permet de parcourir les élements d'un select et d'en selectionner un seul par la valeur
    $scope.selectItemByValue = function(element, value){

        for(var x=0; x < element.options.length; x++)
        {
            if(element.options[x].value == value)
                element.selectedIndex = x;
        }
    };

    /* Fin des fonction réutilisable*/

    /* Principale fonctionnalité  */

    $scope.removeItem = function (i) {
        var index = $scope.students.indexOf(i);
        $scope.students.splice(index, 1);
    };

    $scope.addItem = function () {
        $scope.students.push({
            avatar: 'unknow.jpg',
            fname: $scope.fname,
            lname: $scope.lname,
            formation: $scope.formation
        });

        //vider les valeurs après la validation.
        $scope.fname = '';
        $scope.lname = '';
        $scope.formation = '';
    };

    $scope.modifyItem = function (i) {
        /*
         *
         * En selectrionnant l'item
         * On récupère la valeur de la ligne du tableau
         * Les informations remonte dans celui-ci
         * On modifie le formulaire : le bouton se tranforme en modifier
         *
         */
        var selected = $scope.students.indexOf(i);

        var studentItem = {
            fname: $scope.students[selected].fname,
            lname: $scope.students[selected].lname,
            formation: $scope.students[selected].formation
        };

        $scope.scrollTo('top');

        var fillForm = function () {
            $scope.firstNameElement.setAttribute('value', studentItem.fname);
            $scope.lastNameElement.setAttribute('value', studentItem.lname);
            $scope.selectItemByValue(formation, studentItem.formation);
            $scope.buttonElement.setAttribute('value', 'Modifier');
            $scope.buttonElement.setAttribute('ng-click', 'modifyStudent()');
        };

        fillForm();

        return selected;
    };

    /*
    * Au clique sur le bouton modifier on change l'item sélectionner dans le tableau
    * A la fin on refait l'opération inverse, changement de l'état du bouton value et ng-click
    */

    var modifyStudent = function () {

        $scope.students[$scope.selected].push({
            fname : $scope.firstNameElement,
            lname : $scope.lastNameElement,
            formation : $scope.formation
        });

        $scope.firstNameElement.setAttribute('value', '');
        $scope.lastNameElement.setAttribute('value', '');
        $scope.selectItemByValue(formation, '');
        $scope.buttonElement.setAttribute('value', 'Ajouter');
                $scope.buttonElement.setAttribute('ng-click', 'addItem()');
    };
});

Ce que je veux

Modifier les élèves de mon tableau, en faisant remonter les informations concernant celui sélectionner dans le formulaire.
Changer le formulaire niveau du bouton. Et mettre à jour les informations.

Ce que j'obtiens

J'obtiens plusieurs choses :
1 - Les informations montent bien sur le formulaire. le bouton change bien comme décrit dans la fonction modifyItem(). Mais au click le bouton agit exactement comme ma fonction ajouter addItem(). Ne prends pas en compte les élément non modifié alors que je pensais avoir demander dans mon script de récupérer la totalité des valeurs.
2 - A la fin de ma fonction modifyItem() les champs n'ont pas l'air de se vider. Et le bouton ne reviens plus à son été initiale addItem().
3 - Quand je re-clique sur un bouton modifier. Les informations remonte dans les balise Html mais ne s'affiche plus sur le navigateur. (?!?)

Si ça ne semble clair, j'ai fais un petit Jsfiddle :
https://jsfiddle.net/keuo1q86/4/

Merci d'avance celle et ceux qui peuvent m'aiguiller sur une solution.

10 réponses


Bonjour,

c'est normal d'avoir ces erreurs car tu modifies des éléments AngularJS avec du code non AngularJS, du coup tu perds toute la notion de scope et de watcher.

Pour régler ton problème, il te faut penser un peu différemment.

Problème du formulaire :
Il faut voir le formulaire comme un système d'entrée/sortie découpée de ton student. Le rôle de ton formulaire c'est de prendre des propriétés et de les enregistrer.

Problème de la création/update :
Une fois que le formulaire a récupéré les valeurs, tu peux analyser s'il s'agit d'une création ou d'un update.

j'ai fait un petit schéma :
http://hpics.li/7c9a16d

html

<section class="student-form">
            <form class="form-student">
                <div class="eleve-prenom">
                    <div class="input-block">
                        <label for="fname">Prénom</label>
                        <input type="text" name="fname" id="fname" ng-model="data.fname">
                    </div>
                    <div class="alert"></div>
                </div>

                <div class="eleve-nom">
                    <div class="input-block">
                        <label for="lname">Nom</label>
                        <input type="text" name="lname" id="lname" ng-model="data.lname">
                    </div>
                    <div class="alert"></div>
                </div>

                <div class="eleve-formation">
                    <label for="formation">Formation</label>
                    <select name="formation" id="formation" ng-model="data.formation">
                        <option value="Développeur Multimédia">Développeur Multimédia</option>
                        <option value="Développeur Front End">Développeur Front End</option>
                    </select>
                </div>

                <div class="action-form">
                    <input type="submit" class="btn eleve-ajout" value="Ajouter" ng-click="saveForm()">
                </div>
            </form>

Et voila le squelette du code (tout n'y est pas, je n'ai mis que ce qui corrige ton problème).

var appStudent = angular.module('appStudent', []);
appStudent.controller('studentCtrl', function ($scope) {
    $scope.students = [
        {id:1, avatar: 'titi.jpg', fname: 'titi', lname: 'toto', formation: 'Développeur Front End'},
        {id:2, avatar: 'tutu.jpg', fname: 'tutu', lname: 'tata', formation: 'Développeur Front End'},
        {id:3, avatar: 'foo.jpg', fname: 'foo', lname: 'br', formation: 'Développeur Multimédia'},
    ];
    $scope.searchStudent = '';

    // les données du formulaire
    $scope.data = {}

    $scope.saveForm = function() {
      //si id est dans le tableau $scope.students
      updateStudent($scope.data);

     // sinon
     addNewStudent($scope.data);

    // vider les champs
    }

    $scope.modifyItem = function(student) {
        fillForm(student); 
    }

    function fillForm(data) {
        $scope.data = data;
   }

   function addNewStudent(studentData) {
        // ajouter le student dans le tableau
    }

    function updateStudent(studentData) {
        // recuperer l'id depuis studentData et modifier le bon student
    };

    // initialisation du formulaire
    $scope.fillForm({
      id: 0,
      fname : '',
      lname: '',
      formation : ''
    });
});

PS : Lorsque tu utilises ngModel, ne modifie pas la valeur avec des setAttribute, modifie directement la valeur.

// imaginons que nous avons le html suivant
// <input type="text"  ng-model="toto">
// <button ng-click="modify()" class="btn">Modifier</button>

$scope.toto = "hello"

$scope.modify = function() {
  $scope.toto = "au revoir"
}

Tu verras qu'Angular gère la mise à jour de la vue lors du clic sur le bouton. C'est le principe de watcher.

Je rajouterai une petite chose supplémentaire. le controller n'a pour but que la manipulation de donnée, si tu manipules le DOM, utiliser une directive est plus approprié.

Techniquement on devrait plutôt partir sur des components que des directives dans son exemple mais je suis d'accord avec toi. Je pense que ça fera un peu trop de changements d'un coup, il vaudrait mieux aller par étapes.

reuno92
Auteur

J'ai lu vos réponse et je vous en remercie. Si je fais une synthèse ça devrait donner ça :
Dans mon HTML :

<body ng-app="appStudent">
<header class=header-page></header>
<main class="elevepage" ng-controller="studentCtrl">
    <section class="eleve-col main" >
        <section class="student-form">
        </section>
   </section> <!-- student form -->
   <section class="resultat">
   </section> <!-- resultat-->
<footer class="footer-page"></footer>
</body>

Dans mon js :

var appStudent = angular.module('appStudent', []);
appStudent.directive('headerPage', function() {
   restrict : "C",
   template : "//mon header"
}
appStudent.directive('footerPage', function() {
   restrict : "C",
   template : "//mon footer"
}
appStudent.directive('studentForm', function(){
    return {
         restrict : "C",
         template : "//mon formulaire html"
    }
});
appStudent.directive('resultat', function() {
  return {
   restrict : "C",
   template : "//ma table html"
});
app.controller("studentCtrl", function($scope){
//La structure du code de prBaron
});

Si j'ai bien saisi, je dois placer dans mon html que les grandes sections de ma page où sont appelé.

Mes questions :
Est-ce utile de mettre en directive les header et les footer (pour ne pas avoir à les réécrire à chaque fois) ?
Quand l'application va grandir et qu'il y aura beaucoup de directive du coup. Ca va pas ralentir la navigation ?

Ce que j'ai compris avec Defy, c'est que dans les directives (ou le moins possible) on ne met pas de contenu brut. Donc dans mon select il ne devrait pas y avoir les options...

Dans ma directive studentForm

<select name="formation" id="formation" ng-option="data-formations" ng-model="data.formation">

Dans mon controller

$scope.data-formations = ['Développeur Multimédia', 'Développeur Front End'];

Par la suite, en lisant ce matin et en faisant des tests, j'avais pas compris compris jusqu'alors le principe du scope. Du coup, je viens de comprendre ce qui est dans le controller est récuperé les données dans mes inputs.

Avec quelle version d'AngularJS travailles tu ? A partir de la v1.5.0, ils ont sorti les components. Pour ton besoin tu devrais travailler avec ceci plutôt, la directive sera à utiliser pour modifier le comportement d'un élément déjà existant (exemple, modifier le comportement d'un input en rajoutant un datepicker). Le composant est à utiliser pour créer un nouvel élément réutilisable. Sur cette page, ils expliquent quand utiliser l'un ou l'autre : https://docs.angularjs.org/guide/component.

Est-ce utile de mettre en directive les header et les footer (pour ne pas avoir à les réécrire à chaque fois) ?

Oui, mais tu veux un component.

Quand l'application va grandir et qu'il y aura beaucoup de directive du coup. Ca va pas ralentir la navigation ?

Non

Dans ton exemple, $scope.data-formations doit être $scope.data.formations.

PS : quand tu crées des components, tu n'as pas le droit d'utiliser un nom déjà utilisé par HTML.

PS2: Utiliser 'C' comme valeur pour restrict est déconseillé (de même que 'M'). (cf doc : Comment directives were commonly used in places where the DOM API limits the ability to create directives that spanned multiple elements (e.g. inside <table> elements). AngularJS 1.2 introduces ng-repeat-start and ng-repeat-end as a better solution to this problem. Developers are encouraged to use this over custom comment directives when possible.).

Un component est une directive avec un restrict 'E'. Du coup, si tu veux utiliser une directive :

  • avec restrict 'E' : component
  • avec restrict 'A': directive.

    Les autres restrict sont deprecated et à ne pas utiliser.

reuno92
Auteur

Avec quelle version d'AngularJS travailles tu ?
Avec Angular 1.6-2

J'ai réussi à créer mes components, ça m'a pris pas mal de temps avant de comprendre. J'ai aussi changer ma manière de travailler mes fichiers js, pour me facilité la vie et y voir plus clair :

|-- app
|   |-- components
|   |            |------ common
|   |            |            |-- header.js
|   |            |            |-- footer.js
|   |            |------ eleve
|   |                          |-- formStudent.js
|   |                          |-- tableStudents.js
|   |-- partials
|   |         | -- footer.hmtl
|   |         | -- formStudent.html
|   |         | -- header.html
|   |         | -- tableStudent.html
|  angular1-6-2.js
|  app.js
|
index.html

J'ai à l'heure actuelle un erreur avec le ng-repeat-start et ng-repeat-end :

Error: [$compile:uterdir] http://errors.angularjs.org/1.6.2/$compile/uterdir?p0=ng-repeat-start&p1=ng-repeat-end
...

Et je n'arrive pas à savoir ce qui fonctionne pas. J'ai passé le reste de ma journée à lire la doc sans trouver ce qui pourrait me débloquer.

Si j'étais toi, je mettrai tout ce qui a attrait au même composant dans le même dossier, ca sera plus facile de copier/coller les composants entre tes projets par exemple.

|-- app
    |-- components
        |-- header
            |-- header.js
            | -- header.html
            | -- header.css
        |-- footer
            |-- footer.js
            | -- footer.html

Pour ton erreur de compilation, il va être dur de t'aider sans code.

reuno92
Auteur

Je réponds avec un peu de retard la semaine dernière j'étais entretien 3jours d'affillées + 1 réunion avec un conseiller et pas mal de chose à voir avec la préparation au entretien et les retour que j'ai eu (apprendre à utiliser des librairie JS pour animer plus efficacement des pages (TweenLite/Max.js et ScollMagic.js voir les deux ensembles. Si vous conaissez d'autre librairies du même style je suis preneur).

en gros voilà mon fichier HTML :

<!DOCTYPE html>
<html lang="fr"  ng-app="appStudent">
<head>
    <meta charset="UTF-8">
    <title>eleve ecole-multimdia</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="assets/css/screen.css">
    <script src="app/angular1-6-2.js"></script>
</head>
<body ng-controller="studentCtrl">
<header>
    <header-content class="header-menu"></header-content>
</header>

<main class="elevepage">

    <section class="eleve-col main">
        <form-student></form-student>
        <table-student></table-student>

    </section> <!-- eleve-col main -->
    <section class="eleve-col cadre">
        <header class="info-head">
            <div class="info-icon">
                <i class="fa fa-info-circle" aria-hidden="true"></i>
            </div>
            <h2>
                Informations
            </h2>
        </header>
        <article class="info-instruction">
            <ul>
                <li>Pour connaitre la liste des élèves, il suffit de regarder le tableau.</li>
                <li>Pour connaitre rechercher un élève, il suffit d'entrer le prénom, le nom de l'élève ou sa
                    formation.
                </li>
                <li>Pour ajouter un élève, il suffit d'entrer le prénom et/ou le nom de l'élève et de cliquer sur le
                    bouton <strong>Ajouter</strong>.
                </li>
            </ul>
        </article>
        <footer>
            Pour toutes informations, n'hésitez pas à nous contacter.
        </footer>
    </section> <!-- cadre -->
</main> <!-- elevepage -->

<footer class="footer">
    <footer-content></footer-content>
</footer>
<script src="app/main2.js" type="text/javascript"></script>
<script src="app/components/common/header.js"></script>
<script src="app/components/common/footer.js"></script>
<script src="app/components/eleve/formStudent.js"></script>
<script src="app/components/eleve/tableStudents.js"></script>
</body>
</html>

Mon component formStudent.js :

formStudentCtrl = function() {
    formations = ['Développeur Front End', 'Développeur Multimédia'];
};

app.component('formStudent', {
    templateUrl: 'app/partials/formStudent.html',
    controller : formStudentCtrl()
});

Mon partial formStudent.html

<section class="student-forms">
    <form class="form-student">
        <div class="eleve-prenom">
            <div class="input-block">
                <label for="fname">Prénom</label>
                <input type="text" name="fname" id="fname" data-ng-model="data.fname">
            </div>
            <div class="alert"></div>
        </div>
        <div class="eleve-nom">
            <div class="input-block">
                <label for="lname">Nom</label>
                <input type="text" name="lname" id="lname" data-ng-model="data.lname">
            </div>
            <div class="alert"></div>
        </div>
        <div class="eleve-formation">
            <label for="formation">Formation</label>
            <select name="formation" id="formation" ng-option="data.formations" data-ng-model="data.formation">
<!--                <option value="Développeur Multimédia">Développeur Multimédia</option>
                <option value="Développeur Front End">Développeur Front End</option>-->
            </select>
        </div>
        <div class="action-form">
            <input type="submit" class="btn eleve-ajout" value="Ajouter" ng-click="saveForm()">
        </div>
    </form>

    <form class="form">
        <div class="eleve-recherche">
            <label for="searchStudent">Rechercher un élève</label>
            <input type="text" id="searchStudent" ng-model="searchStudent">
        </div>
    </form>
</section> <!-- student form -->

mon component tableStudent.js :

tableStudentCtrl = function() {
    var students = [
        {id : 1, avatar: 'toto.jpg', fname: 'toto', lname: 'titi', formation: 'Développeur Front End'},
        {id : 2, avatar: 'tutu.jpg', fname: 'tutu', lname: 'tata', formation: 'Développeur Front End'},
        {id : 3, avatar: 'foo.jpg', fname: 'foo', lname: 'bar', formation: 'Développeur Multimédia'}
    ];

    var studentSearch = '';
};

app.component('tableStudent', {
    templateUrl : 'app/partials/tableStudents.html',
    controller : "tableStudentCtrl",
    controllerAs: "i",
    bindings: { value: "=" }
});

mon partial tableStudent.html.html :

<section class="resultat">
    <table>

        <thead>
        <tr class="resultat-entete">
            <th class="avatar">Avatar</th>
            <th class="prenom">
                Prénom et nom
            </th>
            <th class="formation">
                Formation

            </th>
            <th class="supprimer">Edition</th>
        </tr>
        </thead>

        <tbody>
        <tr class="resultat-ligne" ng-controller="studentCtrl" ng-repeat-start="i in students | filter : searchStudent">
            <td class="resultat-avatar">
                <img src="../../assets/img/eleve/{{ i.avatar }}" alt="photo de {{ i.fname }} {{ i.lname }}">
            </td>
            <td class="resultat-prenom">
                {{ i.fname }} {{ i.lname }}
            </td>
            <td class="resultat-formation" >
                {{ i.formation }}
            </td>
            <td class="resultat-supprimer" ng-repeat-end>
                <button ng-click="modifyItem(i)" class="btn">Modifier</button>
                <button ng-click="removeItem(i)" class="btn">Supprimer</button>
            </td>
        </tr>
        </tbody>

    </table>
</section> <!-- resultat -->

Mon fichier main.js

var app = angular.module('appStudent', []);

app.controller('studentCtrl', function($scope)  {

});

Problème rencontré

J'ai la page qui s'affiche avec les partials. Il se trouve que dans mes controllers. j'ai essayé de mettre $Scope dans les arguments et tout mes partials disparaissent de ma page... j'avoue je suis paumé entre le scope et les isolate scope de mes components...

Le scope est le lien entre le code JS et ta vue. Pour pouvoir l'utiliser, il te faut l'injecter dans ton controller.

app.component('formStudent', {
    templateUrl: 'app/partials/formStudent.html',
    controller : function($scope) { // injection du $scope dans le controller
      var myVar = true; // variable accessible seulement dans ton code JS
      $scope.foobar = 'Hello world'; // variable accessible dans ton code JS et dans ta vue.
    }
});

Lors de l'utilisation d'un component, la propriété controllerAs équivaut à $ctrl. tu peux donc faire la chose suivante dans ta vue :

<p>{{$ctrl.foobar}}</p> <!-- affiche Hello world-->

Dans le code que tu as donné, il y a comme erreurs :
1) Pas d'injection du $scope dans le code JS
2) Pas d'utilisation du $scope dans la vue et utilisation de mauvaises variables (ex : data.fname n'existe pas)
3) Mauvaise utilisation du controllerAs.

reuno92
Auteur

Je me doute qu'il n'y a pas d'injection. J'ai résolu une partie du problème :

Je suis passé de ça :

formStudentCtrl = function() {
formations = ['Développeur Front End', 'Développeur Multimédia'];
};

app.component('formStudent', {
templateUrl: 'app/partials/formStudent.html',
controller : formStudentCtrl()
});

à ceci :

formStudentCtrl = function() {
$scope.formations = [
        {
            'id': 1,
            'label' : '0',
            'name': 'Développeur Front End'
        },

        {
            'id' : 2,
            'label' : '1',
            'name' : 'Développeur Multimédia'
        }
    ];

    $scope.student = {
        'firstName'  : $scope.student.fname,
        'lastName'  : $scope.student.lname,
        'formation' : $scope.student.formation
    };
};

app.component('formStudent', {
    templateUrl: 'app/partials/formStudent.html',
    controller : 'formStudentCtrl' //fonctionne beaucoup mieux...
});

Ma question était que comme je suis un un controller différents pour chaque component. Comment je le déclare ?
Actuellement, je me retrouve avec une erreur :

Error: $controller:ctrlreg
A controller with this name is not registered.
The controller with the name 'formStudentCtrl' is not registered.

Donc J'ai essayé de mettre un ng-controller="formStudentCtrl" au parent j'ai le droit à "Wrong attribute Value"... Donc on peut pas imbriqué un controller dans un autre...

J'ai été regardé du coté de la doc avec https://docs.angularjs.org/api/ng/provider/$controllerProvider#register

La deuxième erreur qui revient depuis que j'essaye d'utiliser les components :

Error: $compile:uterdir
Unterminated Directive
Unterminated attribute, found 'ng-repeat-start' but no matching 'ng-repeat-end' found.