Editer

Généralités



Javascript est un langage objet mais orienté prototype : on va créer une instance d'un objet et on le modifie par la suite (contrairement au Java où on va créer le "moule" d'un objet -la classe).
Le langage est standardisé par la spécification Ecmascript (European Computers Manufacturers Association) et continue d'évoluer en rajoutant des nouvelles fonctions, syntaxes, etc. Dernièrement on a une nouvelle version d'Ecmascript tous les ans.



Objets


L'objet global est l'objet window, un seul pour chaque fenêtre (ou onglet si on utilise un navigateur à onglet). Si on tape toto=10 puis toto dans une console javascript, on voit que toto est égal à 10. Notre variable toto est lié à cet objet window, il suffit de taper window pour trouver notre variable toto.


Il y a trois façons de créer un objet :

1)Création d'un objet façon JSON



var personne={ nom:'Crasby', prenom:'Bob', direNom:function(){ console.log("Je m'appelle "+this.nom); } } personne.direNom();



2)Création d'un objet via son prototype



function Personne() { this.nom = 'Crasby'; this.moprenom = 'Bob'; } Personne.prototype.direNom = function () { console.log("Je m'appelle "+this.nom); } var p=new Personne(); p.direNom();



3)Création avec une classe



class Personne{ constructor(nom, age = 0){ //sera appelé quand on fera "new Personne()", ici age a une valeur par défaut this.setName(nom); //setters et getters comme en Java, attention il faut le "this" this.setAge(age); } seDecrire(){ return "Je m'appelle "+this.nom+" et j'ai "+this.age+" ans."; } } const p=new Personne("Bob");


Il y a plusieurs façons de créer une string, soit directement soit en instanciant un objet de type String :

a="lol"; "lol" typeof a; "string"


Et le deuxième :

b=new String("lol"); String { "lol" } typeof b; "object"


Dans les 2 cas on aura accès aux méthodes de String (par ex toLowerCase()).

On peut accéder/modifier les attributs d'un objet très facilement :

let o={}; o.monAttribut="ma belle valeur";


Les clefs peuvent être des objets : o['cette clef est une chaine'] = "ma belle valeur";


Nouveautés ES6


Liste de nouveautés
Changement de syntaxe et nouveaux mots-clefs :

//on ne fait plus "var" en es6 mais "let" qui change des choses par rapport au scope, //permet de mieux isoler les variables dans les boucles etc let a=12; console.log(a); //il y a désormais des constantes const user={name:"bob"}; console.log(user.name); user.name="bill"; //on peut quand même changer des trucs dans l'objet vers lequel pointe la constante console.log(user.name); //user={name:"John"}; //par contre on peut pas changer ce vers quoi pointe la constante //Il y a également maintenant des fonctions fléchées (Arrow functions) const action= () => { //autrefois on avait "var action=function() {", c'est pareil sauf qu'elle a pas son propre this console.log("test"); } action(); const sayHello = prenom => { //s'il n'y a qu'un argument on peut se passer des parenthèses return `Hello ${prenom}`; //les `` sont une nouvelle façon de concaténer avec des variables } console.log(sayHello("John")); const add = (a,b) => a+b; //sans accolades après la flèche, la valeur est retournée direct console.log((4+4)); let tab=[1,2,3]; let tab2=[...tab,4,5,6]; //les ... pour merger les tableaux console.log(tab2); let css={color:'red', display: 'inline-block'}; //ca marche aussi pour fusionner les fichiers let style = {...css, width:'400px'}; console.log(style); const {color:recupColor,width:recupWidth}=style; //plutôt que de faire recupColor=style.color, recupWidth=style.width console.log(recupColor,recupWidth); const {color,width}=style; //si on garde le même nom de variable, on peut se passer des 2 points console.log(color,width);


On est d'accord que la fonction ()=>{} ne se déclenche pas, elle ets juste déclarée, il faut faire (()=>{})() pour l'exécuter.

Classes :

//Nouveauté ES6 : les classes ! class Personne{ constructor(nom, age = 0){ //sera appelé quand on fera "new Personne()", ici age a une valeur par défaut this.setName(nom); //setters et getters comme en Java, attention il faut le "this" this.setAge(age); } setName(nom){ this.nom=nom; return this; //permet de continuer à manipuler directement derrière le set } getName(){ return this.nom; } setAge(age){ this.age=age; return this; } getAge(){ return this.age; } seDecrire(){ return "Je m'appelle "+this.nom+" et j'ai "+this.age+" ans."; } } const p=new Personne("Bob"); console.log(p.setAge(18).setName("Bobby")); //comme this est renvoyé par setAge on peut y chainer setName class User extends Personne{ constructor(nom, age, mail, mdp){ super(nom,age); this.mail=mail; this.mdp=mdp; } seDecrire(){ return "Je m'appelle "+this.nom+" et j'ai "+this.age+" ans. Mon mail : "+this.mail; } } const u=new User("Kev97",14,"kev97_le_vrai@hotmail.com","pass123"); console.log(u.seDecrire());


Construction de variables selon des tableaux (destructuring) :

tableau=["ab","b","c","d","e","f"]; const [,,valeur1,,valeur2]=tableau; console.log(valeur1, valeur2); //affichera "c e"


Marche aussi avec des objets :

monObjet={attribut1:"un",attribut2:"deux",attribut3:"trois",attribut4:"quatre"}; let {attribut1, attribut2, attribut4}=monObjet; //les var doivent avoir le même nom que les champs !




Template literals



Pour éviter l'injection de code (permet aussi de passer à la ligne sans devoir écrire \n) :

const personne = {nom:"Bob"}; let chainePresentation = `Mon nom est ${personne.nom}. Bonjour !`; console.log(chainePresentation); //"Mon nom est Bob."



Fonctions fléchées



argument valeur de retour

On met les parenthèses dans 2 cas : s'il n'y a aucun paramètres ou s'il y en a plus un.
Si la valeur de retour est une création d'objet avec des accolades, il faut la mettre entre parenthèse.


//exemple de fonction fléchée ages=[10,12,13,14,19,20,17]; minors=ages.filter(age => age <18); //"age => age <18" est une fonction de callback qui sera appelée pour chaque élément du tableau //la fonction filter retourne un tableau qui sera composé des éléments pour lequels la fonction sera true


Equivalent à :

ages=[10,12,13,14,19,20,17]; minors=ages.filter( function (age){return age<18;});


Par défaut this est l'objet "global" (window pour les navigateurs). Sinon, il est interne à chaque fonction :

function a(){ monThis=this; function b(){ //le "this" ici est le this de la fonction b. console.log(monThis); //là on récupère le this d'avant } }


SAUF dans le cas des fonctions fléchées.


Portée des variables


Il y a une différence entre un var et un let.
Un var déclaré en dehors des fonctions sera toujours global. Ce n'est pas une condition d'accolades, mais bien de fonctions !
Exemple :

for(var i=0;i<10;i++){ } console.log(i); //même en dehors du for, on verra la valeur de i, donc "10", car i est attaché à window


Pour éviter la problématique du dessus, on peut utiliser let :

for(let j=0;j<10;j++){ } console.log(j); //j est undefined


Tableaux


La façon la plus moderne de faire est let monTableau=[]. Attention typeof monTableau renverra "object", pour savoir si c'est un tableau il faut voir s'il a la propriété length et les méthodes de tableaux comme push.


Pour boucler :

for(let i in monTableau){ console.log(monTableau[i]); }



for(let val of monTableau){ console.log(monTableau); }


For each appelle une fonction pour chaque élément du tableau.

monTableau.forEach((val) => console.log(val));


Equivalent à :

monTableau.forEach(function(element) { console.log(element); });


Encore plus détaillé, équivalent à :

function callBack(element) { console.log(element); } monTableau.forEach(callBack);


Il y a des paramêtres supplémentaires optionels dans la fonction de callback : l'index de l'élément et le tableau utilisé.


function callBack(element, index, tableau) { console.log("Valeur de l'élément n°"+index+" : "+ element); console.log("Pour rappel, on s'occupe du tableau "+tableau); } monTableau.forEach(callBack);


La function map() permet de triturer l'intérieur des tableaux pour retourner un tableau avec les éléments modifieés :

let a=[1,2,3].map(item=>item*10); //a est un tableau qui vaudra 10,20,30


Autre exemple :

tabMonObjet=[1,2,3].map(itemDuTableau => ({sonAttribut:itemDuTableau*10}));


Plus complexe :

const tab=[1,2,3,4]; const mapped=tab.map((item, index) => { return index > 2 ? { //si l'index est sup à 2 on renvoie un objet clef:index }:'' //sinon une chaine vide });


Une autre méthode intéressante, reduce, ne retourne qu'une seule valeur mais permetd e faire des opérations sur les éléments du tableau.

Divers



Au lieu de faire parseInt(maVariable), on peut faire +(maVariable).


Ajax :



function X() { //création d'un objet pour XMLHttpRequest var xhr; try { xhr = new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) { try { xhr = new ActiveXObject('Microsoft.XMLHTTP'); } catch (e2) { try { xhr = new XMLHttpRequest(); } catch (e3) { xhr = false; } } } xhr.onreadystatechange = function() { if(xhr.readyState == 4) { if(xhr.status == 200){ //si on a récupéré quelque chose on l'affiche dans une alert alert(xhr.responseText); } else{ document.ajax.dyn="Error code " + xhr.status; } } }; //demande quelque chose à la page XXX.php xhr.open( "GET", "XXXX.php", true); xhr.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT"); xhr.send(null); }


On peut ajouter un truc en cas de timeout, par exemple :

xhr.timeout = 20000; xhr.ontimeout = function (e) { alert("Délai de réponse dépassé."); };



Manipulation DOM


On peut modifier et même générer des éléments HTML uniquement en Javascript.

Génération d'éléments DOM


Pour créer des éléments y a plusieurs étapes :

1)Création de l'élement : const li = document.createElement('li');
2)Eventuellement, modification de ses attributs (texte affiché, style, nom de la classe, etc) : i.className="choseAFaire";
3)Récupération de son élément parent parent=document.getElementById('parent');
4)Enfin, on l'attache au parent : parent.appendChild(li);

Ci-dessous un système de "todo list" avec des éléments générés dynamiquement :

<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Titre de ma page</title> <style> .xSupression{ font-size:1.2em; cursor: pointer; color:red; } </style> </head> <body> <div class="todo"> <div> <input type="text" class="todo_input"> <ul class="todo_items"> </ul> </div> </div> <script> const todoInput=document.querySelector(".todo_input"); const todoItems=document.querySelector(".todo_items"); todoInput.addEventListener('keyup', evenement =>{ if (evenement.keyCode === 13){ const texteRecupere=evenement.target.value; if(texteRecupere){ const li = document.createElement('li'); li.className="choseAFaire"; const texte=document.createTextNode(texteRecupere); //on créé un texte const span = document.createElement('button'); li.contentEditable=true; span.className="xSupression"; span.innerText=" 🗑️"; span.addEventListener('click',suppressionNote); li.appendChild(texte); li.appendChild(span); todoItems.appendChild(li); todoInput.value=''; } } },false); function suppressionNote(){ elementASupprimer=this.parentNode; elementASupprimer.parentNode.removeChild(elementASupprimer); } </script> <script> window.onload = () =>{ const elem=document.getElementById('element'); //on récupère l'élement elem.style.backgroundColor='yellow'; //on change sa couleur de fond const texte=document.createTextNode('Mon texte'); //on créé un texte const contenuP=document.createElement('p'); //on créé une partie paragraphe contenuP.appendChild(texte); //à ce P, on rajoute le texte en tant qu'enfant elem.appendChild(contenuP); //à l'élément du début, on y met notre Ps } </script> <div id="element">coucou</div> </body> </html>

Bubbling et capturing



Si on met des listener sur des éléments superposés, il est logique qu'ils soient tous déclenchés. Par exemple, si on clique sur p qui est lui même dans une div lui-même dans body, avec chacun un listener, les 3 seront déclenchés. Dans quel ordre ? Bubbling : de bas en haut, celui de p en premier. Capturing : de haut en bas.

Dans l'exemple ci-dessous on a un p dans un article dans une section :

<section onclick="alert(this.tagName);"> <article onclick="alert(this.tagName);"> <p onclick="alert(this.tagName);">Lorem ipsum dolor sit amet.</p> </article> </section> <section class="section2"> <article> <p>Lorem ipsum dolor sit amet.</p> </article> </section> <script> const tabElements = document.querySelectorAll('.section2, .section2 *'); console.log(tabElements); for(let element of tabElements){ element.addEventListener('click', evenement =>{ alert(element.tagName); },true); //true=capturing (de bas en haut), false=bubbling (de haut en bas). } </script>


Local storage


Pour stocker des variables dans le navigateur de l'utilisateur. Il vaut mieux utiliser ça que des cookies, car ces derniers sont envoyés à chaque requête au serveur.


localStorage.setItem("prenom", "Bob"); alert(localStorage.getItem("prenom"));


Récupérer les coordonnés de la souris :


e.clientX avec firefox, event.clientX avec IE.

<script language="JavaScript1.2"> //on assigne la fonction getMouseXY si la souris est bougé sur le document document.onmousemove = getMouseXY; function getMouseXY(e) { mouseX=0; mouseY=0; if(e){ //si argument passé, pas IE mouseX=e.clientX ; mouseY=e.clientY ; } else { //sinon, ie, donc event mouseX=event.clientX ; mouseY=event.clientY ; } document.getElementById('vaisseau').style.top=mouseY ; document.getElementById('vaisseau').style.left=mouseX ; } </script> //l'image qui suit la souris <img src="vaisseau.jpg" id="vaisseau" style="position:absolute;">


document.onclick


"document.onclick = fonction;" Signifie que si on clique, on fera le contenu de fonction.

"document.onclick = fonction();" Signifie que si on clique, on fera la fonction retournée par "fonction".

Pour passer un argument, ça sera :

document.onclick = function (evt) { fonction('arg'); };

document.write


Pose problème quand est appelé une fois la page chargé, il efface tout.
Il existe document.body.insertAdjacentHTML('beforeEnd',"Hello"); comme alternative.

document.body.insertAdjacentHTML

Soustraire une seconde à une date UTC


var e=new Date();
var e = new Date(new Date().getTime() -60000);
document.write(e.toUTCString());


Taille de l'écran


function largeur() { return screen.width } function hauteur() { return screen.height }


Script au démarrage


Pour lancer le script au tout début du chargement de la page, il suffit de l'appeler directement.

Mais souvent on a besoin que la page soit chargée, surtout pour récupérer des éléments :

window.onload = function() { alert(document.getElementById("monElement")); };


Un pop in en html


Mais autant utiliser prompt() en javascript....

#divFondPopIn{ position:fixed; padding:0; margin:0; top:0; left:0; width: 100%; height: 100%; background:#000000; opacity:.70; filter: alpha(opacity=70); } #divCadrePopIn{ background:#FFFFFF; padding:1em; position:fixed; top:40%; left:40%; } #boutonsOkAnnulerPopIn{ float:right; }



<div id="divFondPopIn"></div> <div id="divCadrePopIn"> <form action="action_page.php"> Nom du nouvel écran ?:<br> <input type="text" name="nom_ecran" value="Ecran"><br> <br> <span id="boutonsOkAnnulerPopIn"><input type="submit" value=" Créer l'écran "> <input type="button" value=" Annuler "></span> </form> </div>


Redimensionner des iframes selon leur contenu


Il faut déjà s'assurer que la page qu'on va appeler dans l'iframe se redimensionne correctement lorsqu'on change la taille du navigateur.
Pour ça, ne pas oublier de définir des tailles pour tous les éléments parents (body, html, puis les div jusqu'au div qu'on veut redimensionner, car si on demande "20%" à un div en position relative dont le parent n'a pas de taille définie, il ne saura pas faire).


window.onload = function() { var tableau_iframe_element = document.getElementsByClassName("iframe_element"); for(var i = 0; i < tableau_iframe_element.length; i++) { tableau_iframe_element[i].style.height = (tableau_iframe_element[i].parentNode.offsetHeight * (100-8))/100+'px'; tableau_iframe_element[i].style.width = (tableau_iframe_element[i].parentNode.offsetWidth * (100-1))/100+'px'; tableau_iframe_element[i].contentWindow.location.reload(true); } };


setTimeout avec argument



setTimeout(function() { redimensionner_iframe('iframe'+event.target.id.substring(3)); }, 500);


Envoyer du JSON en Ajax avec Jquery



$.ajax({ type: 'POST', url: 'index.php?sauver_affichage_ecran_num=<?=$page_actuelle->get_id_ecran() ?>', data: {elements_que_l_on_veut_envoyer:mon_super_json}, success: function(msg) { alert(msg); } });


Augmenter la police d'un select



.contactselect option:checked { background: #ffb7b7; /* WebKit/Blink Browsers */ font-size:4em; }


Boucle i avec variable locale



var tabNombres=[293,34,233,423,44,56,32]; var max=0; for(var i=0, tailleTableau=tabNombres.length;i<tailleTableau;i++){ if(max<tabNombres[i]){ max=tabNombres[i]; } } alert(max);



Héritage avec prototypes



// /*prototype Vehicule*/ // function Vehicule(marque, modele, prix) { this.marque = marque; this.modele = modele; this.prix = prix; } Vehicule.prototype.seDeplacer = function () { console.log("Le véhicule "+this.modele+" se déplace"); } Vehicule.prototype.seDecrire = function () { console.log("Je suis un véhicule de marque "+this.marque+" et de modele "+this.modele+", mon prix est de "+this.prix); } // /*prototype Voiture qui hérite de Vehicule*/ // function Voiture(marque, modele, prix) { Vehicule.call(this,marque,modele,prix); //sorte de super pour appeler le constructeur de Vehicule en lui donnant le contexte "this" } Voiture.prototype = new Vehicule(this.marque,this.modele, this.prix); // Voiture.prototype.seDeplacer = function () { console.log("La voiture "+this.modele+" se déplace sur 4 roues."); } /*prototype Moto qui hérite de Vehicule*/ // function Moto(marque, modele, prix) { Vehicule.call(this,marque,modele,prix); } Moto.prototype = new Vehicule(this.marque,this.modele, this.prix); Moto.prototype.seDeplacer = function () { console.log("La moto "+this.modele+" se déplace sur 2 roues."); } /*Création des objets*/ var v1 = new Voiture('Opel','Astra',15000); var v2 = new Voiture('Renaud','Twingo',6000); var m1 = new Moto('Suzuki','SV650',25000); //démarrage v1.seDecrire(); v2.seDecrire(); v2.seDeplacer(); m1.seDecrire(); m1.seDeplacer();



Utiliser "this" dans une fonction de callback



var Monstre={ name:'Crasby', executer:function(callback){ console.log(this.name+" se prépare..."); callback.call(this); //on execute la fonction de callback en lui donnant le this actuel //"apply" fait la même chose, avec un tableau par contre } } function attack(){ console.log(this.name+" attaque !"); } Monstre.executer(attack);


jQuery



<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Titre de ma page</title> <script src="jquery-3.3.1.js"></script> <style> .favcolor { color: royalblue; } .boite, .accordeon-boite { border: 1px solid black; max-width: 30em; } .boite-titre, .accordeon-boite-titre { padding: 0; margin: 0; background-color: mediumblue; color: white; } .boite-contenu, .accordeon-boite-contenu { background-color: royalblue; color: white; margin: 0; display: inline-block; } .accordeon { border: 1px solid red; max-width: 30em; } </style> </head> <body> <div> <p>Bonjour !</p> <p>Hello !</p> </div> <div class="boite"> <h2 class="boite-titre">Titre</h2> <div class="boite-contenu"> <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptatem quas provident repellendus perspiciatis blanditiis omnis harum.</p> </div> </div> <hr> <div class="accordeon"> <div class="accordeon-boite"> <h2 class="accordeon-boite-titre">Titre</h2> <div class="accordeon-boite-contenu"> <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptatem quas provident repellendus perspiciatis blanditiis omnis harum.</p> </div> </div> <div class="accordeon-boite"> <h2 class="accordeon-boite-titre">Titre</h2> <div class="accordeon-boite-contenu"> <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptatem quas provident repellendus perspiciatis blanditiis omnis harum.</p> </div> </div> <div class="accordeon-boite"> <h2 class="accordeon-boite-titre">Titre</h2> <div class="accordeon-boite-contenu"> <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptatem quas provident repellendus perspiciatis blanditiis omnis harum.</p> </div> </div> </div> <hr> <div class="accordeon"> <div class="accordeon-boite"> <h2 class="accordeon-boite-titre">Titre</h2> <div class="accordeon-boite-contenu"> <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptatem quas provident repellendus perspiciatis blanditiis omnis harum.</p> </div> </div> <div class="accordeon-boite"> <h2 class="accordeon-boite-titre">Titre</h2> <div class="accordeon-boite-contenu"> <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptatem quas provident repellendus perspiciatis blanditiis omnis harum.</p> </div> </div> <div class="accordeon-boite"> <h2 class="accordeon-boite-titre">Titre</h2> <div class="accordeon-boite-contenu"> <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptatem quas provident repellendus perspiciatis blanditiis omnis harum.</p> </div> </div> </div> <script> //deux façons d'appeler la fonction jquery : $() et jQuery() $(document).ready(function () { //pareil que windows.onload, on attends le chargement du DOM $('p').addClass('classeAjoutee'); //on ajoute une classe sur les éléments p $('p').css('cursor', 'pointer'); //on modifie la css en mettant le curseur en pointeur fermerTousAccordeons(); }); //si un clique sur un p la couleur change $('p').on('click', function (e) { console.log(this); $(this).toggleClass('favcolor'); //on peut en ajouter une 2e }); //ouvre et ferme une boite $('.boite-titre').on('click', function (e) { console.log($(this).find().tagName); $(this).next(".boite-contenu").slideToggle(); }); $.fn.accordeoniser = function() { this.css( "color", "green" ); return this; } $('.accordeon-boite-titre').on('click', function (e) { //on met le listener sur les titres d'accordéon //alert("Parent :"+$(this).parent().parent().prop('class')); fermerAccordeon(this); //et on ouvre uniquement celui qu'on veut $(this).next().slideDown(); }); //fonction typiquement appelée à l'ouverture de la page pour fermer les accordéons function fermerTousAccordeons(){ $('.accordeon').children("*").each(function () { //alert("Enfant : "+$(this).prop('class')); //pour chaque accordeon-boite-contenu dans l'enfant $(this).children(".accordeon-boite-contenu").each(function () { //alert("Enfant intéressant : "+$(this).prop('class')); $(this).slideUp(); //on le ferme }); }); } //fermer tous les éléments d'un accordéon dont on a cliqué sur le titre d'une boîte function fermerAccordeon(ac){ $(ac).parent().parent().children("*").each(function () { //alert("Enfant : "+$(this).prop('class')); //pour chaque accordeon-boite-contenu dans l'enfant $(this).children(".accordeon-boite-contenu").each(function () { //alert("Enfant intéressant : "+$(this).prop('class')); $(this).slideUp(); //on le ferme }); }); } </script> </body> </html>


Parcourir toutes les propriétés d'un objet



for (var key in items) { console.log(items[key]); }


Closure




En JS on peut mettre une fonction dans une variable : toto=function(){ return 10; }. Pour l'appeler : toto()



function test(param){ var option=param; return function (val){ //une fonction sera retournée, donc on return val*option; } } var mult10=test(10); //mult10 EST une fonction var mult5=test(2); //mult5 EST une fonction var result1 = mult10(2); var result2 = mult5(2); console.log(result1,result2);



TypeScript


Un surensemble syntaxique strict de javascript, sera "transpilé" (traduit avec une phase de contrôle) pour un navigateur. Open source, de Microsoft. Rajoute des notions aux classes ES6 (private/protected, les interfaces, etc).

Fait de l'inférence de type : on peut déclarer une variable sans la typer, mais elle sera typée lors de sa première affectation (on dit aussi "assertion de type").

Types primitifs


let isDone: boolean = false;
let decimal: number=6;
let hex: number=0xf00d;
let binary: number=0b0101;
let octal: number=0o744;
let maChaine: string='valeur de ma chaine';

Le type "any" stocke n'importe quoi.

On peut faire des sortes de cast :

llet uneValeur: any = "C'est une string stocké dans un type any"; //puis, si on a besoin d'une méthode de string, il faut caster : let longueurChaine: number = (<string>uneValeur).length; alert(longueurChaine);



On peut typer les retours des fonctions et donner des valeurs par défaut aux arguments :

function add(x:number, y:number, sertarien?:number, sertarienAvecValeurParDefaut='valParDefaut'):number{ //point d'interrogation pour facultatif, undefined par défaut. On peut spéficier la valeur par défaut. return x+y; }


On peut utiliser les 3 points comme en java (rest parameters) :

function maFonction(a, b, ...plusDArguments) { console.log("a", a); console.log("b", b); console.log("plusDArguments", plusDArguments); } maFonction("un", "deux", "trois", "quatre", "cinq");


Pour les classes :

class Personne{ nom: string; age: number; constructor(nom, age = 0){ //sera appelé quand on fera "new Personne()", ici age a une valeur par défaut this.nom = nom; //setters et getters comme en Java, attention il faut le "this" this.age = age; } seDecrire(){ return "Je m'appelle "+this.nom+" et j'ai "+this.age+" ans."; } } const p=new Personne("Bob"); console.log(p.seDecrire()); /je m'appelle bob et j'ai 0 ans


On peut faire ça aussi :

class Personne{ constructor(private nom: string, private age: number){} seDecrire(){ return "Je m'appelle "+this.nom+" et j'ai "+this.age+" ans."; } } const p=new Personne("Bob",18); console.log(p.seDecrire()); //je m'appelle bob et j'ai 18 ans


Interface :


interface Speak{ say(words: string); } class Dog implements Speak{ say(words: string){ console.log("Wouf"), words); } }


Les getters et les setters sont appelés en modifiant directement la variable :

class Personne{ constructor(private nom: string, private age: number){} get leNom(){ return this.nom; } set leNom(nouveauNom: string){ this.nom = nouveauNom; } } let personne = new Personne("Bob",18); personne.leNom = "John"; alert(personne.leNom);



Angular


Créé par google en 2012, grosse évolution en 2016 qui donnera Angular 2 fonctionnant avec des composants (comme son concurrent react.js), des services, bref, des petites entités qui vont communiquer entre elles.
Les numéros de versions suivent le Semantic Versionning : MAJOR.MINOR.PATCH.

Permet de faire de faire du progressive web app (client entre le léger et lourd), native (exécute les instructions natives du processeur mobile sur lequel l'application tourne, compilée pour marcher sur un type de téléphone spécifique grâce à Ionic Framework, NativeScript, React Native... Ce n'est pas un interpréteur de js, l'application est compilée pour fonctionner sur le téléphone cible), desktop (des applications de bureau Mac, Windows, Linux).

JQuery était une première abstraction (au lieu de devoir écrire plusieurs versions du code javascript, on écrivait une seule en Jquery), Angular est une couche d'abstraction au dessus (mais ça ne veut pas dire qu'il se sert de Jquery ! Il y a quand même un Jquery minimal émulé à l'intérieur) : avant, on chargeait nous même les scripts avec des balises scripts (JQuery, bootstrap, etc) et c'était à nous d'ordonner ça dans la page HTML. A chaque chargement de la page, le navigateur allait chercher les js, il ne fallait pas se tromper de sens dans la déclaration des js... Avec angular, il n'y a plus tout ça, ce sera installé par npm.

En angular on va faire des composants (de préférence réutilisables), définir une nouvelle balise HTML avec des nouveaux attributs pour récupérer des informations de l'extérieur. Angular offre un accès en ligne de commande (cli) qui permet la transpilation du TypeScript, l'internationalisation, l'utilisation de webpack, le guide du style angular, du scaffholding (générer tout le nécessaire -fichiers, arborescence... pour créer un composant et notamment le fichier pour les tests qui nous permettrons de modifier l'application en étant confiant de ne pas introduire de bugs. En théorie, on devrait avoir 2 consoles tout le temps ouvertes, une qui lance la compilation et l'autre qui lance le test. Angular facilite les tests mais aussi l'accessibilité, l'internationalisation.

Pour installer le cli angular : sudo npm install -g @angular/cli
ng new [options] : crée un nouveau répertoire contenant une nouvelle application angular. Par défaut angular créé le projet en utilisant css, si on veut utiliser less ou scss il faudra faire : ng new bookstore --style=scss
ng serve : simule un serveur http, build et lance l'application dans le navigateur, rebuild automatique si des fichiers sont modifiés. ng serve -o --port 4402 pour ouvrir automatiquement dans le navigateur avec un certain port.
ng build : build pour déployer l'application.
ng generate [options] : ou ng g, création d'un nouvel élément angular (component, directive, interface, module). m pour module, exemple pour générer un module books : ng g m books. ng g c books/booksList pour créer un composant booksList dans le module books. ng g s services/auteur pour créer un service auteur.
ng test : exécute la suite de test de l'application. A faire pendant le
ng xi18n : extraction des messages i18n du code source.
ng e2e : tests end to end (fonctionnels, simuler les clics de l'utilisateur sur notre application).

Un grand nombre de fichiers vont être téléchargés/générés.
A la racine :
-package.json : 2 parties, tout ce qui va servir à faire marcher le projet + tout ce qui va servir à le construire.
"scripts" : des raccourcis vers les commandes, par exemple "ng serve" est raccourci en "start". Il va chercher les commandes dans le path (au cas où ng serait installé globalement), s'il ne trouve pas, il ira dans dossier node/module/bin. On peut taper npm run start pour lancer "ng serve". On peut ajouter nous même des commandes (par exemple une commande pour lancer json server).
"dependencies" : tout ce qui va être chargé dans le navigateur.
"devDependencies" : ce dont on a besoin en tant que dev (linter pour controler les sources, jasmine/karma pour les tests.
-angular.json : ne jamais le toucher à la main ! Par exemple, "prefix": "app" sera le prefixe de nos composants (balise "app-root" par ex), "styleext": "scss" indique qu'on voudra utiliser du scss et pas juste du css.
-tslint.json : pour la correction syntaxique de notre code, on peut changer les règles.

Dans le dossier src on aura un fichier index.html avec une balise app-root (ne pas rajouter d'autres balises ! pas besoin de touche ce fichier sauf si on veut rajouter des balises meta etc), tsconfig.spec.json (tout ce qui contient "spec" concerne les tests). "Polyfill.ts" pour émuler les tests. main.ts (le point d'entrée de l'application) ne pas y toucher.

import { enableProdMode } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; //on importe l'objet environnement if (environment.production) { //si on est en production enableProdMode(); //Disable Angular's development mode, which turns off assertions and other checks within the framework. } platformBrowserDynamic().bootstrapModule(AppModule) //on se sert de app.module.ts .catch(err => console.error(err));




Modules angular


Un fichier *.module.ts annoté de @NgModule.
Il y a au minimum un NgModule (root), appelé par convention AppModule. Chaque module aura un ou plusieurs composants (dont un par défaut), des imports qu'il va utiliser et éventuellement des exports qu'il va mettre à disposition.

app.module.ts est là par défaut :

import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; @NgModule({ declarations: [ //les classes de vues appartenant au module (components, directives, pipes), ce qui est en dessous de lui. AppComponent ], imports: [ //les autres modules utilisés par notre module, nécessaires pour le faire marcher, il est en dessous d'eux. BrowserModule ], providers: [], //autrefois pour les services, on ne s'en sert plus vraiment depuis angular6, maintenant par défaut si on créé un service il est à disposition de tout le monde bootstrap: [AppComponent] //le composant qui sera chargé, celui qui sera chargé. il sera forcément dans la liste de déclaration des composants en haut. }) //ATTENTION cette ligne et celle du dessous doivent être accolées. export class AppModule { }



Composant


Les composants sont en général formés de 4 fichiers : html, css, ts (controleur) et spec.ts (tests). Ils sont contenus dans un module.
Annotés de @Component.

Pour créer un : ng g c books/booksList . Il sera ajouté aux déclarations du module (soit le module dans le même dossier, soit le module app-root), mais on voudra sans doute également l'exporter.
Exemple du contrôleur du composant app-root :

import { Component } from '@angular/core'; @Component({ selector: 'app-root', //la balise html pour appeler ce composant templateUrl: './app.component.html', //le fichier html pour définir la structure du composant, on peut aussi mettre le code html directement ici styleUrls: ['./app.component.scss'] //le style à appliquer au html }) export class AppComponent { title = 'bookstore'; //une variable qui sera affichée dans le template html //il y aura un constructeur, des méthodes, etc }


Cyle de vie d'un composant :
Le navigateur requière la page de la route et execute les js qu'il y aura sur cette page. Il travaille sur le "shadowdom", un dom qui sera chargé en arrière plan puis qui remplacera le vrai dom (sinon, on verrait les composants se charger petits à petits).
Quand Angular charge le module principal, il va faire un dictionnaire de tous les modules qui existent. Quand il analysera la page html, il va détecter les balises et les remplacer avec les modules correspondants dans son dictionnaire.

->Angular créé le composant.
->Construit et insère le code html dans le DOM.
->Gère récursivement la création et le rendu des composants.
->Gère le changement des propriétés.
->Détruit les composants.

Des "fonctions" comme ngOnInit() (appelé lors de l'initialisation, avant même que le composant soit dans le shadowdom), ngOnDestroy() (appelé à la destruction de l'élément) sont dans les composants.

Le fichier browserlist pour définir les navigateurs que l'on veut viser.
Un dossier styles.scss : pour appliquer des styles communs à tous les composants qu'on va créer.
Le dossier app contiendra notre code.
environnement : comment on va lancer le serveur et quelles variables vont être différentes entre le mode production et le mode local. Par exemple, si on a des urls différentes entre le test en local pour accéder à une api et une vraie url vers une vraie api en prod :

export const environment = { production: true, BASE_URL:'http://localhost:3000/books' };

Autre exemple, en prod on veut afficher 5 articles par pages mais en debug on voudra 20 articles par pages.
le dossier assets ne contient rien par défaut (sauf un fichier .gitkeep vide, il y a un fichier pour s'assurer que git copie le dossier -par défaut il ne prend pas les dossiers vide sinon). Ce dossier assets contiendra tous les médias (il convertira les images les plus petites en base 64 et les incluera dans le code).
Ne pas modifier l'intérieur du dossier "node-module".

Nous n'allons travailler que dans "app". Lorsqu'on créé un composant, il y aura automatiquement 4 fichiers. nomdelappli.typedecomposant.extension, par exemple app.component.html ou app.component.scss.


Le css et le html est souvent inclut dans le composant, décrit directement dans le js.

Pour intégrer un composant dans la page :
-Eventuellement, créer son module : ng g m books.
-Créer le composant : ng g c books/booksList
-Dans notre module, le composant doit avoir été ajouté, mais pas exporté. Rajouter l'annotation @NgModule, rajouter notre ligne pour exporter le composant : exports:[ BooksListComponent]
-A la racine, dans app.module.ts, ajouter les imports dans @NgModule (on aura donc imports: [BrowserModule,BooksModule)
-Intégrer la balise dans app.component.html

Exemple de books-list.component.ts :

import { Component, OnInit } from '@angular/core'; import { Book } from 'src/app/models/book.model'; @Component({ selector: 'app-books-list', templateUrl: './books-list.component.html', styleUrls: ['./books-list.component.scss'] }) export class BooksListComponent implements OnInit { monLivre: Book = { //ici on instancie un livre, juste pour l'exemple il faudrait utiliser un service plutôt id: 1, titre: 'Germinal', nomAuteur: 'Zola', description: 'Livre sur les grèves dans les mines' } constructor() { } ngOnInit() { } }



Modèles


Pour les classes modèles, il faut le faire manuellement (un dossier "models" dans "app", avec un fichier "book.model.ts" par exemple) :

export class Book { id: number; titre: string; nomAuteur: string; description: string; }


Services


Une class exportée avec l'annotation @Injectable. Grâce à l'injection de dépendance, le service sera accessible partout. On l'injectera dans le constructeur de la classe où on en aura besoin.
Le service sera un Singleton.

Exemple de service :

import { Injectable } from '@angular/core'; import { Book } from '../models/book.model'; import { HttpClient } from '@angular/common/http'; import { Observable, from } from 'rxjs'; import {environment} from '../../environments/environment'; const BASE_URL: string=environment.BASE_URL; @Injectable({ providedIn: 'root' }) export class BookService { //va chercher un httpclient et retourner un observable de tableau de Book constructor(private http: HttpClient) { console.log("Nous sommes dans le constructeur de BookService"); } putBooks(livre: Book){ return this.http.post<Book>(`${BASE_URL}/books/`,livre); } getBook(numLivre){ console.log("Ici le book.service, on va chercher",BASE_URL+"/"+numLivre); return this.http.get<Book>(`${BASE_URL}/books/${numLivre}`); } getBooks(){ return this.http.get<Book[]>(`${BASE_URL}/books/`); } }


Puis dans la classe du livre (books-list.component.ts), injection et utilisation du service :

import { Component, OnInit } from '@angular/core'; import { BookService } from 'src/app/services/book.service'; //on a besoin du service pour récupérer le livre import { Book } from 'src/app/models/book.model'; //il faut connaitre le model @Component({ selector: 'app-books-list', templateUrl: './books-list.component.html', styleUrls: ['./books-list.component.scss'] }) export class BooksListComponent implements OnInit { livreAAfficher: Book ; //la variable est en attribut pour pouvoir s'en servir en dehors du constructeur, l'afficher constructor(private bookService: BookService) { //ici le type BookService sera détecté comme injectable et sera INJECTE this.livreAAfficher= bookService.getBook(1); //on récupère le livre 1 console.log("Nous sommes dans le constructor de BooksListComponent"); } ngOnInit() { console.log("Nous sommes dans le ngOnInit de BooksListComponent"); } }



Directives


Se met dans le template html.
Directives structurelles : ngIf va instancier selon une condition, ngFor va instancier tant qu'il y a dans un tableau, ngShow va montrer des choses si une valeur mise, ngSwitch pour afficher des choses selon que ou selon que.
Exemple (si article vaut null ou undefined ou false, il ne va pas afficher cette div) : <div *ngIf="article">{{article.title}}</div>
Pour afficher un tableau :

<ul> <li *ngFor="let article of articles">{{article.title}}</li> //va générer autant de lignes que d'articles, on peut rajouter : "let i= index" après "let article of articles" </ul>









Directives attributes : Pour définir les attributs personnalisées, par exemple pour mettre un texte en majuscule on pourrait faire <p appUppercase>texte qu'on veut en maj</p>

Il faudra la directive quelque part, dans un dossier Directives dans notre élément par exemple :

import { Directive,ElementRef } from '@angular/core'; @Directive({ selector: '[appUppercase]' }) export class UppercaseDirective { constructor(el: ElementRef){ el.nativeElement.style.textTransform = 'uppercase'; } }


On pourrait faire une directive pour du drag and drop par exemple.

Pour créer une directive : ng generate directive shared/Bold. Les fichiers bold.directive.ts et bold.directive.spec.ts vont se créer dans le dossier app/shared.

Pour pouvoir utiliser notre directive dans toute notre appli, on peut la mettre dans un module "Shared" par exemple. Il faudra y déclarer notre directive et l'exporter, puis importer ce shared module partout où on voudra l'utiliser :

import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { UppercaseDirective } from './uppercase.directive'; @NgModule({ imports: [ CommonModule //ce module est important, il y aura les directives angular ], declarations: [ UppercaseDirective //on déclare notre directive dans ce module ], exports: [ UppercaseDirective //on l'exporte ] }) export class SharedModule { }


Data binding/Event binding


Notation moustache pour afficher un objet/une variable : {{monObjet}}

Sauf si c'est dans un composant :

<appMoncomposant [attributAuquelOnVeutPasserQuelqueChose]="instanceDunObjet" attributString="uneString" (action)="uneAction()" (action2)="uneActionAvecParam($event)"></appMoncomposant>


Dans le composant il faudra :

@input() attributAuquelOnVeutPasserQuelqueChose: Type;


Exemple pour détecter un clic :

//dans la vue, moncomposant.component.html <div (click)="methodeAAppelerSiClick()"> //click est un event préprogrammé dans angular, se déclenche si clic de souris sur l'élément Cliquez-moi ! </div>



//dans le contrôleur, moncomposant.component.ts import { Component, OnInit, Input, Output,EventEmitter } from '@angular/core'; import { Book } from 'src/app/models/book.model'; @Component({ selector: 'app-book', templateUrl: './book.component.html', styleUrls: ['./book.component.scss'] }) export class BookComponent { methodeAAppelerSiClick(){ console.log("Un clic a été fait !"); } }


On peut générer nous même des events avec un objet "EventEmitter". Exemple typique : un composant enfant détecte un évènement clic -> la fonction associée se lance et demande à un objet EventEmitter d'envoyer un évènement -> cet évènement est détecté chez un composant parent->la fonction associée se lance chez le composant parent.


//Dans la vue du composant enfant <div (click)="monActionATransferer()"> <p appUppercase> Titre : {{livre.titre}} </p> </div>


Dans le contrôleur du composant enfant :

import { Component, OnInit, Input, Output,EventEmitter } from '@angular/core'; import { Book } from 'src/app/models/book.model'; @Component({ selector: 'app-book', templateUrl: './book.component.html', styleUrls: ['./book.component.scss'] }) export class BookComponent { @Input() livre: Book; @Output() monEventEmit=new EventEmitter(); //c'est lui qui émettra vers le parent monActionATransferer(){ console.log("transfert de l'action !"); this.monEventEmit.emit(this.livre); //ici l'évènement est émit vers le parent } }


Dans la vue du composant parent :

<div> Livre cliqué : {{livreSelectionne.titre}} <app-book [livre]="livreAAfficher" (monEventEmit)="monAction($event)" *ngFor="let livreAAfficher of tabLivres" class="book"></app-book> </div>


Dans le contrôleur du composant parent :

import { Component, OnInit } from '@angular/core'; import { BookService } from 'src/app/services/book.service'; import { Book } from 'src/app/models/book.model'; @Component({ selector: 'app-books-list', templateUrl: './books-list.component.html', styleUrls: ['./books-list.component.scss'] }) export class BooksListComponent { tabLivres: Book[] ; livreSelectionne: Book; constructor(private bookService: BookService) { this.tabLivres= bookService.getBooks(); //récupère un tableau de livres console.log("Nous sommes dans le constructor de BooksListComponent"); } monAction(livreTransfere:Book){ console.log(livreTransfere); console.log(livreTransfere.titre); this.livreSelectionne=livreTransfere; } }


JSON


json-server permet de simuler un serveur REST.


Pour interroger notre serveur REST, on va utiliser un objet de type HttpClient. Attention, il ne nous retourne pas directement les objets mais un objet Observable, auquel devra souscrire quelqu'un.


Dans le service on aura :

getMonBelObservable(){ return this.http.get<Book[]>(BASE_URL); }


Dans le ts du composant :

bookService.getMonBelObservable().subscribe( //on s'abonne à l'observable (books:Book[]) => {this.vraiTabLivres = books}, //on récupère les Objets, ici, des livres, et on en fait ce qu'on en veux (error) => this.errorMessage = <any>error); //on peut afficher des erreurs



Si on veut juste les lister dans un tableau, on peut utiliser async qui fera la subscription :
*ngFor="let livre of tabDeMesObservables|async"

Routes



Fonctionne avec un module interne à Angular, RouterModule, à importer là où on veut l'utiliser.
Lors de l'importation il faudra utiliser sa méthode forRoot() pour lui donner un tableau de routes. Pour chaque route, on aura path/composant, ou path/redirect...

Les composants qui seront chargés selon la route seront affichés sous une balise <router-outlet></router-outlet>.


import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { HttpClientModule } from '@angular/common/http'; //rajout perso import { RouterModule, Routes } from '@angular/router'; //ajout, pour les routes import { AppComponent } from './app.component'; import { BooksModule } from './books/books.module'; import { SharedModule } from './shared/shared.module'; import { BookDetailsComponent } from './books/book-details/book-details.component'; import { BooksListComponent } from './books/books-list/books-list.component'; const appRoutes: Routes = [ //on peut aussi mettre les routes dans un fichier à part, par exemple app.routes.ts { path: 'livres/:id', component: BookDetailsComponent }, //puis faire import {appRoutes} from './app.routes.ts' { path: 'livres', component: BooksListComponent }, { path: '', redirectTo:'livres',pathMatch:'full' } //exemple de redirection ]; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, BooksModule SharedModule, HttpClientModule, RouterModule.forRoot( //ne pas oublier d'importer notre module ! appRoutes, //ici le tableau contenant les routes qu'on veut charger { enableTracing: true } // affiche des messages d'infos concernant les routes ) ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }


Pour faire nos liens, on utilisera la directive "routerLink" et non plus href. Par exemple <a routerLink="/livres">Retour</a>.
Les liens dynamiques se présenteront comme ça (notez les crochets pour indiquer qu'il s'agit d'un objet, en l’occurrence un tableau composé d'une string et d'un livre) : <a [routerLink]="['/livres',livre?.id]">Voir les détails du livre</a>

On peut utiliser la directive "RouterLinkActive" pour charger une classe sur le lien qui correspond à la route sur laquelle on se trouve (imaginer une barre de menu, le lien correspondant à la page sur laquelle on se trouve serait coloré).

Il existe aussi une façon d'appliquer une classe seulement si une condition est remplie. Ci-dessous, les liens prendront une couleur selon la valeur d'une variable :

<a [ngCLass]="{nomDeLaClasse: locale === 'en' }"> <a [ngCLass]="{nomDeLaClasse: locale === 'fr' }">


Pour récupérer un paramètre, il y a 2 façons.
Snapshot, la plus simple :

import { Component, OnInit, OnDestroy } from '@angular/core'; import { Router, ActivatedRoute } from '@angular/router'; @Component({ selector: 'app-book-details', templateUrl: './book-details.component.html', styleUrls: ['./book-details.component.scss'] }) export class BookDetailsComponent implements OnInit { constructor( private route: ActivatedRoute, //la route actuelle sera injectée ) { } ngOnInit() { const numLivreAAfficher=this.route.snapshot.params.id; //on récupère le paramètre (la route sera "/book/:id", noter les 2 points) console.log("FAUT AFFICHER LE LIVRE N°",numLivreAAfficher); }





Exemple avec récupération d'un livre sur monsite.com/livres/2 :

import { Component, OnInit, OnDestroy } from '@angular/core'; import { Router, ActivatedRoute } from '@angular/router'; import { Subscription } from 'rxjs'; import { Book } from 'src/app/models/book.model'; import { BookService } from 'src/app/services/book.service'; @Component({ selector: 'app-book-details', templateUrl: './book-details.component.html', styleUrls: ['./book-details.component.scss'] }) export class BookDetailsComponent implements OnInit, OnDestroy { subscription: Subscription=null; //on doit avoir la subscription en attribut pour pouvoir se désabonner par la suite private livreAAfficher: Book; //sur la page on affichera {{livreAAfficher.description}} par exemple private errorMessage: string=""; //pour les erreurs si rien n'est renvoyé constructor( private route: ActivatedRoute, private bookService: BookService ) { } ngOnInit() { const numLivreAAfficher=this.route.snapshot.params.id; console.log("FAUT AFFICHER ",numLivreAAfficher); this.subscription=this.bookService.getBook(numLivreAAfficher).subscribe( (book:Book) => {this.livreAAfficher = book; console.log("On a recup",book);}, //une fois qu'on a récup le book on l'affiche (error) => this.errorMessage = <any>error); } ngOnDestroy() { this.subscription.unsubscribe; //il faut absolument unsuscribe sinon des observateurs vont s'accumuler chaque fois qu'on voudra les détails d'un livre ! } }


Sous-routes



Pour que chaque composant soit isolé, il s'occupera lui même des routes le concernant.
On aura le module de l'app avec sa balise router-outlet qui contiendra un module admin apr exemple avec sa propre balise router outlet. Il aura un tableau de route avec la route principale qui contiendra la ou les sous-routes.

Exemple de module enfant :

import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterModule, Routes } from '@angular/router'; //ajout, pour les routes //a été ajouté automatiquement lors de la génération du composant //en général on saute une ligne pour lister nos propres composants import { BooksListComponent } from './books-list/books-list.component'; import { SharedModule } from '../shared/shared.module'; import { BookComponent } from './books-list/book/book.component'; import { BookDetailsComponent } from './book-details/book-details.component'; import { BooksComponent } from './books.component'; import { bookRoutes } from './books.routes'; @NgModule({ imports: [ CommonModule, SharedModule, RouterModule, RouterModule.forChild( //ce n'est plus "forRoot" bookRoutes //un tableau de routes ) ], declarations: [ BooksListComponent, BookComponent, BookDetailsComponent, BooksComponent ], exports:[ //rajouté par moi BooksListComponent //faut lui dire ce qu'on veut mettre à disposition ] }) export class BooksModule {}



import { Routes } from "@angular/router"; import { BooksListComponent } from "./books-list/books-list.component"; import { BookDetailsComponent } from "./book-details/book-details.component"; import { BooksComponent } from "./books.component"; export const bookRoutes: Routes = [ { path: 'livres', component: BooksComponent, children: [ //la sous-route { path: ':id', component: BookDetailsComponent }, { path: '', component: BooksListComponent } ] } ];


Lazy loading


Les composants listés dans le module racine seront TOUS chargés au lancement de l'application. Elle sera donc plus rapide lors de son utilisation au détriment d'un temps de chargement au premier lancement.


import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { HttpClientModule } from '@angular/common/http'; //rajout perso import { RouterModule, Routes } from '@angular/router'; //ajout, pour les routes import { AppComponent } from './app.component'; import { BooksModule } from './books/books.module'; import { SharedModule } from './shared/shared.module'; import { BooksListComponent } from './books/books-list/books-list.component'; import { AdminModule } from './admin/admin.module'; const appRoutes: Routes = [ //on peut aussi mettre les routes dans un fichier à part, par exemple app.routes.ts { path: '', redirectTo:'livres',pathMatch:'full' }, { path: 'admin', loadChildren:'./admin/admin.module#AdminModule'} //si le chemin correspond à "admin", le module sera chargé ]; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, BooksModule, //à parti de là, rajouts perso SharedModule, HttpClientModule, // AdminModule, //il faut pas mettre l'AdminModule ici pour qu'on puisse le lazyloader RouterModule.forRoot( appRoutes, { enableTracing: false } ) ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }


Du côté de ce module admin :

import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { AdminComponent } from './admin.component'; import { NewBookComponent } from './new-book/new-book.component'; import { NewAuthorComponent } from './new-author/new-author.component'; import { RouterModule } from '@angular/router'; import { adminRoutes } from './admin.routes'; @NgModule({ imports: [ CommonModule, RouterModule.forChild( adminRoutes ) ], declarations: [AdminComponent, NewBookComponent, NewAuthorComponent] }) export class AdminModule { }


Et les routes du module admin :

import { Routes } from "@angular/router"; import { AdminComponent } from "./admin.component"; export const adminRoutes: Routes = [ { path: '', component: AdminComponent, children: [ { path: '', component: AdminComponent } ] } ];


Formulaires


Deux façons de faire des formulaires : template driven et reactive form.

Exemple template driven (pensez à importer FormsModule et ReactiveFormsModule dans votre app.module !) :

<div *ngIf="messageDErreur">{{messageDErreur}}</div> <form (ngSubmit)="envoyerForm()" #monFormulaire="ngForm"> <p>Titre : <input name="titre" [(ngModel)]="livreACreer.titre" required></p> <p>Nom de l'auteur : <input name="nomAuteur" [(ngModel)]="livreACreer.nomAuteur" required></p> <p>Description : <textarea name="description" [(ngModel)]="livreACreer.description" required></textarea></p> <p><button type="submit">Créer le nouveau livre</button></p> </form>



import { Component, OnInit } from '@angular/core'; import { Book } from 'src/app/models/book.model'; import { BookService } from 'src/app/services/book.service'; import { Router } from '@angular/router'; import { Subscription } from 'rxjs'; @Component({ selector: 'app-new-book', templateUrl: './new-book.component.html', styleUrls: ['./new-book.component.scss'] }) export class NewBookComponent implements OnInit { private livreACreer: Book; subscription: Subscription=null; messageDErreur: string=""; constructor(private bookService: BookService, private routeur:Router) { this.livreACreer=new Book(); } ngOnInit() { } envoyerForm(){ //il faudrait griser le bouton dès l'envoi pour éviter que si le serveur met 10s à répondre, on puisse cliquer 3 fois sur le bouton et envoyer 3 livres console.log("envoi du formulaire, il contient le nouveau livre ",this.livreACreer); this.subscription=this.bookService.putBooks(this.livreACreer).subscribe( //ATTENTION IL FAUT ABSOLUMENT SUBSCRIBE POUR POUVOIR POST () => {this.routeur.navigateByUrl("/")}, //on redirige vu qu'on a eu un succès (error)=>{this.messageDErreur=error.message;}); //ya eu une erreur, on la met dans notre attribut } }


Exemple reactive form :

<div *ngIf="messageDErreur">{{messageDErreur}}</div> <Form [formGroup]="monFormulairePourAuteur" (ngSubmit)="envoiFormulaire()"> <input placeholder="Name" formControlName="nomauteur"> <p><button type="submit">Créer le nouvel auteur</button></p> </Form>



import { Component, OnInit } from '@angular/core'; import { FormGroup, FormControl } from '@angular/forms'; import { Auteur } from 'src/app/models/auteur.model'; import { Subscription } from 'rxjs'; import { AuteurService } from 'src/app/services/auteur.service'; import { Router } from '@angular/router'; @Component({ selector: 'app-new-author', templateUrl: './new-author.component.html', styleUrls: ['./new-author.component.scss'] }) export class NewAuthorComponent implements OnInit { private subscription: Subscription=null; private messageDErreur=""; private monFormulairePourAuteur: FormGroup = new FormGroup({ //Construction du formulaire ! nomauteur: new FormControl() } ); constructor(private auteurService: AuteurService, private routeur:Router) { } ngOnInit() { } envoiFormulaire(){ console.log("Il faut envoyer le formulaire !"); const formModel=this.monFormulairePourAuteur.value; const auteurACreer:Auteur = { id:0, //comme notre modèle a une ID, on est forcé de la spécifier. En la mettant à 0 le serveur va l'incrémenter. On pourrait faire new Auteur(nomauteur) aussi... nomAuteur: formModel.nomauteur as string }; this.subscription=this.auteurService.putAuteur(auteurACreer).subscribe( //ATTENTION IL FAUT ABSOLUMENT SUBSCRIBE POUR POUVOIR POST (auteur: Auteur) => { //c'est l'auteur renvoyé par le serveur une fois qu'il est créé, il aura un id this.routeur.navigateByUrl("/") console.log('Auteur créé : ', auteur); //on le display pour le fun }, (error)=>{this.messageDErreur=error.message;}); } }


Validation


Ici, on a un composant affiche-erreur-valid-forms qui affiche les messages d'erreurs.

Le formulaire à remplir :

<div *ngIf="messageDErreur">{{messageDErreur}}</div> <form (ngSubmit)="envoyerForm()" #monFormulaire="ngForm"> <p>Titre : <input name="titre" [(ngModel)]="livreACreer.titre" required minlength="4" appForbiddenName="bob" #titre="ngModel"></p> <app-affiche-erreur-valid-forms [objetATester]="titre"></app-affiche-erreur-valid-forms> <!-- on met le champ "desc" dans l'objet objetATester. --> <p>Nom de l'auteur : <input name="nomAuteur" [(ngModel)]="livreACreer.nomAuteur" required minlength="4"#nomauteur="ngModel"></p> <app-affiche-erreur-valid-forms [objetATester]="nomauteur"></app-affiche-erreur-valid-forms> <p>Description : <textarea name="description" [(ngModel)]="livreACreer.description" required minlength="4" #desc="ngModel"></textarea></p> <app-affiche-erreur-valid-forms [objetATester]="desc"></app-affiche-erreur-valid-forms> <p> <button type="submit" [disabled]="!monFormulaire.form.valid">Créer le nouveau livre</button></p> <!-- par contre ya une verif à faire dans le contrôleur derrière car l'attribut disabled peut être enlevé dans l'inspecteur--> </form>


Le composant affiche-erreur-valid-forms :

<div *ngIf="objetATester.invalid && (objetATester.dirty || objetATester.touched)" class="alert alert-danger"> <div *ngIf="objetATester.errors.required"> Champ requis. </div> <div *ngIf="objetATester.errors.minlength"> Longueur incorrecte. </div> <div *ngIf="objetATester.errors.forbiddenName"> Interdit de rentrer cette valeur. </div>


Transclusion


Les composants se présentent avec une balise app-moncomposant ouvrante et une fermante. Entre les 2 on peut mettre du texte qui sera remplacé par le javascript. Mais on peut aussi utiliser une balise ng-content pour y mettre des bouts d'autres composants.


Build


ng build et ng build --prod.

Just In Time : transpilateur lancé à chaque execution du code. Les librairies sont rechargées à chaque utilisation.
Ahead of time : une phase de build qui va inclure les librairies etc. Plus légér à charger et rapide à exécuter.

Des fichiers js seront générés.
main : notre code.
polyfill : emulation navigateurs anciens.
Les modules lazy loadés seront dans des js à part.

Les fichiers map permettent de faire la correspondance avec les lignes non-minifiées et de débugger.

On peut tester en installatn un serveur http : npm i -h http-server

Traduction


Angular i18n : statique (il faudra générer un site par langue !), mais du coup très rapide. Ne peut être utilisé que dans le HTML.

Préférer la librairie NGX-Translate dont les traductions peuvent être substituées dynamiquement dans l'application. Peut être utilisé aussi au niveau des contrôleurs. Pour installer : npm i -S @ngx-translate/core et npm install @ngx-translate/http-loader --save

Mettre un dossier i18n dans assets, avec un fichier json par langage.

Exemple avec "en.json" :

{ "APP":{ "BOOKS_LINK":"Books zone", "ADMIN_LINK":"Admin zone" }, "BOOK":{ "BIENVENUE":"Welcome in our bookstore!", "LIST":{ "TITRE":"Book title:", "AUTEUR":"Author :", "DETAILS":"Show book details" }, "DETAILS":{ } }, "ADMIN":{ } }


Puis un fichier :

import {TranslateLoader } from '@ngx-translate/core'; import {TranslateHttpLoader } from '@ngx-translate/http-loader'; import { HttpClient} from '@angular/common/http'; export function createTranslateLoader(http: HttpClient){ return new TranslateHttpLoader(http,'./assets/','.json'); } export const appTranslate = { loader: { provide: TranslateLoader, useFactory: (createTranslateLoader), deps: [HttpClient] } };


Et enfin, dans le app.module.ts :

import { Component, OnInit } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { environment} from '../environments/environment'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent implements OnInit{ title = 'bookstore'; langage=environment.langageParDefaut; constructor(private traduction:TranslateService){} ngOnInit(){ this.traduction.setDefaultLang(this.langage); this.traduction.use(this.langage); } changerLangue(){ console.log(this.langage); this.traduction.use(this.langage); } }



Platform-server


Angular a un système pour générer des pages html statiques (server side rendering) au lieu de notre interface en JS. Utilise pour les moteurs de rechere, les preview facebook, etc.


Tests


Avec Jasmine (exécute les tests) et Karma (test runner, enchaine les tests en lançant Jasmine) + Testbed (créé un environnement pour les tests).

Tests principaux dans app.component.spec.ts :


import { TestBed, async } from '@angular/core/testing'; import { AppComponent } from './app.component'; import { NO_ERRORS_SCHEMA } from '@angular/core'; describe('AppComponent', () => { beforeEach(async(() => { //s'éxécutera avant chaques tests, on peut faire "beforeAll" si on veut que que quelque chose soit fait avant tous les tests TestBed.configureTestingModule({ declarations: [ AppComponent ], schemas:[ //on créé ça pour qu'il n'essaie pas de tester les composants du dessous NO_ERRORS_SCHEMA //on veut pas qu'il essaie d'instancier tous les composants en dessous //a mettre pour chaque tests concernant des composants ayant eux-même des composants //pour éviter cascade de test ] }).compileComponents(); })); it('should create the app', () => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.debugElement.componentInstance; expect(app).toBeTruthy(); }); it(`should have as title 'bookstore'`, () => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.debugElement.componentInstance; //ici on fonctionne sur le componentInstance, càd une fois instanciée mais pas forcément dans le dom expect(app.title).toEqual('bookstore'); }); it('should render title in a h1 tag', () => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); //c'est seulement une fois que le DOM est affiché... const compiled = fixture.debugElement.nativeElement; //...qu'on peut tester des balises HTML normales genre H1 expect(compiled.querySelector('h1').textContent).toContain('Welcome to bookstore!'); }); it('should render two routerlinks tags', () => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); const compiled = fixture.debugElement.nativeElement; expect(compiled.querySelectorAll('a[routerLink]').length).toEqual(2); //il devrait y avoir 2 balises routerLink sur la page principale (index et accès admin) }); });


Ci-dessous on créé un faux service HttpClient pour les tests :

import { TestBed } from '@angular/core/testing'; import { BookService } from './book.service'; import { of } from 'rxjs'; import { HttpClient } from '@angular/common/http'; describe('BookService', () => { //il faut simuler le HttpClient et ses méthodes //on pourrait mettre un vrai HttpClient qui ferait des vrais requêtes mais le test ne serait plus isolé const mockHttpClient = jasmine.createSpyObj('HttpClient', { get: of([{ //on créé une fausse fonction get qui renverra un faux observale de tableau de Book id:1, titre:'Title1', nomauteur:'Auteur', description:'blah'}]) }); beforeEach(() => TestBed.configureTestingModule({ //dans notre environnement de test, on va utiliser des choses providers: [ {provide: HttpClient, useValue: mockHttpClient}] //on dit qu'on va utiliser (provide) notre faux http client "mockHttpClient" (on pourrait utiliser le vrai mais cf plus haut) en tant que (usevalue) HttpClient })); it('should be created', () => { const service: BookService = TestBed.get(BookService); expect(service).toBeTruthy(); }); });



Ci-dessous on simule un attribut qui est undefined :

import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { BookComponent } from './book.component'; describe('BookComponent', () => { let component: BookComponent; let fixture: ComponentFixture<BookComponent>; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ BookComponent ] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(BookComponent); component = fixture.componentInstance; component.livre= { //pour le test on va générer cet attribut id:1, titre:'titre test', nomAuteur:'auteur test', description:'description test' } fixture.detectChanges(); //laisser ça à la fin }); it('should create', () => { expect(component).toBeTruthy(); }); });



Pour une directive :

import { UppercaseDirective } from './uppercase.directive'; import { ElementRef } from '@angular/core'; describe('UppercaseDirective', () => { it('should create an instance', () => { const el:ElementRef = new ElementRef({ //on fait un élément sur lequel il peut travailler style:{ textTransform:"" } }); //on doit crééer l'élément ici const directive = new UppercaseDirective(el); //il attend un argument donc il faut lui donner un élément expect(directive).toBeTruthy(); }); });



Pour éviter d'avoir des imports dans chaque tests, on peut faire un module "passe-plat" :

import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { RouterTestingModule } from '@angular/router/testing'; @NgModule({ imports: [ CommonModule, FormsModule, RouterTestingModule, HttpClientTestingModule ], exports:[ FormsModule, RouterTestingModule, HttpClientTestingModule ], declarations: [] }) export class TestsModule { }


Angular Route Guard


Bon tuto

Le principe : sur la route, on va appeler un service "guard" qui va nous renvoyer true ou false. Il va lui même vérifier auprès d'un service "authentification" qu'on a les droits.

On commence par créer le service autentification : ng g s agent/auth. Ci-dessous son auth.service.ts :

import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class AuthService { private authenticated=false; constructor() { } isAuthenticated(){ return this.authenticated; } setAuthenticated(authenticated:boolean){ this.authenticated=authenticated; } }


Ensuite on va créer le service qui va renvoyer le booléen : ng g s agent/loginGuard. Dans son login-guard.service.ts :

import { Injectable } from '@angular/core'; import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router, Route } from '@angular/router'; import { Observable } from 'rxjs'; import { AuthService } from './auth.service'; @Injectable({ providedIn: 'root' }) export class LoginGuardService implements CanActivate { constructor(private _authService: AuthService, private _router: Router) { } canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean { if (this._authService.isAuthenticated()) { return true; } // navigate to login page this._router.navigate(['/login']); // you can save redirect url so after authing we can move them back to the page they requested return false; } }


Puis, une page de login et son controleur ng g c agentLogin :

<form (ngSubmit)="envoyerForm()" #monFormulaire="ngForm"> <p>Identifiant : <input name="login" [(ngModel)]="login" required></p> <p>Mot de passe : <input name="password" type="password" [(ngModel)]="password" required></p> <p><button type="submit">Connexion</button></p> </form>



import { Component, OnInit } from '@angular/core'; import {Router} from "@angular/router" import { AuthService } from '../auth.service'; @Component({ selector: 'app-login', templateUrl: './agent-login.component.html', styleUrls: ['./agent-login.component.css'] }) export class AgentLoginComponent implements OnInit { private messageDErreur: string; private login: string; private password: string; constructor(private authService:AuthService, private router: Router) { } ngOnInit() { } envoyerForm(){ if(this.login=="agent" && this.password=="password"){ console.log("Redirection vers la page de l'agent..."); this.authService.setAuthenticated(true); this.router.navigate(['/agent/login']) }else{ console.log("Mauvais login ou mot de passe !"); } } }


Enfin, dans les routes, il suffit de mettre notre service :

{ path: 'login', component: AgentLoginComponent}, { path: 'customers', component: AgentCustomersManagComponent, canActivate: [LoginGuardService]}, { path: '**', component: AgentLoginComponent, canActivate: [LoginGuardService]},



Debug


Se fait dans chrome, F12, debug. Dossier webpack/./src/.

Node.js/npm


On en aura besoin pour utiliser nos outils Angular.
Le gestionnaire de paquets de Node est npm. Il existe aussi Yarn, une alternative. Les 2 travaillent sur le fichier package.json.
Pour installer sous Ubuntu : sudo apt-get install nodejs npm puis sudo npm i -g n, enfin n latest pour se mettre sur la dernière version.

Font Awesome


La librarie fortawesome permet d'importer une seule icône de la librairie font awesome : https://www.npmjs.com/package/@fortawesome/angular-fontawesome


ReactiveX


Reactive programming.
Asynchrone : on lance une tâche qui va aboutir un jour. On ne l'attend même pas. Par exemple onreadystatechange pour ajax, ou le mousemove... on demande à être notifié quand un évènement arrivera.
Synchrone : on appelle une fonction et on ATTEND son retour, c'est bloquant.
Promesses : pour éviter le "callback hell".

ReactiveX : permet de regarder un flux d'évènement. ReactiveX c'est le pattern Observer. Par exemple un élément "photo_profil" observe si l'user est connecté et affichera son avatar si c'est le cas.
Le type principal de RxJS est Observable (qui va observer des flux), avec les type Observers, Subject... qui gravitent autours.

Observable : représente la source d'une collection de future valeurs ou d'évènement.

import { Observable } from 'rxjs'; //Observable est une classe const source=Observable.create(observer => { //on créé une source d'évènement observer.next(33); //lorsqu'il va s'inscrire, l'observeur va recevoir "33". ici pas très intéressant avec "33" mais une source d'évènement pourrait être du du REST par exemple. }); const subscription=source.subscribe(val=>{ console.log("Observer1 :"+val); });


Une source d'véènement sur lesquels on va greffer des opérateur qui vont modifier ou filtrer les evénements qui arrivent et donner (si applicable) les éléments filtrés à l'abonné.

On peut chainer avec pipe :

const inputObservable = Observable.of(1,2,6,4,12,3); //fonction statique de la classe Observable, émet une séquence d'évènements, il existe par exemple Observable.interval (1000), Observable.fromEvent, Observable.concat.... of sert souvent en tests unitaires pour injecter un faux flux de données. Ici on envoie n° par n° mais on reçoit souvent des tableaux. observable.pipe( //par exemple notre valeur est A map(x=>x*5), //si c'est 1, je le multiplie par 5 filter(x=>x<45) //puis ce résultat je vérifie s'il est inférieur à 45 scan((acc,x)=>acc+x), //on l'aditioonne avec la valeur accumulé tap(x=>console.log('TAP:',x)), bufferCount(5)//une fois qu'on en a 5 on renvoie à l'observer, si on l'élève elles seront retournées à chaque fois


Les différentes opérations sur les sources sont représentées par des diagrammes :






Modules


Un ensemble de fonctions dans un fichier. On va importer ces fonctions dans notre appli.
Exemple : on a un module pour gérer les zips. On pourrait vouloir se servir d'une seule fonction, par exemple celle pour compresser.
Le point d'entré de notre appli est un module.

Là où on va utiliser le module : import { sayHello } from './say';, s'il ne trouve pas say.js, il va chercher un répertoire "say" et lancer son "index.js" (on peut nous même y importer plein de choses, cette technique est le "barrel" ou "passe-plat")
Dans le module : export function sayHello(){ console.log("Hello!");

On peut aussi exporter des classes, des const, des interfaces...

Si on fait import { Observable } from 'rxjs';, sans ., ça signifie que c'est le module RxJS dans le dossier des node_modules(et pas à la racine de notre projet).


Webpack


Bundler de modules : rassemble les modules js pour diminuer le nombre de fichiers (parfois rassemblés en un seul fichier). Si on build pour publier le site, webpack met les fichiers dans un dossier. Si on build juste pour tester, webpack ne génère pas réellement les fichiers mais les gardes juste en ram, ce qui est beaucoup plus rapide.
Il est capable de générer juste les modifications.