<?xml version="1.0" encoding="utf-8"?><rss version="2.0"><channel><title>Les articles de c2i.fr</title><link>http://www.c2i.fr:80/articles</link><description>Les articles de c2i.fr</description><item><title>Angular : Ajouter un bouton d'installation pour votre application PWA</title><link>http://www.c2i.fr:80/articles/angular-ajouter-un-bouton-d-installation-pour-votre-application-pwa</link><description>&lt;p&gt;&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/BK9CQLRsU7c" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen=""&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p&gt;Quand vous cr&amp;eacute;ez une application web PWA, sous Android, quand Chrome charge votre application, il vous propose automatiquement de l'installer.&lt;/p&gt;
&lt;p&gt;Il faut savoir que ce comportement par d&amp;eacute;faut va changer : ils ont en effet trouv&amp;eacute; que c'&amp;eacute;tait trop intrusif, qu'on ne laissait pas le choix &amp;agrave; l'utilisateur de faire cette installation quand il le voulait.&lt;/p&gt;
&lt;p&gt;De plus, si l'utilisateur &amp;eacute;tait en train de faire quelquechose dans l'application, il y a de fortes chances pour qu'il envoit bouler ce "pop-up" et moralit&amp;eacute;, l'application n'&amp;eacute;tait pas install&amp;eacute;e.&lt;/p&gt;
&lt;p&gt;Avec Chrome 70 et Windows 10, le comportement est de ne pas proposer ce pop-up d'installation.&lt;/p&gt;
&lt;p&gt;L'utilisateur a toujours la possibilit&amp;eacute; d'installer l'application en allant dans le menu de Chrome puis en cliquant sur "Installer xxx...". Mais bon, il y a 99.9% de chances que Mme Michu ne fasse jamais cette action. D'ailleurs, &amp;agrave; part le d&amp;eacute;veloppeur de l'application, je ne vois pas qui le ferait.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;img src="/Media/Default/BlogPost/articles/PWAAddButton-1.png" alt="" width="552" height="520" /&gt;&lt;/p&gt;
&lt;h1&gt;Cr&amp;eacute;er un bouton Installer l'application&lt;/h1&gt;
&lt;p&gt;L'id&amp;eacute;e est donc plut&amp;ocirc;t de proposer un bouton Installer l'application. Plusieurs avantages :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;L'utilisateur saura que l'application est "installable",&lt;/li&gt;
&lt;li&gt;L'utilisateur choisira le moment de cette installation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Respect de l'utilisateur tout en poussant ce dernier &amp;agrave; installer son application f&amp;eacute;tiche.&lt;/p&gt;
&lt;p&gt;&lt;img src="/Media/Default/BlogPost/articles/PWAAddButton-2.png" alt="" width="582" height="560" /&gt;&lt;/p&gt;
&lt;p&gt;C&amp;ocirc;t&amp;eacute; UI, c'est simple : j'ajoute un bouton/lien qui n'est visible que si l'application est "installable" (ie bien form&amp;eacute;e selon les crit&amp;egrave;res PWA et pas d&amp;eacute;j&amp;agrave; install&amp;eacute;e). Quand on clique dessus, j'appelerais une m&amp;eacute;thode qui effectuera cette installation :&lt;/p&gt;
&lt;div style="color: #d4d4d4; background-color: #1e1e1e; font-family: Consolas, 'Courier New', monospace; font-size: 14px; line-height: 19px; white-space: pre;"&gt;
&lt;div&gt;&lt;span style="color: #808080;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #569cd6;"&gt;a&lt;/span&gt; &lt;span style="color: #9cdcfe;"&gt;mat-list-item&lt;/span&gt; &lt;span style="color: #9cdcfe;"&gt;(click)&lt;/span&gt;=&lt;span style="color: #ce9178;"&gt;'installApp()'&lt;/span&gt; &lt;span style="color: #9cdcfe;"&gt;*ngIf&lt;/span&gt;=&lt;span style="color: #ce9178;"&gt;'showInstallButton'&lt;/span&gt;&lt;span style="color: #808080;"&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style="color: #808080;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #569cd6;"&gt;mat-icon&lt;/span&gt; &lt;span style="color: #9cdcfe;"&gt;mat-list-icon&lt;/span&gt; &lt;span style="color: #9cdcfe;"&gt;fontSet&lt;/span&gt;=&lt;span style="color: #ce9178;"&gt;'fas'&lt;/span&gt; &lt;span style="color: #9cdcfe;"&gt;fontIcon&lt;/span&gt;=&lt;span style="color: #ce9178;"&gt;'fa-download'&lt;/span&gt;&lt;span style="color: #808080;"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #569cd6;"&gt;mat-icon&lt;/span&gt;&lt;span style="color: #808080;"&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style="color: #808080;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #569cd6;"&gt;div&lt;/span&gt; &lt;span style="color: #9cdcfe;"&gt;mat-line&lt;/span&gt;&lt;span style="color: #808080;"&gt;&amp;gt;&lt;/span&gt;Installer l'application&lt;span style="color: #808080;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #569cd6;"&gt;div&lt;/span&gt;&lt;span style="color: #808080;"&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style="color: #808080;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #569cd6;"&gt;a&lt;/span&gt;&lt;span style="color: #808080;"&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;showInstallButton est une variable de mon composant de type boolean par d&amp;eacute;faut &amp;eacute;gal &amp;agrave; false.&lt;/p&gt;
&lt;p&gt;Maintenant &lt;strong&gt;ATTENTION&lt;/strong&gt; : le code qui suit n'est valable que pour Chrome 45+ sous Android et Windows 10. C'est une "non-standard feature" !&lt;/p&gt;
&lt;p&gt;Si le navigateur (en l'occurence Chrome) d&amp;eacute;tecte que votre application est une application PWA valide (manifeste + service worker + icones qu'il faut), il d&amp;eacute;clenchera l'&amp;eacute;v&amp;egrave;nement de l'object window BeforeInstallPromptEvent. Cet &amp;eacute;v&amp;egrave;nement signifie : "je vais afficher la boite de dialogue pour installer l'application. Je la montre pour de vrai ou pas ?"&lt;/p&gt;
&lt;p&gt;Ce que l'on va faire, puisqu'on ne veut pas d&amp;eacute;ranger l'utilisateur dans sa lecture du site, c'est de diff&amp;eacute;rer notre r&amp;eacute;ponse. Donc on sauvegarde pour r&amp;eacute;pondre plus tard et on affiche alors le bouton Installer l'application.&lt;/p&gt;
&lt;p&gt;Dans notre composant Angular, il faut donc s'abonner &amp;agrave; cet &amp;eacute;v&amp;egrave;nement, ce que l'on fait gr&amp;acirc;ce au d&amp;eacute;corateur HostListener :&lt;/p&gt;
&lt;div style="color: #d4d4d4; background-color: #1e1e1e; font-family: Consolas, 'Courier New', monospace; font-size: 14px; line-height: 19px; white-space: pre;"&gt;
&lt;div&gt;@&lt;span style="color: #dcdcaa;"&gt;HostListener&lt;/span&gt;(&lt;span style="color: #ce9178;"&gt;'window:beforeinstallprompt'&lt;/span&gt;, [&lt;span style="color: #ce9178;"&gt;'$event'&lt;/span&gt;])&lt;/div&gt;
&lt;div&gt;&lt;span style="color: #dcdcaa;"&gt;onBeforeinstallprompt&lt;/span&gt;(&lt;span style="color: #9cdcfe;"&gt;ev&lt;/span&gt;) {&lt;/div&gt;
&lt;div&gt;&lt;span style="color: #9cdcfe;"&gt;ev&lt;/span&gt;.&lt;span style="color: #dcdcaa;"&gt;preventDefault&lt;/span&gt;();&lt;/div&gt;
&lt;div&gt;&lt;span style="color: #6a9955;"&gt;// on affiche le bouton install&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style="color: #569cd6;"&gt;this&lt;/span&gt;.&lt;span style="color: #9cdcfe;"&gt;showInstallButton&lt;/span&gt; = &lt;span style="color: #569cd6;"&gt;true&lt;/span&gt;;&lt;/div&gt;
&lt;div&gt;&lt;span style="color: #6a9955;"&gt;// on "stocke" le prompt.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style="color: #569cd6;"&gt;this&lt;/span&gt;.&lt;span style="color: #9cdcfe;"&gt;deferredPrompt&lt;/span&gt; = &lt;span style="color: #9cdcfe;"&gt;ev&lt;/span&gt;;&lt;/div&gt;
&lt;div&gt;}&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Puis quand l'utilisateur a enfin d&amp;eacute;cider d'installer l'application, c'est &amp;agrave; dire quand il a cliqu&amp;eacute; sur notre bouton, on continue la proc&amp;eacute;dure d'installation :&lt;/p&gt;
&lt;div style="color: #d4d4d4; background-color: #1e1e1e; font-family: Consolas, 'Courier New', monospace; font-size: 14px; line-height: 19px; white-space: pre;"&gt;
&lt;div&gt;&lt;span style="color: #dcdcaa;"&gt;installApp&lt;/span&gt;() {&lt;/div&gt;
&lt;div&gt;&lt;span style="color: #6a9955;"&gt;// on affiche la boite de dialogue : installer l'application&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style="color: #569cd6;"&gt;this&lt;/span&gt;.&lt;span style="color: #9cdcfe;"&gt;deferredPrompt&lt;/span&gt;.&lt;span style="color: #dcdcaa;"&gt;prompt&lt;/span&gt;();&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style="color: #6a9955;"&gt;// selon la r&amp;eacute;ponse de l'utilisateur&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style="color: #569cd6;"&gt;this&lt;/span&gt;.&lt;span style="color: #9cdcfe;"&gt;deferredPrompt&lt;/span&gt;.&lt;span style="color: #9cdcfe;"&gt;userChoice&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;.&lt;span style="color: #dcdcaa;"&gt;then&lt;/span&gt;((&lt;span style="color: #9cdcfe;"&gt;choiceResult&lt;/span&gt;) &lt;span style="color: #569cd6;"&gt;=&amp;gt;&lt;/span&gt; {&lt;/div&gt;
&lt;div&gt;&lt;span style="color: #c586c0;"&gt;if&lt;/span&gt; (&lt;span style="color: #9cdcfe;"&gt;choiceResult&lt;/span&gt;.&lt;span style="color: #9cdcfe;"&gt;outcome&lt;/span&gt; === &lt;span style="color: #ce9178;"&gt;'accepted'&lt;/span&gt;) {&lt;/div&gt;
&lt;div&gt;&lt;span style="color: #4ec9b0;"&gt;console&lt;/span&gt;.&lt;span style="color: #dcdcaa;"&gt;log&lt;/span&gt;(&lt;span style="color: #ce9178;"&gt;'Youpi, notre appli est install&amp;eacute;e'&lt;/span&gt;);&lt;/div&gt;
&lt;div&gt;} &lt;span style="color: #c586c0;"&gt;else&lt;/span&gt; {&lt;/div&gt;
&lt;div&gt;&lt;span style="color: #4ec9b0;"&gt;console&lt;/span&gt;.&lt;span style="color: #dcdcaa;"&gt;log&lt;/span&gt;(&lt;span style="color: #ce9178;"&gt;'Arg, il en veut pas !'&lt;/span&gt;);&lt;/div&gt;
&lt;div&gt;&lt;span style="color: #569cd6;"&gt;this&lt;/span&gt;.&lt;span style="color: #9cdcfe;"&gt;showInstallButton&lt;/span&gt; = &lt;span style="color: #569cd6;"&gt;false&lt;/span&gt;;&lt;/div&gt;
&lt;div&gt;}&lt;/div&gt;
&lt;div&gt;&lt;span style="color: #6a9955;"&gt;// la madame elle fait le m&amp;eacute;nache&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style="color: #569cd6;"&gt;this&lt;/span&gt;.&lt;span style="color: #9cdcfe;"&gt;deferredPrompt&lt;/span&gt; = &lt;span style="color: #569cd6;"&gt;null&lt;/span&gt;;&lt;/div&gt;
&lt;div&gt;});&lt;/div&gt;
&lt;div&gt;}&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;That's all !&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;</description><pubDate>Fri, 19 Oct 2018 12:28:48 GMT</pubDate><guid isPermaLink="true">http://www.c2i.fr:80/articles/angular-ajouter-un-bouton-d-installation-pour-votre-application-pwa</guid></item><item><title>De Angular 6 à Angular 7</title><link>http://www.c2i.fr:80/articles/de-angular-6-a-angular-7</link><description>&lt;p&gt;&lt;img src="/Media/Default/BlogPost/articles/migration%20A7.png" alt="" width="800" height="414" /&gt;&lt;/p&gt;
&lt;p&gt;Angular 7 vient juste d'&amp;ecirc;tre disponible, nous allons donc tester si la migration se passe sans douleurs.&lt;/p&gt;
&lt;p&gt;Bien entendu, je partirai d'une application Angular 6 qui a &amp;eacute;t&amp;eacute; d&amp;eacute;velopp&amp;eacute; &amp;agrave; partir d'Angular CLI (cela devient du suicide de faire autrement aujourd'hui).&lt;/p&gt;
&lt;p&gt;NB : la doc d'angular/cli - qui &amp;eacute;tait quasi inexistante - fait partie maintenant du site officiel d'angular :&amp;nbsp;&lt;a href="https://angular.io/cli"&gt;https://angular.io/cli&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;C'est une application moyennement complexe, qui utilise @angular/material et @angular/flex-layout pour l'UI.&lt;/p&gt;
&lt;p&gt;Il s'agit de &lt;a href="https://www.gs-tennis.com/"&gt;https://www.gs-tennis.com&lt;/a&gt;, l'application qui permet de g&amp;eacute;rer les matchs par &amp;eacute;quipe de son club de tennis, application d&amp;eacute;velopp&amp;eacute;e en collaboration avec la FFT.&lt;/p&gt;
&lt;p&gt;&lt;img src="/Media/Default/BlogPost/articles/updateA7-0.png" alt="" width="800" height="627" /&gt;&lt;/p&gt;
&lt;p&gt;La premi&amp;egrave;re &amp;eacute;tape est simple, juste lancer un script angular/cli :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;ng update @angular/cli @angular/core&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/Media/Default/BlogPost/articles/updateA7-1.png" alt="" width="664" height="280" /&gt;&lt;/p&gt;
&lt;p&gt;R&amp;eacute;sultat : il me modifie mon fichier package.json et met &amp;agrave; jour tout ce qui fait partie du framework angular comme le montre la capture ci-dessous (remarquez que l'on passe en Typescript 3.1.3, bonne nouvelle ;-)) :&lt;/p&gt;
&lt;p&gt;&lt;img src="/Media/Default/BlogPost/articles/updateA7-2.png" alt="" width="800" height="598" /&gt;&lt;/p&gt;
&lt;p&gt;J'essaye alors une compilation : arg! j'ai des erreurs et elles proviennent de angular/flex-layout.&lt;/p&gt;
&lt;p&gt;J'en profite donc pour mettre &amp;agrave; jour les autres packages que ceux du framework Angular. Un :&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;npm outdated&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;me permet de lister les packages qui sont "p&amp;eacute;rim&amp;eacute;s".&lt;/p&gt;
&lt;p&gt;ATTENTION : n'essayez pas de mettre &amp;agrave; jour tous vos packages d'un seul coup, il est pr&amp;eacute;f&amp;eacute;rable de le faire progressivement pour s'assurer de la compatibilit&amp;eacute;.&lt;/p&gt;
&lt;p&gt;Je vais donc dans un premier temps mettre &amp;agrave; jour uniquement material et flex-layout.&lt;/p&gt;
&lt;div style="color: #d4d4d4; background-color: #1e1e1e; font-family: Consolas, 'Courier New', monospace; font-size: 14px; line-height: 19px; white-space: pre;"&gt;
&lt;div&gt;&lt;span style="color: #9cdcfe;"&gt;"@angular/cdk"&lt;/span&gt;: &lt;span style="color: #ce9178;"&gt;"^7.0.0"&lt;/span&gt;,&lt;/div&gt;
&lt;div&gt;&lt;span style="color: #9cdcfe;"&gt;"@angular/material"&lt;/span&gt;: &lt;span style="color: #ce9178;"&gt;"^7.0.0"&lt;/span&gt;,&lt;/div&gt;
&lt;div&gt;&lt;span style="color: #9cdcfe;"&gt;"@angular/flex-layout"&lt;/span&gt;: &lt;span style="color: #ce9178;"&gt;"^7.0.0-beta.19"&lt;/span&gt;,&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Suivi d'un :&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;npm update&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;ou vous pouvez faire &amp;eacute;galement un :&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;ng update @angular/material&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;ng update @angular/cdk&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;ng update @angular/flex-layout&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Je peux donc maintenant r&amp;eacute;essayer de compiler, et cette fois, cela fonctionne.&lt;/p&gt;
&lt;p&gt;Je n'ai plus qu'&amp;agrave; faire la mise &amp;agrave; jour des packages qui me restent. Je pense notamment aux packages suivant (pr&amp;eacute;sents dans tous les projets cli et source de warning de s&amp;eacute;curit&amp;eacute; avec npm) :&lt;/p&gt;
&lt;div style="color: #d4d4d4; background-color: #1e1e1e; font-family: Consolas, 'Courier New', monospace; font-size: 14px; line-height: 19px; white-space: pre;"&gt;
&lt;div&gt;&lt;span style="color: #9cdcfe;"&gt;"@angular-devkit/build-angular"&lt;/span&gt;: &lt;span style="color: #ce9178;"&gt;"^0.10.1"&lt;/span&gt;,&lt;/div&gt;
&lt;div&gt;&lt;span style="color: #9cdcfe;"&gt;"@types/node"&lt;/span&gt;: &lt;span style="color: #ce9178;"&gt;"~8.9.4"&lt;/span&gt;,&lt;/div&gt;
&lt;div&gt;&lt;span style="color: #9cdcfe;"&gt;"karma"&lt;/span&gt;: &lt;span style="color: #ce9178;"&gt;"~3.0.0"&lt;/span&gt;,&lt;/div&gt;
&lt;div&gt;&lt;span style="color: #9cdcfe;"&gt;"karma-chrome-launcher"&lt;/span&gt;: &lt;span style="color: #ce9178;"&gt;"~2.2.0"&lt;/span&gt;,&lt;/div&gt;
&lt;div&gt;&lt;span style="color: #9cdcfe;"&gt;"karma-coverage-istanbul-reporter"&lt;/span&gt;: &lt;span style="color: #ce9178;"&gt;"~2.0.4"&lt;/span&gt;,&lt;/div&gt;
&lt;div&gt;&lt;span style="color: #9cdcfe;"&gt;"karma-jasmine-html-reporter"&lt;/span&gt;: &lt;span style="color: #ce9178;"&gt;"^1.3.1"&lt;/span&gt;,&lt;/div&gt;
&lt;div&gt;&lt;span style="color: #9cdcfe;"&gt;"ts-node"&lt;/span&gt;: &lt;span style="color: #ce9178;"&gt;"~7.0.1"&lt;/span&gt;,&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Quid du r&amp;eacute;sultat c&amp;ocirc;t&amp;eacute; taille des fichiers g&amp;eacute;n&amp;eacute;r&amp;eacute;s ?&lt;/p&gt;
&lt;p&gt;Je ne dirais pas que mon test est repr&amp;eacute;sentatif mais j'ai des fichiers qui sont l&amp;eacute;g&amp;egrave;rement plus imposants (sans doute parce que @angular/material devient plus important).&lt;/p&gt;
&lt;p&gt;&lt;img src="/Media/Default/BlogPost/articles/de-angular-6-a-angular-7/updateA7-3.png" alt="" width="597" height="84" /&gt;&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Le passage &amp;agrave; Angular 7 s'est pass&amp;eacute; sans douleur pour ma part (ce qui n'&amp;eacute;tait pas le cas entre la 5 et la 6 avec la migration de rxjs).&lt;/p&gt;
&lt;p&gt;Pour des projets dits professionnels, je vais encore attendre quelques semaines mais cela semble prometteur.&lt;/p&gt;
&lt;h1&gt;Quelques petites remarques avant de partir...&lt;/h1&gt;
&lt;p&gt;Vous aurez remarqu&amp;eacute; que pour la sortie de cette version 7, l'&amp;eacute;quipe d'Angular communique non seulement sur les mises &amp;agrave; jour du framework mais sur @angular/material et @angular/cdk et ses nouveaux composants (NB : pour le nouveau compilateur ivy, faudra attendre encore un peu).&lt;/p&gt;
&lt;p&gt;On sent qu'ils poussent Material de fa&amp;ccedil;on intensive.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;D&amp;eacute;j&amp;agrave; la 7 mais la 6 vient &amp;agrave; peine de sortir ?!!! Je vous rassure, c'est juste une phylosophie de num&amp;eacute;rotation qui est propre &amp;agrave; Google (Chrome en est &amp;agrave; la v70).&lt;/p&gt;
&lt;p&gt;Maintenant la question est : pensez-vous que 4.7.2.12568 est plus lisible que 7 ? Faut juste changer ses habitudes ;-)&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Flex-Layout est toujours en beta ! Je m'interroge un peu sur ce package ou il n'y a pas grand monde de mobilis&amp;eacute; dessus (1 pers chez Google ?). La r&amp;eacute;ponse a ce qu'il soit toujours en beta est que toutes les fonctionnalit&amp;eacute;s imagin&amp;eacute;es pour ce package ne sont pas encore impl&amp;eacute;ment&amp;eacute;es.&lt;/p&gt;
&lt;p&gt;Ok, mais allez-dire &amp;agrave; votre chef de projet que vous voulez utiliser un package en beta et vous verrez sa r&amp;eacute;ponse !!!&lt;/p&gt;
&lt;p&gt;De plus, rien qu'&amp;agrave; voir comment sont g&amp;eacute;r&amp;eacute;s les branches et les tags sur ce package, et on a l'impression qu'onb a affaire un un gars qui a tr&amp;egrave;s froid et qui utilisent des moufles. Je dis ca, je dis rien...&lt;/p&gt;
&lt;p&gt;Dommage car ce package &amp;eacute;tait vraiment prometteur.&lt;/p&gt;</description><pubDate>Fri, 19 Oct 2018 09:31:23 GMT</pubDate><guid isPermaLink="true">http://www.c2i.fr:80/articles/de-angular-6-a-angular-7</guid></item><item><title>Comment installer votre application web PWA sous Windows 10</title><link>http://www.c2i.fr:80/articles/comment-installer-votre-application-web-pwa-sous-windows-10</link><description>&lt;p&gt;C'est une des grande nouveaut&amp;eacute; de Chrome 70 : votre application web PWA peut maintenant &amp;ecirc;tre install&amp;eacute;e sous Windows 10 comme si c'&amp;eacute;tait une application "native".&lt;/p&gt;
&lt;p&gt;La premi&amp;egrave;re &amp;eacute;tape est bien entendu d'&lt;a href="https://www.google.com/chrome"&gt;installer Chrome 70&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Ensuite, avec Chrome, rendez-vous sur le site de votre application PWA. Par exemple sur &lt;a href="https://www.myTjm.fr"&gt;https://www.myTjm.fr&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Allez dans le menu de Chrome :&lt;/p&gt;
&lt;p&gt;&lt;img width="800" src="/Media/Default/BlogPost/articles/PWAChrome1.png" /&gt;&lt;/p&gt;
&lt;p&gt;Cliquez sur Installer myTjm et confirmer que vous voulez bien l'installer.&lt;/p&gt;
&lt;p&gt;Aussit&amp;ocirc;t, votre application est recharg&amp;eacute;e dans une fen&amp;ecirc;tre "native" :&lt;/p&gt;
&lt;p&gt;&lt;img width="800" height="520" alt="" src="/Media/Default/BlogPost/articles/PWAChrome2.png" /&gt;&lt;/p&gt;
&lt;p&gt;Cool non ? Plus la peine de passer par le store ;-)&lt;/p&gt;
&lt;p&gt;Si vous voulez la d&amp;eacute;sinstaller, lancer l'application et dans le menu en haut &amp;agrave; droite, vous avez l'option D&amp;eacute;sinstaller :&lt;/p&gt;
&lt;p&gt;&lt;img width="582" height="425" alt="" src="/Media/Default/BlogPost/articles/PWAChrome3.png" /&gt;&lt;/p&gt;
&lt;p&gt;Bon, il reste un petit probl&amp;egrave;me avec les ic&amp;ocirc;nes dans le menu d&amp;eacute;marrer mais cela va certainement se corriger bient&amp;ocirc;t. Dans la liste des applis, l'ic&amp;ocirc;ne est correcte mais pas quand on l'ajoute comme tuile. Peut &amp;ecirc;tre est-ce mon manifeste qui ne contient pas les images qu'il faut pour WIndows 10.&lt;/p&gt;
&lt;p&gt;&lt;img width="638" height="369" alt="" src="/Media/Default/BlogPost/articles/PWAChrome4.png" /&gt;&lt;/p&gt;
&lt;p&gt;Donc maintenant, d&amp;egrave;s que vous avez une application PWA, vous pouvez "l'installer" sous Windows 10.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;</description><pubDate>Thu, 18 Oct 2018 08:36:56 GMT</pubDate><guid isPermaLink="true">http://www.c2i.fr:80/articles/comment-installer-votre-application-web-pwa-sous-windows-10</guid></item><item><title>Angular Material : Créer une Business Data Table</title><link>http://www.c2i.fr:80/articles/angular-material-business-datatable</link><description>&lt;p&gt;&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/bdoNaQolMIs?rel=0" frameborder="0" allowfullscreen="" allow="autoplay; encrypted-media"&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p&gt;L'un des contr&amp;ocirc;les le plus demand&amp;eacute; dans tout d&amp;eacute;veloppement dit "Business", c'est la gestion d'une DataGrid.&lt;/p&gt;
&lt;p&gt;Si vous faites une recherche sur Google (ou Bing ? ;-)), vous verrez qu'il y a pas mal de composant payant/gratuit. Si vous voulez voir une liste des 10 meilleures DataTable selon NgDevelop, &lt;a href="https://www.ngdevelop.tech/best-angular-tables/"&gt;consultez cet article&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Et c'est toujours pareil avec des composants ext&amp;eacute;rieurs, ils sont pr&amp;eacute;vus pour faire un maximum de chose (c'est m&amp;ecirc;me leur argument de vente) mais on tombe toujours sur LA fonctionnalit&amp;eacute; qu'on doit im&amp;eacute;prativement utiliser qui n'existe pas dans le composant que vous avez choisi.&lt;/p&gt;
&lt;p&gt;De plus, j'ai tendance &amp;agrave; limiter au maximum mes d&amp;eacute;pendences &amp;agrave; des composants ext&amp;eacute;rieurs. Sauf pour les gros &amp;eacute;diteurs (et encore), on n'est jamais sur du suivi et de la p&amp;eacute;r&amp;eacute;nnit&amp;eacute; de leurs d&amp;eacute;veloppements.&lt;/p&gt;
&lt;p&gt;Enfin, par principe, qui ne se discute pas parce que c'est moi le chef, d&amp;egrave;s qu'il y a une d&amp;eacute;pendence &amp;agrave; jQuery, j'&amp;eacute;limine le bousin. (je sais, ce n'est pas rationnel mais c'est comme &amp;ccedil;a).&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;NB : &lt;a href="https://github.com/RichardC64/AngularMaterial-BusinessTable"&gt;ce code est disponible sur Github&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Tout ce pr&amp;eacute;ambule pour vous dire que l'on va voir comment cr&amp;eacute;er une DataGrid dite "Business" &amp;agrave; partir de la DataTable d'Angular Material.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Le cahier des charges est le suivant. Elle doit :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Supporter des donn&amp;eacute;es tr&amp;egrave;s importantes,&lt;/li&gt;
&lt;li&gt;Permettre d'&amp;eacute;diter en ligne les donn&amp;eacute;es,&lt;/li&gt;
&lt;li&gt;Trier les donn&amp;eacute;es selon une colonne,&lt;/li&gt;
&lt;li&gt;Paginer les donn&amp;eacute;es,&lt;/li&gt;
&lt;li&gt;Ajouter des donn&amp;eacute;es.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Dans un premier temps nous allons voir comment cr&amp;eacute;er les apis c&amp;ocirc;t&amp;eacute; serveur pour satisfaire nos besoins avec une application ASP .NET WebApi (pas .NET Core, .NET "normal") puis nous verrons le code de l'application Angular cliente.&lt;/p&gt;
&lt;h1&gt;L'application Serveur&lt;/h1&gt;
&lt;p&gt;Pour permettre la pagination de nos donn&amp;eacute;es, nos apis REST doivent pouvoir fournir les donn&amp;eacute;es pagin&amp;eacute;es (&amp;eacute;tonnant non ?) avec une information compl&amp;eacute;mentaire qui est le nombre total de donn&amp;eacute;es. La m&amp;eacute;thode Get de notre api doit donc avoir comme param&amp;egrave;tres :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;l'index de la page,&lt;/li&gt;
&lt;li&gt;la taille d'une page,&lt;/li&gt;
&lt;li&gt;le nom de la colonne de tri,&lt;/li&gt;
&lt;li&gt;l'ordre du tri (ascendant ou descendant).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ce qui donne une URL du type :&lt;/p&gt;
&lt;p&gt;http://localhost:10614/api/persons?pageIndex=0&amp;amp;pageSize=5&amp;amp;sortColumn=lastName&amp;amp;sortDirection=desc&lt;/p&gt;
&lt;p&gt;Qui retournera les donn&amp;eacute;es en json :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;{
  "items": [
    {
      "id": 5,
      "firstName": "Eric",
      "lastName": "Verre",
      "age": 35
    },
    {
      "id": 11,
      "firstName": "Isabelle",
      "lastName": "Routin",
      "age": 74
    },
    {
      "id": 1014,
      "firstName": "Richard",
      "lastName": "Richard",
      "age": 20
    },
    {
      "id": 6,
      "firstName": "Jean",
      "lastName": "Renee",
      "age": 35
    },
    {
      "id": 3,
      "firstName": "Jean",
      "lastName": "Rene",
      "age": 34
    }
  ],
  "count": 12
}&lt;/pre&gt;
&lt;p&gt;Pour faire cela, on a besoin dans un premier temps d'une classe qui repr&amp;eacute;sente ces donn&amp;eacute;es. On va faire donc une classe g&amp;eacute;n&amp;eacute;rique valable pour tout type de donn&amp;eacute;es :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;    public class PaginationResult&amp;lt;T&amp;gt; where T : class
    {
        public PaginationResult(int count, IEnumerable&amp;lt;T&amp;gt; items)
        {
            Count = count;
            Items = items;
        }
        public IEnumerable&amp;lt;T&amp;gt; Items { get; set; }
        public int Count { get; set; }
    }&lt;/pre&gt;
&lt;p&gt;Nous devons donc requ&amp;ecirc;ter notre base de donn&amp;eacute;es en paginant les donn&amp;eacute;es. Avec SQL Server, le code Transact SQL sera donc :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;SELECT Count(*) FROM Persons;
SELECT * FROM Persons 
                ORDER BY lastName asc 
                OFFSET 0 
                ROWS FETCH NEXT 5 
                ROWS ONLY;&lt;/pre&gt;
&lt;p&gt;On voit ici que l'on ex&amp;eacute;cute 2 requ&amp;ecirc;tes SQL en m&amp;ecirc;me temps. La premi&amp;egrave;re retourne le nombre de donn&amp;eacute;es de la table, la second les donn&amp;eacute;es de la page 0 avec 5 &amp;eacute;l&amp;eacute;ments. L'Offset correspond au nombre de ligne que l'on souhaite "sauter", et Next le nombre de ligne.&lt;/p&gt;
&lt;p&gt;Pour la page 3, on aura :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;SELECT Count(*) FROM Persons;
SELECT * FROM Persons 
                ORDER BY lastName asc 
                OFFSET 10 
                ROWS FETCH NEXT 5 
                ROWS ONLY;&lt;/pre&gt;
&lt;p&gt;Pour ex&amp;eacute;cuter cette requ&amp;ecirc;te, j'ai choisi l'excellent ORM Dapper (light et performant) avec le code suivant :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;        public PaginationResult&amp;lt;Person&amp;gt; LoadPagedAll(
              int pageIndex,
              int pageSize, 
              string sortColumn = "LastName", 
              string sortDirection = "Asc")
        {          
            var sql = $"SELECT Count(*) FROM Persons;" ;
            sql += $"SELECT * FROM Persons ";


            sql += GetPaginationQuery(pageIndex, pageSize, sortColumn, sortDirection);

            using (var connection = new SqlConnection(ConnectionString))
            {

                using (var multi = connection.QueryMultiple(sql))
                {
                    var numbers = multi.Read&amp;lt;int&amp;gt;().First();
                    var persons = multi.Read&amp;lt;Person&amp;gt;();
                    return new PaginationResult&amp;lt;Person&amp;gt;(numbers, persons);
                }
            }
        }

protected override IEnumerable&amp;lt;string&amp;gt; AllowedSortColumns =&amp;gt; new[] {"FIRSTNAME", "LASTNAME", "AGE"};&lt;/pre&gt;
&lt;p&gt;Dapper permet simplement d'ex&amp;eacute;cuter 2 requ&amp;ecirc;tes &amp;agrave; la fois pour remplir notre object PaginationResult&amp;lt;Person&amp;gt;. La m&amp;eacute;thode GetPaginationQuery permet d'ajouter dans notre requ&amp;ecirc;te SQL les &amp;eacute;l&amp;eacute;ments de pagination :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;        protected string GetPaginationQuery(int pageIndex, int pageSize, string sortColumn, string sortDirection)
        {
            if (!AllowedSortColumns.Contains(sortColumn, StringComparer.InvariantCultureIgnoreCase))
                throw new ArgumentException("Not allowed columns", nameof(sortColumn));
            if (!_sortDirections.Contains(sortDirection, StringComparer.InvariantCultureIgnoreCase))
                throw new ArgumentException("Invalid sort direction", nameof(sortDirection));


            return $@"
                ORDER BY {sortColumn} {sortDirection} 
                OFFSET {pageIndex * pageSize} 
                ROWS FETCH NEXT {pageSize} 
                ROWS ONLY;";
        }&lt;/pre&gt;
&lt;p&gt;Remarquez que je laisse au repository le choix des colonnes ou l'on a le droit d'effectuer un tri pour des raisons d'optimisation. Laissez la possibilit&amp;eacute; au code client de trier sur n'importe quelle colonne est la porte ouverte &amp;agrave; des pertes de performances &amp;eacute;videntes. Il faut toujours imaginer que l'on a des tables contenant plusieurs millions d'enregistrement, et le tri ne peut s'effectuer que sur des colonnes pr&amp;eacute;vus pour (avec les index dans SQL server qu'il faut).&lt;/p&gt;
&lt;p&gt;De ce fait, le code de notre api REST est tout simple :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;        public PaginationResult&amp;lt;Person&amp;gt; Get(
           int pageIndex, 
           int pageSize, 
           string sortColumn = "LastName", 
           string sortDirection = "Asc")
        {
            return _personsRepositoy.LoadPagedAll(pageIndex, pageSize, sortColumn, sortDirection);
        }&lt;/pre&gt;
&lt;p&gt;Si le code client essaye de trier sur une colonne non optimis&amp;eacute;e, on aura une exception.&lt;/p&gt;
&lt;p&gt;Les autres codes de notre api sont triviaux, je ne vous ferais pas l'affront de le d&amp;eacute;tailler ici (voyez le code par vous m&amp;ecirc;me).&lt;/p&gt;
&lt;p&gt;Notre code c&amp;ocirc;t&amp;eacute; serveur est donc termin&amp;eacute;, il n'y a plus qu'&amp;agrave; le consommer c&amp;ocirc;t&amp;eacute; client.&lt;/p&gt;
&lt;h1&gt;Angular Material : DataTable&lt;/h1&gt;
&lt;p&gt;Notre application cliente repose sur le framework Angular avec comme compl&amp;eacute;ment, le package @angular/material. Le plus simple pour l'ajouter est d'utiliser la commande d'angular/cli :&lt;/p&gt;
&lt;p&gt;ng add @angular/material&lt;/p&gt;
&lt;p&gt;La DataTable d'Angular Material est bas&amp;eacute;e sur la CdkTable. Si vous venez comme moi du monde XAML, vous ne serez pas trop d&amp;eacute;payser. Le principe est que vous devez d&amp;eacute;finir un template pour chaque colonne que vous voulez afficher.&lt;/p&gt;
&lt;p&gt;Dans ce template, vous pouvez d&amp;eacute;finir l'aspect de 3 zones :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;L'en-t&amp;ecirc;te de la colonne,&lt;/li&gt;
&lt;li&gt;Le contenu de chaque cellule de la colonne,&lt;/li&gt;
&lt;li&gt;Le pied de la colonne.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Par exemple :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;&amp;lt;table mat-table mat-table [dataSource]="dataSource" &amp;gt;
    &amp;lt;!-- firtName column--&amp;gt;
    &amp;lt;ng-container matColumnDef="firstName"&amp;gt;
      &amp;lt;th mat-header-cell *matHeaderCellDef&amp;gt; First Name &amp;lt;/th&amp;gt;
      &amp;lt;td mat-cell *matCellDef="let person"&amp;gt;
        &amp;lt;div&amp;gt;{{person.firstName}}&amp;lt;/div&amp;gt;
      &amp;lt;/td&amp;gt;
    &amp;lt;/ng-container&amp;gt;
...
&amp;lt;/table&amp;gt;&lt;/pre&gt;
&lt;p&gt;Je d&amp;eacute;finie ici une colonne nomm&amp;eacute;e firstName. Dans l'en-t&amp;ecirc;te, j'affiche juste "First Name" et dans chaque cellule, gr&amp;acirc;ce &amp;agrave; ma variable person, j'affiche son lastName. Vous voyez qu'ici, je n'ai pas d&amp;eacute;fini de pied pour cette colonne.&lt;/p&gt;
&lt;p&gt;Je peux continuer ainsi pour les autres colonnes pour d&amp;eacute;finir les templates que je veux. Il ne me reste plus maintenant qu'&amp;agrave; dire quelles sont les colonnes que je veux r&amp;eacute;ellement afficher. On peut d&amp;eacute;finir un template et ne pas l'utiliser. L'int&amp;eacute;r&amp;ecirc;t est que la liste des colonnes affich&amp;eacute;es peut &amp;ecirc;tre modifi&amp;eacute;e par le code. On ajoute donc juste 2 lignes de code :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;&amp;lt;table mat-table mat-table [dataSource]="dataSource" &amp;gt;
    &amp;lt;!-- firtName column--&amp;gt;
    &amp;lt;ng-container matColumnDef="firstName"&amp;gt;
      &amp;lt;th mat-header-cell *matHeaderCellDef mat-sort-header&amp;gt; First Name &amp;lt;/th&amp;gt;
      &amp;lt;td mat-cell *matCellDef="let person"&amp;gt;
        &amp;lt;div&amp;gt;{{person.firstName}}&amp;lt;/div&amp;gt;
      &amp;lt;/td&amp;gt;
    &amp;lt;/ng-container&amp;gt;

    &amp;lt;tr mat-header-row *matHeaderRowDef="['firstName']"&amp;gt;&amp;lt;/tr&amp;gt;
    &amp;lt;tr mat-row *matRowDef="let row; columns: ['firstName']"&amp;gt;&amp;lt;/tr&amp;gt;
  &amp;lt;/table&amp;gt;
&amp;lt;/table&amp;gt;&lt;/pre&gt;
&lt;p&gt;Il faut maintenant d&amp;eacute;finir notre dataSource, c'est-&amp;agrave;-dire la source de nos donn&amp;eacute;es. Le CdkTable d&amp;eacute;finie une interface avec juste deux m&amp;eacute;thodes (connect et disconnect) pour notre source de donn&amp;eacute;es. la bonne nouvelle c'est qu'avec la DataTable, on a une impl&amp;eacute;mentation de cette derni&amp;egrave;re avec la classe MatTableDataSource.&lt;/p&gt;
&lt;p&gt;Il nous suffit donc de cr&amp;eacute;er une instance de cette classe :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;dataSource = new MatTableDataSource();&lt;/pre&gt;
&lt;p&gt;Puis gr&amp;acirc;ce &amp;agrave; notre service qui appelle notre api REST de lui passer les donn&amp;eacute;es &amp;agrave; afficher. Avant cela, il nous faut &amp;eacute;crire ce service. Il est tout simple :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;@Injectable({
  providedIn: 'root'
})
export class PersonsService {

  private url = environment.apiUrl + 'persons';
  constructor(private httpClient: HttpClient) { }

  loadAll(
    pageIndex?: number,
    pageSize?: number,
    sortColumn?: string,
    sortDirection?: string): Observable&amp;lt;IPaginationResult&amp;lt;IPerson&amp;gt;&amp;gt; {
    const params = new HttpParams()
      .set('pageIndex', pageIndex == null ? '0' : pageIndex.toString())
      .set('pageSize', pageSize == null ? '10' : pageSize.toString())
      .set('sortColumn', sortColumn == null ? 'lastName' : sortColumn)
      .set('sortDirection', sortDirection == null ? 'asc' : sortDirection);
    const headers = new HttpHeaders().set('Content-Type', 'application/json');
    return this.httpClient.get&amp;lt;IPaginationResult&amp;lt;IPerson&amp;gt;&amp;gt;(this.url, { headers: headers, params: params });
  }
}&lt;/pre&gt;
&lt;p&gt;Avec comme interface de "r&amp;eacute;ception" des donn&amp;eacute;es, l'&amp;eacute;quivalent en TypeScript de notre classe C# PaginationResult&amp;lt;Person&amp;gt; (elle m&amp;ecirc;me g&amp;eacute;n&amp;eacute;rique) :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;export interface IPaginationResult&amp;lt;T&amp;gt; {
    items: T[];
    count: number;
}
&lt;/pre&gt;
&lt;h1&gt;La pagination&lt;/h1&gt;
&lt;p&gt;Pour la pagination, la DataTable de Angular Material fournit un composant que l'on ajoute sous notre mat-table :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;  &amp;lt;table mat-table [dataSource]="dataSource"&amp;gt;
    &amp;lt;tr mat-header-row *matHeaderRowDef="['firstName', 'lastName']"&amp;gt;&amp;lt;/tr&amp;gt;
    &amp;lt;tr mat-row *matRowDef="let row; columns: ['firstName', 'lastName']"&amp;gt;&amp;lt;/tr&amp;gt;
  &amp;lt;/table&amp;gt;
  &amp;lt;mat-paginator [length]='resultLength' [pageSizeOptions]="[5, 10, 20]" showFirstLastButtons&amp;gt;&amp;lt;/mat-paginator&amp;gt;&lt;/pre&gt;
&lt;p&gt;On voit que l'on "bind" sa propri&amp;eacute;t&amp;eacute; length &amp;agrave; la variable resultLength de notre composant. Cette variable correspond &amp;agrave; la donn&amp;eacute;e count du IPaginationResult&amp;lt;Person&amp;gt;.&lt;/p&gt;
&lt;h1&gt;Le tri&lt;/h1&gt;
&lt;p&gt;Pour le tri, c'est tout aussi simple : il faut tout d'abord dire que notre mat-table supporte le tri :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;&amp;lt;table mat-table mat-table [dataSource]="dataSource" matSort matSortActive="lastName"
    matSortDisableClear matSortDirection="asc"&amp;gt;
...
&amp;lt;/table&amp;gt;&lt;/pre&gt;
&lt;p&gt;On indique ici que le tri sera par d&amp;eacute;faut sur la colonne lastName et de type ascendant. Ensuite, il faut indiquer quelles sont les colonnes ou l'on a le droit d'effectuer un tri. Cela se d&amp;eacute;fini dans le template de la colonne avec la directive mat-sort-header. Par exemple sur la colonne lastName :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;&amp;lt;!-- lastName column--&amp;gt;
    &amp;lt;ng-container matColumnDef="lastName"&amp;gt;
      &amp;lt;th mat-header-cell *matHeaderCellDef mat-sort-header&amp;gt; Last Name &amp;lt;/th&amp;gt;
      &amp;lt;td mat-cell *matCellDef="let person"&amp;gt;
        &amp;lt;div&amp;gt;{{person.lastName}}&amp;lt;/div&amp;gt;
      &amp;lt;/td&amp;gt;
    &amp;lt;/ng-container&amp;gt;&lt;/pre&gt;
&lt;p&gt;Voila, la pagination et le tri sont pr&amp;ecirc;ts. Il faut maintenant r&amp;eacute;agir aux &amp;eacute;v&amp;egrave;nements correspondants, c'est-&amp;agrave;-dire r&amp;eacute;interroger le serveur quand on change de page et/ou quand on clique sur l'en-t&amp;ecirc;te d'une colonne triable.&lt;/p&gt;
&lt;h1&gt;Connexion aux api REST&lt;/h1&gt;
&lt;p&gt;Pour r&amp;eacute;agir &amp;agrave; ces &amp;eacute;v&amp;egrave;nements, on a besoin d'avoir une r&amp;eacute;f&amp;eacute;rence aux composants correspondants de la DataTable, la pagination et le tri :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;  @ViewChild(MatPaginator) paginator: MatPaginator;
  @ViewChild(MatSort) sort: MatSort;&lt;/pre&gt;
&lt;p&gt;Ces deux composants ont des Observable correspondant au changement de page et au clic sur un en-t&amp;ecirc;te. Dans les 2 cas, on interroge le serveur de la m&amp;ecirc;me fa&amp;ccedil;on. On va donc combiner ces deux Observable gr&amp;acirc;ce &amp;agrave; l'op&amp;eacute;rateur static merge :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;    merge(this.sort.sortChange, this.paginator.page)
      .pipe(
        switchMap(() =&amp;gt; {
          this.isWorking = true;
          return this.loadPersons();
        }),
        tap(data =&amp;gt; {
          this.resultLength = data.count;
          this.dataSource.data = data.items;
          this.isWorking = false;
        })
      ).subscribe();&lt;/pre&gt;
&lt;p&gt;Quand le tri change ou que la page change, on appelle la m&amp;eacute;thode loadPerson qui nous retourne un IPaginationResult. Il ne nous reste plus qu'&amp;agrave; affecter les donn&amp;eacute;es re&amp;ccedil;ues &amp;agrave; la dataSource et &amp;agrave; la variable resultLength (pour la pagination).&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;  private loadPersons() {
    return this.personsService.loadAll(
      this.paginator.pageIndex,
      this.paginator.pageSize,
      this.sort.active,
      this.sort.direction);
  }&lt;/pre&gt;
&lt;p&gt;Un petit d&amp;eacute;tail : quand on change le tri, il faut imp&amp;eacute;rativement retourner &amp;agrave; la page d'index 0 :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;this.sort.sortChange.subscribe(() =&amp;gt; this.paginator.pageIndex = 0);&lt;/pre&gt;
&lt;p&gt;Enfin, je veux dans mon code pouvoir forcer le rechargement des donn&amp;eacute;es (notamment quand on va vouloir ajouter un nouvel &amp;eacute;l&amp;eacute;ment dans la liste). Le plus simple est de cr&amp;eacute;er un nouvel Observable qui sera combiner avec les 2 pr&amp;eacute;c&amp;eacute;dents. Donc on d&amp;eacute;fini cet Observable sous forme d'EventEmitter :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;mustReload = new EventEmitter();&lt;/pre&gt;
&lt;p&gt;Et l'on combine le tout. Dans l'&amp;eacute;v&amp;egrave;nement ngAfterViewInit, on a donc le code complet suivant :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;  ngAfterViewInit(): void {
    // go back to first page when changing sort
    this.sort.sortChange.subscribe(() =&amp;gt; this.paginator.pageIndex = 0);

    // query when changing sort or changing page or manual query
    merge(this.sort.sortChange, this.paginator.page, this.mustReload)
      .pipe(
        switchMap(() =&amp;gt; {
          this.isWorking = true;
          return this.loadPersons();
        }),
        tap(data =&amp;gt; {
          this.resultLength = data.count;
          this.dataSource.data = data.items;
          this.isWorking = false;
        })
      ).subscribe();
   // first load
    this.mustReload.emit();
  }&lt;/pre&gt;
&lt;p&gt;NB: ce code est plac&amp;eacute; dans le ngAfterViewInit et pas ngInit pour que les &amp;eacute;l&amp;eacute;ments sort et page soient bien instanci&amp;eacute;s.&lt;/p&gt;
&lt;p&gt;That's it! On pagine, on tri nos donn&amp;eacute;es c&amp;ocirc;t&amp;eacute; serveur.&lt;/p&gt;
&lt;h1&gt;La s&amp;eacute;lection&lt;/h1&gt;
&lt;p&gt;Dans la video ci-dessus,&amp;nbsp; vous voyez que l'on a une colonne qui nous permet de s&amp;eacute;lectionner une ligne pour l'&amp;eacute;diter. On pourrait cr&amp;eacute;er une classe similaire &amp;agrave; la classe IPerson en lui ajoutant une propri&amp;eacute;t&amp;eacute; IsSelected. mais Angular material a pr&amp;eacute;vu le coup et nous propose une classe SelectionModel&amp;lt;T&amp;gt; qui va le faire pour nous. Cette classe g&amp;egrave;re une collection d'&amp;eacute;l&amp;eacute;ments s&amp;eacute;lectionn&amp;eacute; (dans les exemples du SDK, on a toujours la possibilit&amp;eacute; de faire une s&amp;eacute;lection multiple). Ici, ce qui nous int&amp;eacute;resse c'est de n'avoir qu'un seul &amp;eacute;l&amp;eacute;ment s&amp;eacute;lectionnable (celui qu'on &amp;eacute;dite). On va donc d&amp;eacute;finir notre SelectonModel dans ce sens :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;selection = new SelectionModel&amp;lt;IPerson&amp;gt;(false, [], true);&lt;/pre&gt;
&lt;p&gt;Il n'est pas multi-s&amp;eacute;lectionnable et ne contient aucun &amp;eacute;l&amp;eacute;ment s&amp;eacute;lectionn&amp;eacute; &amp;agrave; l'instanciation. Le dernier param&amp;egrave;tre indique que l'on va &amp;eacute;couter l'Observable qui indique que la s&amp;eacute;lection &amp;agrave; chang&amp;eacute;e.&lt;/p&gt;
&lt;p&gt;On ajoute ensuite une colonne qui nous permettra de s&amp;eacute;lectionner une ligne :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;    &amp;lt;!--select column--&amp;gt;
    &amp;lt;ng-container matColumnDef="select"&amp;gt;
      &amp;lt;th mat-header-cell *matHeaderCellDef&amp;gt;
      &amp;lt;/th&amp;gt;
      &amp;lt;td mat-cell *matCellDef="let person"&amp;gt;
        &amp;lt;mat-checkbox (click)="$event.stopPropagation()" (change)="$event ? selection.toggle(person) : null" [checked]="selection.isSelected(person)"&amp;gt;
        &amp;lt;/mat-checkbox&amp;gt;
      &amp;lt;/td&amp;gt;
    &amp;lt;/ng-container&amp;gt;&lt;/pre&gt;
&lt;p&gt;Cette colonne contient un ckeckbox qui est coch&amp;eacute; si la personne fait partie de la s&amp;eacute;lection (selection.isSelected(row)) et qui s&amp;eacute;lectionne/d&amp;eacute;s&amp;eacute;lectionne l'&amp;eacute;lement si on la coche/d&amp;eacute;coche (selection.toggle(person)).&lt;/p&gt;
&lt;h1&gt;Mode &amp;eacute;dition&lt;/h1&gt;
&lt;p&gt;Maintenant que l'on sait s&amp;eacute;lectionner une ligne, on va pouvoir passer en mode &amp;eacute;dition de la ligne. Un premier point important, c'est que l'on doit pouvoir annuler les modifications que l'on a faite. On ne va donc pas travailler sur l'instance originelle de la personne mais sur son clone. Comme on n'a qu'une seule ligne &amp;eacute;ditable, on va utiliser une variable temporaire de type Person. Quand une personne est ajout&amp;eacute;e &amp;agrave; notre SelectionModel, on va donc copier l'&amp;eacute;l&amp;eacute;ment s&amp;eacute;lectionn&amp;eacute; dans notre variable editingPerson :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;    // copy original datas before editing
    this.selection.onChange
      .pipe(
        filter(v =&amp;gt; v.added.length === 1),
        tap(v =&amp;gt; this.editingPerson = Object.assign({}, v.added[0]))
      ).subscribe();&lt;/pre&gt;
&lt;p&gt;A vous de voir si votre objet contient des propri&amp;eacute;t&amp;eacute;s plus complexe auquel cas le code de clonage est plus complexe ;-).&lt;/p&gt;
&lt;p&gt;Maintenant, &amp;agrave; nous de changer les templates de nos colonnes. Par exemple pour la colonne firstName :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;    &amp;lt;!-- firstName column--&amp;gt;
    &amp;lt;ng-container matColumnDef="firstName"&amp;gt;
      &amp;lt;th mat-header-cell *matHeaderCellDef mat-sort-header&amp;gt; First Name &amp;lt;/th&amp;gt;
      &amp;lt;td mat-cell *matCellDef="let person"&amp;gt;
        &amp;lt;div *ngIf="!selection.isSelected(person)"&amp;gt;{{person.firstName}}&amp;lt;/div&amp;gt;
        &amp;lt;mat-form-field *ngIf="selection.isSelected(person)"&amp;gt;
          &amp;lt;input matInput [(ngModel)]='editingPerson.firstName'&amp;gt;
        &amp;lt;/mat-form-field&amp;gt;
      &amp;lt;/td&amp;gt;
    &amp;lt;/ng-container&amp;gt;&lt;/pre&gt;
&lt;p&gt;Si la personne fait partie de la s&amp;eacute;lection, on affiche l'input, sinon on affiche juste la valeur de la propri&amp;eacute;t&amp;eacute;.&lt;/p&gt;
&lt;p&gt;Il nous reste plus qu'&amp;agrave; ajouter une colonne avec les actions de validation des modifications ou d'annulation :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;    &amp;lt;!-- actions column--&amp;gt;
    &amp;lt;ng-container matColumnDef="actions"&amp;gt;
      &amp;lt;th mat-header-cell *matHeaderCellDef&amp;gt; &amp;lt;/th&amp;gt;
      &amp;lt;td mat-cell *matCellDef="let person"&amp;gt;
        &amp;lt;div *ngIf="!selection.isSelected(person)"&amp;gt;
          &amp;lt;button mat-icon-button (click)='delete(person.id)'&amp;gt;
            &amp;lt;mat-icon&amp;gt;delete&amp;lt;/mat-icon&amp;gt;
          &amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div *ngIf="selection.isSelected(person)"&amp;gt;
          &amp;lt;button mat-icon-button (click)='save(person)'&amp;gt;
            &amp;lt;mat-icon&amp;gt;done&amp;lt;/mat-icon&amp;gt;
          &amp;lt;/button&amp;gt;
          &amp;lt;button mat-icon-button (click)="selection.clear(); editingPerson = null;"&amp;gt;
            &amp;lt;mat-icon&amp;gt;undo&amp;lt;/mat-icon&amp;gt;
          &amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/td&amp;gt;
    &amp;lt;/ng-container&amp;gt;&lt;/pre&gt;
&lt;p&gt;On affiche un bouton delete quand la ligne n'est pas s&amp;eacute;lectionn&amp;eacute;e et un bouton save et cancel quand elle est s&amp;eacute;lectionn&amp;eacute;e. Les clics sur les diff&amp;eacute;rents boutons d&amp;eacute;s&amp;eacute;lectionne la ligne ou appelent les m&amp;eacute;thodes de suppression ou de sauvegarde. Dans les 2 cas, on va forcer le rechargement des donn&amp;eacute;es pour afficher le r&amp;eacute;sultat de notre action gr&amp;acirc;ce &amp;agrave; notre EventEmitter mustReload :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;  delete(id: number) {
    this.isWorking = true;
    this.personsService.delete(id).subscribe(result =&amp;gt; {
      this.isWorking = false;
      this.snackBar.open('Person deleted');
      this.selection.clear();
      this.mustReload.emit();
    });
  }

  save(person: IPerson) {
    this.isWorking = true;
    this.personsService.update(this.editingPerson).subscribe(result =&amp;gt; {
      this.isWorking = false;
      this.snackBar.open('Person saved');
      this.selection.clear();
      this.mustReload.emit();
    });
  }&lt;/pre&gt;
&lt;h1&gt;Cr&amp;eacute;ation&lt;/h1&gt;
&lt;p&gt;Le dernier point concerne l'ajout de la ligne en pied de notre tableau qui permet d'ajouter une Person a notre base de donn&amp;eacute;es. Pour cela, je vais ajouter un pied &amp;agrave; notre tableau. Mais ce pied, je vais lui demander d'occuper la totalit&amp;eacute; de notre tableau. C'est possible avec le mat-table car il nous g&amp;eacute;n&amp;egrave;re des tags HTML de type table. Donc on peut utiliser l'attribut colspan pour les colonnes. On va donc changer le template de la premi&amp;egrave;re colonne en y d&amp;eacute;finissant un footer qui occupe la totalit&amp;eacute; de la table :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;    &amp;lt;!--select column--&amp;gt;
    &amp;lt;ng-container matColumnDef="select"&amp;gt;
      &amp;lt;th mat-header-cell *matHeaderCellDef&amp;gt;
      &amp;lt;/th&amp;gt;
      &amp;lt;td mat-cell *matCellDef="let row"&amp;gt;
        &amp;lt;mat-checkbox (click)="$event.stopPropagation()" (change)="$event ? selection.toggle(row) : null" [checked]="selection.isSelected(row)"&amp;gt;
        &amp;lt;/mat-checkbox&amp;gt;
      &amp;lt;/td&amp;gt;

      &amp;lt;td mat-footer-cell *matFooterCellDef colspan=5 style="padding: 0"&amp;gt;
        &amp;lt;app-add-person (personCreated)='personCreated($event)'&amp;gt;&amp;lt;/app-add-person&amp;gt;
      &amp;lt;/td&amp;gt;
    &amp;lt;/ng-container&amp;gt;&lt;/pre&gt;
&lt;p&gt;Dans le footer, je place un nouveau composant (pour une leilleure lisibilit&amp;eacute;) qui contient le formulaire de cr&amp;eacute;ation. Ce composant contient toute la logique de cr&amp;eacute;ation d'une personne (avec l'appel &amp;agrave; l'api REST correspond). Le code est "trivial", je ne l'exposerais donc pas ici. Remarquez toutefois qu'il expose un EventEmitter qui nous permet de r&amp;eacute;agir &amp;agrave; la cr&amp;eacute;ation d'une personne : quand on sauvegarde la nouvelle personne, il faut pouvoir rafraichir le table donc faire de nouveau appel au mustReload.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Cet article est relativement long mais la logique est relativement simple. Il vous permet de cr&amp;eacute;er des DataTable sans d&amp;eacute;pendences externes suppl&amp;eacute;mentaire, jsute avec le package angular/material.&lt;/p&gt;
&lt;p&gt;Bon code !&lt;/p&gt;
&lt;p&gt;NB : &lt;a href="https://github.com/RichardC64/AngularMaterial-BusinessTable"&gt;ce code est disponible sur Github&lt;/a&gt; dans la branche NoFilter. La branche master contient en plus la possibilit&amp;eacute; de filtrer les donn&amp;eacute;es selon la colonne lastName.&lt;/p&gt;</description><pubDate>Thu, 11 Oct 2018 13:16:16 GMT</pubDate><guid isPermaLink="true">http://www.c2i.fr:80/articles/angular-material-business-datatable</guid></item><item><title>Créer des tests unitaires avec Angular [Part 3] : Tests d'intégration</title><link>http://www.c2i.fr:80/articles/creer-des-tests-unitaires-avec-angular-part-3</link><description>&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/Media/Default/BlogPost/articles/angularkarmajasmine.png" alt="" width="800" height="271" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Jusqu'&amp;agrave; pr&amp;eacute;sent, les tests que nous faisions ne prenaient pas en compte le framework Angular. Ils ont l'avantage d'&amp;ecirc;tre tr&amp;egrave;s rapide mais ne reflettent pas compl&amp;egrave;tement l'environnement dans lequel vos classes/services s'ex&amp;eacute;cuteront.&lt;/p&gt;
&lt;p&gt;Cette fois, c'est le Framework Angular qui nous propose un environnement de test qui tiendra compte par exemple de l'injection de d&amp;eacute;pendance. Il permet &amp;eacute;galement de tester les templates de ses pages comme par exemple si le binding s'effectue correctement. cela nous permettra donc de tester des composants.&lt;/p&gt;
&lt;p&gt;Quand vous demandez &amp;agrave; Angular CLI de g&amp;eacute;n&amp;eacute;rer un service, une classe, c'est cette approche qui est privil&amp;eacute;gi&amp;eacute;e. Par exemple, si vous faites :&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;ng g s users&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;le fichier usersService.spec.ts aura l'aspect suivant :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;import { TestBed, inject } from '@angular/core/testing';
import { UsersService } from './users.service';

describe('UsersService', () =&amp;gt; {
  beforeEach(() =&amp;gt; {
    TestBed.configureTestingModule({
      providers: [UsersService]
    });
  });

  it('should be created', inject([UsersService], (service: UsersService) =&amp;gt; {
    expect(service).toBeTruthy();
  }));
});
&lt;/pre&gt;
&lt;p&gt;Ce code peut &amp;eacute;galement s'&amp;eacute;crire de cette fa&amp;ccedil;on :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;import { UsersService } from './users.service';
import { TestBed } from '@angular/core/testing';

describe('UsersService', () =&amp;gt; {

  let usersService: UsersService;

  beforeEach(() =&amp;gt; {
    TestBed.configureTestingModule({
      providers: [UsersService]
    });

    usersService = TestBed.get(UsersService);
  });

  it('should be created', () =&amp;gt; {
    expect(usersService).toBeTruthy();
  });
});&lt;/pre&gt;
&lt;p&gt;Le premier point est que nous utilisons le TestBed fournit par Angular. Tout comme votre app.Module, on doit lui fournir les informations initiales comme les providers, les imports, etc. Sa m&amp;eacute;thode get permet de r&amp;eacute;cup&amp;eacute;rer l'instance d'un service en utilisant le m&amp;ecirc;me service d'injection d'Angular. Donc dans le code ci-dessus, le test plantera parce qu'il ne sait pas ce qu'est un HttpClient (qui est dans le constructeur du service. Nous allons lui fournir et comme nous voulons le 'mocker', on va utiliser un utilitaire fournir dans les outils d'Angular, le&amp;nbsp;HttpTestingController :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;import { UsersService } from './users.service';
import { TestBed, inject } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { IUser } from '../entities/user';

describe('UsersService', () =&amp;gt; {

  let usersService: UsersService;
  let httpTestingController: HttpTestingController;&lt;br /&gt;
  beforeEach(() =&amp;gt; {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [UsersService, HttpTestingController]
    });

    httpTestingController = TestBed.get(HttpTestingController);&lt;br /&gt;    usersService = TestBed.get(UsersService);
  });

  it('should be created', () =&amp;gt; {
    expect(usersService).toBeTruthy();
  });
});&lt;/pre&gt;
&lt;p&gt;Cette fois ci, notre test fonctionne ;-) On teste bien ici que le HttpClient est bien pass&amp;eacute; en argument dans le constructeur de notre service, bref, on a test&amp;eacute; que l'injection fonctionnait bien.&lt;/p&gt;
&lt;p&gt;Maintenant, testons comme pr&amp;eacute;c&amp;eacute;demment si l'appel http est correct, que l'url est correcte :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;  it('appel avec la bonne url', () =&amp;gt; {
    const user: IUser = { id: 1, firstName: 'Richard', lastName: 'Clark' };

    usersService.getUser(1)
      .subscribe((resp: IUser) =&amp;gt; {
        expect(resp).toBe(user);
      });

    const mockRequest = mockHttp.expectOne('https://monSite.fr/api/users/1');
    expect(mockRequest.request.method).toBe('GET');
    mockRequest.flush(user);

    mockHttp.verify();
  });&lt;/pre&gt;
&lt;p&gt;La m&amp;eacute;thode expectOne indique qu'elle url doit &amp;ecirc;tre appel&amp;eacute;e. Vous voyez qu'on peut tester &amp;eacute;galement si c'est bien la m&amp;eacute;thode GET qui est utilis&amp;eacute;e. flush indique ce qui doit &amp;ecirc;tre retourn&amp;eacute;e. On peut aussi sp&amp;eacute;cifier des informations comme les headers. Enfin, verify s'assure qu'il n'y a rien eu d'autres comme appel, autre que ce que l'on a test&amp;eacute;.&lt;/p&gt;
&lt;p&gt;Dernier petit point int&amp;eacute;ressant, on peut injecter un service au niveau de la m&amp;eacute;thode de test. Ainisi, au lieu de d&amp;eacute;clarer une variable global usersService, on peut le faire gr&amp;acirc;ce &amp;agrave; la m&amp;eacute;thode inject du framework de test d'angular. Le test unitaire complet peut donc ressembler &amp;agrave; cela :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;import { UsersService } from './users.service';
import { TestBed, inject } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { IUser } from '../entities/user';

describe('UsersService', () =&amp;gt; {
  let mockHttp: HttpTestingController;

  beforeEach(() =&amp;gt; {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [UsersService]
    });

    mockHttp = TestBed.get(HttpTestingController);
  });

  it('should be created', inject([UsersService], (service: UsersService) =&amp;gt; {
    expect(service).toBeTruthy();
  }));

  it('appel avec la bonne url', inject([UsersService], (service: UsersService) =&amp;gt; {
    const user: IUser = { id: 1, firstName: 'Richard', lastName: 'Clark' };

    service.getUser(1)
      .subscribe((resp: IUser) =&amp;gt; {
        expect(resp).toBe(user);
      });

    const mockRequest = mockHttp.expectOne('https://monSite.fr/api/users/1');
    expect(mockRequest.request.method).toBe('GET');
    mockRequest.flush(user);

    mockHttp.verify();
  }));
});
&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;</description><pubDate>Mon, 03 Sep 2018 05:00:14 GMT</pubDate><guid isPermaLink="true">http://www.c2i.fr:80/articles/creer-des-tests-unitaires-avec-angular-part-3</guid></item><item><title>Créer des tests unitaires avec Angular [Part 2] : Mocker un service</title><link>http://www.c2i.fr:80/articles/creer-des-tests-unitaires-avec-angular-part-2-mocker-un-service</link><description>&lt;p&gt;&lt;img src="/Media/Default/BlogPost/articles/angularkarmajasmine.png" alt="" width="800" height="271" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;D&amp;egrave;s que l'on fait des tests unitaires, on se retrouve tr&amp;egrave;s rapidement face &amp;agrave; la probl&amp;eacute;matique des Mocks.&lt;/p&gt;
&lt;p&gt;Par d&amp;eacute;finition, un test unitaire ne doit tester qu'une infime partie de votre code. Prenons l'exemple d'une m&amp;eacute;thode d'un service A utilise une m&amp;eacute;thode d'un service B qui utilise elle m&amp;ecirc;me une m&amp;eacute;thode d'un service C. Si votre test unitaire plante, vous ne saurez pas si c'est de la faute de A, B ou C.&lt;/p&gt;
&lt;p&gt;D'ou l'id&amp;eacute;e de Mock, c'est-&amp;agrave;-dire d'utiliser des objets qui simulent le comportement d'objets tiers. Prenons un exemple concret avec un service Angular qui retourne un IUser d'une requ&amp;ecirc;te Http :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;@Injectable({
  providedIn: 'root'
})
export class UsersService {

  constructor(private httpClient: HttpClient) { }

  getUser(id: number): Observable&amp;lt;IUser&amp;gt; {
    const url = environment.apiUrl + 'users/' + id;
    return this.httpClient.get&amp;lt;IUser&amp;gt;(url);
  }
}&lt;/pre&gt;
&lt;p&gt;Si l'on veut tester cette m&amp;eacute;thode, il n'est pas question de faire un vrai appel Http vers notre api. Pour peu de le site ne soit pas disponible, notre test unitaire planterait m&amp;ecirc;me si le code est valide, ce qui est contraire au principe des tests unitaires.&lt;/p&gt;
&lt;p&gt;La bonne nouvelle c'est que jasmine nous permet de cr&amp;eacute;er des espions, des mocks assez facilement avec la m&amp;eacute;thode createSpyObj :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;httpClientSpy = jasmine.createSpyObj('HttpClient', ['get']);&lt;/pre&gt;
&lt;p&gt;Le premier argument est le nom de l'objet que l'on veut mocker, le second la liste des m&amp;eacute;thodes que l'on veut g&amp;eacute;rer. Ensuite, on doit d&amp;eacute;finir le comportement de cette m&amp;eacute;thode. Pour notre test, on se moque (ah, ah ah) de ce qu'elle retourne. Ce qui nous importe c'est que l'api soit appel&amp;eacute;e avec la bonne url :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;httpClientSpy.get.and.returnValue(of(jasmine.any('IUser')));&lt;/pre&gt;
&lt;p&gt;Il ne nous reste plus qu'&amp;agrave; appeler la m&amp;eacute;thode de notre service :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;usersService.getUser(1).subscribe(result =&amp;gt; {
      expect(httpClientSpy.get).toHaveBeenCalledWith('https://monSite.fr/api/users/1');
    });&lt;/pre&gt;
&lt;p&gt;Quand on appele la m&amp;eacute;thode getUser avec le param&amp;egrave;tre 1, on veut &amp;ecirc;tre sur que le service HttpClient appele l'url https://monSite.fr/users/1. Notre test unitaire complet est donc le suivant :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;import { UsersService } from './users.service';
import { of } from 'rxjs';

describe('UsersService', () =&amp;gt; {
  let usersService: UsersService;
  let httpClientSpy: { get: jasmine.Spy };

  beforeEach(() =&amp;gt; {
    httpClientSpy = jasmine.createSpyObj('HttpClient', ['get']);
    usersService = new UsersService(&amp;lt;any&amp;gt;httpClientSpy);
  });

  it('appel avec la bonne url', () =&amp;gt; {
    httpClientSpy.get.and.returnValue(of(jasmine.any('IUser')));
    usersService.getUser(1).subscribe(result =&amp;gt; {
      expect(httpClientSpy.get).toHaveBeenCalledWith('https://monSite.fr/api/users/1');
    });
  });
});&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;</description><pubDate>Thu, 30 Aug 2018 13:09:53 GMT</pubDate><guid isPermaLink="true">http://www.c2i.fr:80/articles/creer-des-tests-unitaires-avec-angular-part-2-mocker-un-service</guid></item><item><title>Créer des tests unitaires avec Angular</title><link>http://www.c2i.fr:80/articles/creer-des-tests-unitaires-avec-angular</link><description>&lt;p&gt;&lt;img src="/Media/Default/BlogPost/articles/angularkarmajasmine.png" alt="" width="800" height="271" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Je suppose que comme tout le monde, vous faites des tests unitaires de votre application... Non ? ;-)&lt;/p&gt;
&lt;p&gt;Bref, nous allons voir ici comment cr&amp;eacute;er des tests unitaires simples avec Angular/AngularCLI/Karma/Jasmine.&lt;/p&gt;
&lt;p&gt;Quand on cr&amp;eacute;e une application avec Angular CLI, la bonne nouvelle, c'est qu'il met en place tout le param&amp;eacute;trage pour tester votre application. Vous pouvez le voir dans le fichier package.json ou il vous ajoute les r&amp;eacute;f&amp;eacute;rences aux packages NPM pour karma, jasmine et tout un tas d'autres trrucs du m&amp;ecirc;me genre :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;    "jasmine-core": "~2.99.1",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~1.7.1",
    "karma-chrome-launcher": "~2.2.0",
    "karma-coverage-istanbul-reporter": "~2.0.0",
    "karma-jasmine": "~1.1.1",
    "karma-jasmine-html-reporter": "^0.2.2",&lt;/pre&gt;
&lt;p&gt;Ainsi que les types pour b&amp;eacute;n&amp;eacute;ficier de l'intellisense :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;    "@types/jasmine": "~2.8.6",
    "@types/jasminewd2": "~2.0.3",&lt;/pre&gt;
&lt;p&gt;Karma&amp;nbsp; est le moteur qui permet de lancer les tests dans un vrai navigateur (ici, Chrome, cf karma-chrome-launcher), le test runner (d&amp;eacute;velopp&amp;eacute; par l'&amp;eacute;quipe d'AngularJS). Jasmine est lui le framework de test &amp;agrave; proprement parl&amp;eacute;.&lt;/p&gt;
&lt;p&gt;Vos tests unitaires (et vos tests d'int&amp;eacute;gration), vous devrez les &amp;eacute;crire dans un fichier dont le nom devra se terminer par .spec.ts. D&amp;egrave;s que le moteur d&amp;eacute;tecte un fichier de ce genre, il l'int&amp;egrave;gre dans l'ensemble des tests.&lt;/p&gt;
&lt;p&gt;Vos tests seront ex&amp;eacute;cut&amp;eacute;s dans le navigateur (et pas une &amp;eacute;mulation quelconque) afin de s'assurer que dans la vraie vie, vos tests correspondront bien &amp;agrave; ce que l'on attend.&lt;/p&gt;
&lt;p&gt;Pour commencer, cr&amp;eacute;ons une nouvelle application avec Angular CLI :&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;ng new myApp&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Puis, pour simplifier, supprimez le fichier app.component.spec.ts (on verra cela un peu plus tard).&lt;/p&gt;
&lt;p&gt;Enfin, cr&amp;eacute;ez un nouveau service super compliqu&amp;eacute;, CalculatorService :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class CalculatorService {

  constructor() { }

  add(a: number, b: number) {
    return a + b;
  }

  lowerOrZero(numbers: number[]) {
    if (numbers == null) { return 0; }
    if (numbers.length === 0) { return 0; }

    const result = Math.min(...numbers);
    return result &amp;lt; 0 ? 0 : result;
  }
}
&lt;/pre&gt;
&lt;p&gt;C'est le service de la mort qui tue !&lt;/p&gt;
&lt;h1&gt;Notre premier test unitaire&lt;/h1&gt;
&lt;p&gt;Ajoutons un fichier de test nomm&amp;eacute; calculator.service.spec.ts. Tout test unitaire se d&amp;eacute;compose en trois actes AAA : Arrange, Act, Assert.&lt;/p&gt;
&lt;p&gt;Dans Arrange, nous pr&amp;eacute;parons notre test.&lt;/p&gt;
&lt;p&gt;Dans Act nos ex&amp;eacute;cutons le test.&lt;/p&gt;
&lt;p&gt;Dans Assert, nous v&amp;eacute;rifions les r&amp;eacute;sultats du test.&lt;/p&gt;
&lt;p&gt;Dans jasmine, cela se traduit par un code qui a la structure suivante :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;describe('Intitul&amp;eacute; du test', () =&amp;gt; {

  it('Calcul compliqu&amp;eacute;!', () =&amp;gt; {
    // Arrange
    let a = 4;

    // Act
    let result = a * 2;

    // Assert
    expect(result).toBe(8);
  });
});&lt;/pre&gt;
&lt;p&gt;describe permet de regrouper les tests d'une m&amp;ecirc;me nature qui contient chaque it, chaque test unitaire. Ici, dans l'unique test unitaire intitul&amp;eacute; 'Calcul compliqu&amp;eacute;', on d&amp;eacute;finit les param&amp;egrave;tres initiaux (a=4), on effectue le test (result = a*2) et on test le r&amp;eacute;sultat avec expect(result).toBe(8). Naturel, intuitif, simple.&lt;/p&gt;
&lt;p&gt;Pour ex&amp;eacute;cuter notre test, Angular CLI nous a ajout&amp;eacute; la commande qu'il faut, il suffit dans la console de taper :&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;npm test&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;A ce moment, il compile votre code et lance le navigateur puis ex&amp;eacute;cute les tests et vous montre le r&amp;eacute;sultat dans le navigateur :&lt;/p&gt;
&lt;p&gt;&lt;img src="/Media/Default/BlogPost/articles/creer-des-tests-unitaires-avec-angular/karma1.JPG" alt="" width="404" height="305" /&gt;&lt;/p&gt;
&lt;p&gt;Et dans la console, vous voyez plus d'informations (utilse en cas de probl&amp;egrave;me).&lt;/p&gt;
&lt;p&gt;&lt;img src="/Media/Default/BlogPost/articles/creer-des-tests-unitaires-avec-angular/karma2.JPG" alt="" width="800" height="211" /&gt;&lt;/p&gt;
&lt;p&gt;Surtout ne fermez pas le navigateur : retournez dans votre code, faites une petite modification et sauvegardez : aussit&amp;ocirc;t, votre code est recompil&amp;eacute; et les tests r&amp;eacute;ex&amp;eacute;cut&amp;eacute;s. Cool en cas de probl&amp;egrave;me.&lt;/p&gt;
&lt;p&gt;Testons maintenant le service. On pourrait &amp;eacute;crire :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;describe('CalculatorService simple test', () =&amp;gt; {
  describe('add', () =&amp;gt; {
    const service = new CalculatorService();
    it('should return correct answer', () =&amp;gt; {
      const result = service.add(4, 2);

      expect(result).toBe(6);
    });
  });
});&lt;/pre&gt;
&lt;p&gt;Mais si l'on veut &amp;eacute;crire un autre test unitaire (it) avec de nouveau un service, il faut de nouveau l'instancier (ici c'est juste un new CalculatorService() mais on peut avoir des param&amp;egrave;tres initiaux plus complexe. On pourrait mettre l'instanciation du service avant la premi&amp;egrave;re m&amp;eacute;thode it mais dans ce cas, ce serait toujours la m&amp;ecirc;me instance du service qui serait utilis&amp;eacute; par tous les tests it ce qui est mal ;-). Heureusement, jasmine propse une m&amp;eacute;thode beforeEach qui sera utilis&amp;eacute;e &amp;agrave; chaque ex&amp;eacute;cution d'un it. Donc notre code sera plut&amp;ocirc;t le suivant (j'ai rajout&amp;eacute; un deuxi&amp;egrave;me test) :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;import { CalculatorService } from './calculator.service';

describe('CalculatorService simple test', () =&amp;gt; {
  let service: CalculatorService;

  beforeEach(() =&amp;gt; {
    service = new CalculatorService();
    console.log('creation...');
  });

  it('add: should return correct answer', () =&amp;gt; {
    const result = service.add(4, 2);

    expect(result).toBe(6);
  });

  it('lowerOrZero: Should answer correct answer', () =&amp;gt; {
    const result = service.lowerOrZero([2, 1, 3]);
    expect(result).toBe(1);
  });
});&lt;/pre&gt;
&lt;p&gt;&lt;img src="/Media/Default/BlogPost/articles/creer-des-tests-unitaires-avec-angular/karma3.JPG" alt="" width="474" height="408" /&gt;&lt;/p&gt;
&lt;p&gt;Pour mieux organiser encore ses tests, sachez qu'un describe peut contenir un autre describe. Dans le code suivant, j'ai regroup&amp;eacute; mes tests par m&amp;eacute;thode du service test&amp;eacute;. Le r&amp;eacute;sultat est beaucoup plus lisible :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;import { CalculatorService } from './calculator.service';

describe('CalculatorService simple test', () =&amp;gt; {
  let service: CalculatorService;

  beforeEach(() =&amp;gt; {
    service = new CalculatorService();
    console.log('creation...');
  });

  // tests de add
  describe('add', () =&amp;gt; {
    it('should return correct answer', () =&amp;gt; {
      const result = service.add(4, 2);

      expect(result).toBe(6);
    });
  });

  // tests de lowerOrZero
  describe('lowerOrZero', () =&amp;gt; {
    it('Should answer 0 for null', () =&amp;gt; {
      const result = service.lowerOrZero(null);
      expect(result).toBe(0);
    });

    it('Should answer correct answer', () =&amp;gt; {
      const result = service.lowerOrZero([2, 1, 3]);
      expect(result).toBe(1);
    });

    it('Should answer 0 if negative', () =&amp;gt; {
      const result = service.lowerOrZero([2, 1, -3]);
      expect(result).toBe(0);
    });
  });
});
&lt;/pre&gt;
&lt;p&gt;&lt;img src="/Media/Default/BlogPost/articles/creer-des-tests-unitaires-avec-angular/karma4.JPG" alt="" width="663" height="473" /&gt;&lt;/p&gt;
&lt;p&gt;Remarquez dans la console que l'on a bien cr&amp;eacute;e 4 fois le service.&lt;/p&gt;
&lt;p&gt;Ici nous avons utilis&amp;eacute; le Matcher toBe mais jasmine en propose tout une ribambelle et vous pouvez m&amp;ecirc;me cr&amp;eacute;er vos propres matchers :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;toBe,&lt;/li&gt;
&lt;li&gt;toBeFalsy, toBeThrusly,&lt;/li&gt;
&lt;li&gt;toBeGreaterThan, toBeLesserThan, etc.&lt;/li&gt;
&lt;li&gt;toContain,&lt;/li&gt;
&lt;li&gt;toHaveBeenCalled,&lt;/li&gt;
&lt;li&gt;toHaveBeenCalledWith,&lt;/li&gt;
&lt;li&gt;etc, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Et apr&amp;egrave;s ?&lt;/h1&gt;
&lt;p&gt;Ici, nous avons r&amp;eacute;alis&amp;eacute; des tests tr&amp;egrave;s simples mais on peut se poser la question : quid si le service utilise d'autres services ? Peut on 'mocker' facilement ces services ? Comment faire ? Quid des templates html d'Angular ?&lt;/p&gt;
&lt;p&gt;Nous verrons cela dans un prochain &amp;eacute;pisode.&lt;/p&gt;
&lt;p&gt;Suspense...&lt;/p&gt;</description><pubDate>Wed, 29 Aug 2018 15:31:00 GMT</pubDate><guid isPermaLink="true">http://www.c2i.fr:80/articles/creer-des-tests-unitaires-avec-angular</guid></item><item><title>Angular &amp; Microsoft Graph [Part 7] : Astuce, les dates avec Microsoft Graph</title><link>http://www.c2i.fr:80/articles/angular-microsoft-graph-part-7</link><description>&lt;div style="border: 1px solid black; margin: 10px 0 10px 0; padding: 10px;"&gt;
&lt;div style="text-align: center;"&gt;&lt;img src="/Media/Default/BlogPost/articles/angular-microsoft-graph-part-1/AngularMSGraph.png" alt="" width="800" /&gt;&lt;/div&gt;
&lt;h1&gt;Sommaire&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-1"&gt;Introduction - Cr&amp;eacute;er son app Office365&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-2"&gt;S'identifier aupr&amp;egrave;s de MS Graph&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-3"&gt;R&amp;eacute;cup&amp;eacute;ration des calendriers d'un utilisateur&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-4"&gt;Ajouter des &amp;eacute;v&amp;egrave;nements dans son calendrier avec des donn&amp;eacute;es personnalis&amp;eacute;es&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-5"&gt;Charger/T&amp;eacute;l&amp;eacute;charger un fichier sur OneDrive&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-6"&gt;T&amp;eacute;l&amp;eacute;charger une image prot&amp;eacute;g&amp;eacute;e&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-6"&gt;Astuce: les dates de Microsoft Graph&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/RichardC64/MyTjm"&gt;Le projet sur Github&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mytjm.fr"&gt;La d&amp;eacute;mo sur www.myTjm.fr&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;Si vous allez dans la documentation de Microsoft Graph sur les &amp;eacute;v&amp;egrave;nements d'un calendrier, vous verrez que le type des dates de d&amp;eacute;but/fin est un microsoft.graph.dateTimeTimeZone. Arg: K&amp;eacute;zako ?&lt;/p&gt;
&lt;p&gt;Un DateTimeTimeZone comme son nom l'indique contient 2 informations (de type string) qui sont :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;le dateTime qui est une repr&amp;eacute;sentation "simple" de la date avec l'heure,&lt;/li&gt;
&lt;li&gt;le timeZone qui est le nom de la time Zone comme par exemple America/Chihuahua ou Europe/Paris.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(&lt;a href="https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/resources/datetimetimezone"&gt;la doc de DateTimeTimeZone&lt;/a&gt; qui contient la liste de TimeZone).&lt;/p&gt;
&lt;p&gt;Donc par exemple je peux recevoir de l'api au format JSON l'event avec les propri&amp;eacute;t&amp;eacute;s suivante :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;            "start": {
                "dateTime": "2018-08-01T09:00:00.0000000",
                "timeZone": "Europe/Paris"
            },
            "end": {
                "dateTime": "2018-08-01T12:30:00.0000000",
                "timeZone": "Europe/Paris"
            },&lt;/pre&gt;
&lt;p&gt;J'ai ici un rendez-vous qui a eu lieu le 1er Aout 2018 &amp;agrave; 9h du matin, heure de Paris.&lt;/p&gt;
&lt;p&gt;C&amp;ocirc;t&amp;eacute; Typescript/Javascript, il faut bien pouvour travailler avec une vraie repr&amp;eacute;sentation de type Date.&lt;/p&gt;
&lt;p&gt;Le mieux que j'ai trouv&amp;eacute; (mais si vous avez d'autres solutions sans d&amp;eacute;pendances, je suis preneur), c'est d'utiliser le c&amp;eacute;l&amp;egrave;bre momentjs ou plus exactement &lt;a href="https://momentjs.com/timezone/"&gt;Moment TimeZone&lt;/a&gt;. Et donc, d&amp;egrave;s que vous recevez un objet de type DateTimeTimeZone, pour travaillez avec lui en tant que Date (ou plus exactement Moment), la transformation est ais&amp;eacute;e :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;import * as moment from 'moment-timezone';

const mStartTime = moment.tz(dtTz.start.dateTime, dtTz.start.timeZone);
const dStartTime = mStartTime.toDate();&lt;/pre&gt;
&lt;p&gt;Donc m&amp;ecirc;me si vous recevez un &amp;eacute;v&amp;egrave;nement dont le time zone est America/Chihuahua vous pourrez l'exploiter correctement. (c'est quoi d&amp;eacute;j&amp;agrave; le d&amp;eacute;calage horaire entre Chihuahua et Bayonne ?&lt;/p&gt;</description><pubDate>Wed, 29 Aug 2018 07:16:06 GMT</pubDate><guid isPermaLink="true">http://www.c2i.fr:80/articles/angular-microsoft-graph-part-7</guid></item><item><title>Angular &amp; Microsoft Graph [Part 5] : Onedrive</title><link>http://www.c2i.fr:80/articles/angular-microsoft-graph-part-5</link><description>&lt;div style="border: 1px solid black; margin: 10px 0 10px 0; padding: 10px;"&gt;
&lt;div style="text-align: center;"&gt;&lt;img src="/Media/Default/BlogPost/articles/angular-microsoft-graph-part-1/AngularMSGraph.png" alt="" width="800" /&gt;&lt;/div&gt;
&lt;h1&gt;Sommaire&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-1"&gt;Introduction - Cr&amp;eacute;er son app Office365&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-2"&gt;S'identifier aupr&amp;egrave;s de MS Graph&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-3"&gt;R&amp;eacute;cup&amp;eacute;ration des calendriers d'un utilisateur&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-4"&gt;Ajouter des &amp;eacute;v&amp;egrave;nements dans son calendrier avec des donn&amp;eacute;es personnalis&amp;eacute;es&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-5"&gt;Charger/T&amp;eacute;l&amp;eacute;charger un fichier sur OneDrive&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-6"&gt;T&amp;eacute;l&amp;eacute;charger une image prot&amp;eacute;g&amp;eacute;e&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-7"&gt;Astuce: les dates de Microsoft Graph&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/RichardC64/myTjm"&gt;Le projet sur Github&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mytjm.fr"&gt;La d&amp;eacute;mo sur www.myTjm.fr&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;On a vu dans les pr&amp;eacute;c&amp;eacute;dents articles comment jouer avec Microsoft Graph concernant les calendriers et les &amp;eacute;v&amp;egrave;nements. Regardons maintenant comment jouer avec Onedrive.&lt;/p&gt;
&lt;p&gt;Dans l'application, on g&amp;egrave;re un ensemble de clients. Il faut pouvoir sauvegarder l'ensemble de ces clients dans Office365. Une solution simple (mais non viable si l'on a beaucoup de donn&amp;eacute;es), c'est de stocker cela dans un fichier dans OneDrive.&lt;/p&gt;
&lt;p&gt;C'est le cas de notre application (si, si, je vous assure). Donc nous allons sauvegarder et r&amp;eacute;cup&amp;eacute;rer ces donn&amp;eacute;es depuis un fichier OneDrive de l'utilisateur stock&amp;eacute; dans le dossier myTjm/clients.json.&lt;/p&gt;
&lt;p&gt;Pour r&amp;eacute;cup&amp;eacute;rer le contenu d'un fichier, il y a 2 &amp;eacute;tapes :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;la premi&amp;egrave;re consiste &amp;agrave; r&amp;eacute;cup&amp;eacute;rer l'URL du contenu du fichier,&lt;/li&gt;
&lt;li&gt;r&amp;eacute;cup&amp;eacute;rer son contenu &amp;agrave; partir de l'URL pr&amp;eacute;c&amp;eacute;dente.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;L'URL du contenu d'un fichier est accessible dans les donn&amp;eacute;es d'un fichier, et pour r&amp;eacute;cup&amp;eacute;rer les donn&amp;eacute;es d'un fichier, on a une m&amp;eacute;thode GET vers l'URL constitu&amp;eacute;e ainsi :&lt;/p&gt;
&lt;p&gt;me/drive/root:/{chemin du fichier}:/&lt;/p&gt;
&lt;p&gt;Comme ce qui nous int&amp;eacute;resse ce n'est que l'URL du contenu du fichier, on va faire un select :&lt;/p&gt;
&lt;p&gt;me/drive/root:/{chemin du fichier}:/?select=@microsoft.graph.downloadUrl&lt;/p&gt;
&lt;p&gt;Du coup, pour r&amp;eacute;cup&amp;eacute;rer le contenu d'un fichier, j'ai cr&amp;eacute;e un service, FileService avec la m&amp;eacute;thode suivante :&lt;/p&gt;
&lt;pre class="brush:csharp;"&gt;@Injectable({
  providedIn: 'root'
})
export class FilesService extends GraphBaseService {

  constructor(httpClient: HttpClient) { super(httpClient); }

  loadFile(fileName: string) {
    const url = GraphUrl + '/me/drive/root:/myTjm/' + fileName + ':/?select=@microsoft.graph.downloadUrl';
    return this.httpClient.get(url)
      .pipe(
        mergeMap(result =&amp;gt; {

          const downloadUrl = result['@microsoft.graph.downloadUrl'];
          return this.httpClient.get(downloadUrl);
        }));
  }

  //...
}&lt;/pre&gt;
&lt;p&gt;On appele une premi&amp;egrave;re fois pour obtenir l'URL et une seconde fois pour le contenu (remarquez l'utilisation de MergeMap pour retourner l'Observable de rxjs).&lt;/p&gt;
&lt;p&gt;Donc dans mon service ClientsService, il me suffit d'appeler cette m&amp;eacute;thode avec comme nom de fichier clients.json :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;public load(): Observable&amp;lt;IClient[]&amp;gt; {
    return this.filesService.loadFile(ClientsFileName).pipe(
      mergeMap(content =&amp;gt; {
        const clients = &amp;lt;IClient[]&amp;gt;content;
        this.saveLocal(clients);
        return of(clients);
      }),
      catchError(err =&amp;gt; {
        const localDatas: IClient[] = &amp;lt;IClient[]&amp;gt;JSON.parse(localStorage.getItem(ClientsKey));
        return of(localDatas);
      })
    );
  }&lt;/pre&gt;
&lt;p&gt;Remarquez que je sauvegarde le contenu t&amp;eacute;l&amp;eacute;charger dans le localstorage (via la m&amp;eacute;thode saveLocal).&lt;/p&gt;
&lt;p&gt;C'est le m&amp;ecirc;me principe pour sauvegarder un fichier sur OneDrive. Cette fois, on va utiliser un PUT sur l'URL :&lt;/p&gt;
&lt;p&gt;me/drive/root:/{chemin du fichier}:/content&lt;/p&gt;
&lt;p&gt;D'ou la m&amp;eacute;thode dans le FileService pour sauvegarder un fichier filename avec un contenu content :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;saveFile(fileName: string, content: string) {
    const url = GraphUrl + '/me/drive/root:/myTjm/' + fileName + ':/content';
    return this.httpClient.put(url, content);
  }&lt;/pre&gt;
&lt;p&gt;Et dans le service ClientsService, le contenu est le contenu s&amp;eacute;rialis&amp;eacute; en JSON de nos clients :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;private save(clients: IClient[]): Observable&amp;lt;any&amp;gt; {
    this.saveLocal(clients);
    return this.filesService.saveFile(ClientsFileName, JSON.stringify(clients));
  }&lt;/pre&gt;
&lt;p&gt;Lire et enregistrer le contenu d'un fichier sur OneDrive est donc un jeu d'enfant.&lt;/p&gt;</description><pubDate>Wed, 29 Aug 2018 06:51:33 GMT</pubDate><guid isPermaLink="true">http://www.c2i.fr:80/articles/angular-microsoft-graph-part-5</guid></item><item><title>Angular &amp; Microsoft Graph [Part 6] : Télécharger une image protégée</title><link>http://www.c2i.fr:80/articles/angular-microsoft-graph-part-6</link><description>&lt;div style="border: 1px solid black; margin: 10px 0 10px 0; padding: 10px;"&gt;
&lt;div style="text-align: center;"&gt;&lt;img src="/Media/Default/BlogPost/articles/angular-microsoft-graph-part-1/AngularMSGraph.png" alt="" width="800" /&gt;&lt;/div&gt;
&lt;h1&gt;Sommaire&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-1"&gt;Introduction - Cr&amp;eacute;er son app Office365&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-2"&gt;S'identifier aupr&amp;egrave;s de MS Graph&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-3"&gt;R&amp;eacute;cup&amp;eacute;ration des calendriers d'un utilisateur&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-4"&gt;Ajouter des &amp;eacute;v&amp;egrave;nements dans son calendrier avec des donn&amp;eacute;es personnalis&amp;eacute;es&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-5"&gt;Charger/T&amp;eacute;l&amp;eacute;charger un fichier sur OneDrive&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-6"&gt;T&amp;eacute;l&amp;eacute;charger une image prot&amp;eacute;g&amp;eacute;e&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-7"&gt;Astuce: les dates de Microsoft Graph&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/RichardC64/myTjm"&gt;Le projet sur Github&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mytjm.fr"&gt;La d&amp;eacute;mo sur www.myTjm.fr&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;Un dernier petit point sur cette application concerne l'affiche de l'image de l'utilisateur dans la barre de menu :&lt;/p&gt;
&lt;p&gt;&lt;img src="/Media/Default/BlogPost/articles/msGraph10.JPG" alt="" width="338" height="53" /&gt;&lt;/p&gt;
&lt;p&gt;Cela peut paraitre b&amp;ecirc;te, mais cette image est dans Office365 donc prot&amp;eacute;g&amp;eacute; par notre token oAuth. Si l'on met juste un tag avec un lien vers l'image, on aura une erreur de s&amp;eacute;curit&amp;eacute; (contenu interdit).&lt;/p&gt;
&lt;p&gt;L'URL de la miniature de l'utilisateur est&amp;nbsp;https://graph.microsoft.com/v1.0/me/photo/$value. Donc mettre :&lt;/p&gt;
&lt;div style="font-family: Consolas, 'Courier New', monospace; font-size: 14px; line-height: 19px; white-space: pre;"&gt;&lt;span style="color: #800000;"&gt;&amp;lt;img&lt;/span&gt; &lt;span style="color: #ff0000;"&gt;src&lt;/span&gt;=&lt;span style="color: #0000ff;"&gt;'https://graph.microsoft.com/v1.0/me/photo/$value'&lt;/span&gt; &lt;span style="color: #ff0000;"&gt;class&lt;/span&gt;=&lt;span style="color: #0000ff;"&gt;'mat-mini-fab'&lt;/span&gt; &lt;span style="color: #ff0000;"&gt;[matMenuTriggerFor]&lt;/span&gt;=&lt;span style="color: #0000ff;"&gt;"menu"&lt;/span&gt;&lt;span style="color: #800000;"&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;p&gt;ne fonctionnera pas. Il faut intercepter l'appel Http de l'image. Pour cela, nous allons cr&amp;eacute;er une nouvelle directive et utiliser URL.createObjectURL :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;import { HttpClient } from '@angular/common/http';
import { Directive, ElementRef, Input, HostListener } from '@angular/core';
import { map } from 'rxjs/operators';

@Directive({
  selector: '[appHttpSrc]'
})
export class HttpSrcDirective {
  private _appHttpSrc: string;
  private _url: string;

  constructor(
    private el: ElementRef,
    private http: HttpClient) {
  }

  public get appHttpSrc(): string {
    return this._appHttpSrc;
  }
  @Input()
  public set appHttpSrc(v: string) {
    this._appHttpSrc = v;
    // on fait un appel Http pour r&amp;eacute;cup&amp;eacute;re l'image(format blob)
    this.http.get(v, { responseType: 'blob' }).pipe(
      map(val =&amp;gt; {
        // on cr&amp;eacute;e l'url
        return URL.createObjectURL(val);
      }))
      .subscribe(url =&amp;gt; {
        // on dit au tag img que son url c'est l'url cr&amp;eacute;e ci-dessus
        this.el.nativeElement.src = url;
      });
  }

  @HostListener('load') onLoad() {
    if (this._url != null) {
      // on lib&amp;egrave;re la m&amp;eacute;moire du blob sauvegard&amp;eacute; 
      URL.revokeObjectURL(this._url);
    }
  }
}
&lt;/pre&gt;
&lt;p&gt;Remarquez l'appel &amp;agrave; revokeObjectURL quand l'image a &amp;eacute;t&amp;eacute; t&amp;eacute;l&amp;eacute;charg&amp;eacute;e pour lib&amp;eacute;rer de la m&amp;eacute;moire l'URL inutile.&lt;/p&gt;</description><pubDate>Wed, 29 Aug 2018 06:51:22 GMT</pubDate><guid isPermaLink="true">http://www.c2i.fr:80/articles/angular-microsoft-graph-part-6</guid></item><item><title>Angular &amp; Microsoft Graph [Part 3] : Récupération des calendriers </title><link>http://www.c2i.fr:80/articles/angular-microsoft-graph-part-3</link><description>&lt;div style="border: 1px solid black; margin: 10px 0 10px 0; padding: 10px;"&gt;
&lt;div style="text-align: center;"&gt;&lt;img src="/Media/Default/BlogPost/articles/angular-microsoft-graph-part-1/AngularMSGraph.png" alt="" width="800" /&gt;&lt;/div&gt;
&lt;h1&gt;Sommaire&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-1"&gt;Introduction - Cr&amp;eacute;er son app Office365&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-2"&gt;S'identifier aupr&amp;egrave;s de MS Graph&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-3"&gt;R&amp;eacute;cup&amp;eacute;ration des calendriers d'un utilisateur&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-4"&gt;Ajouter des &amp;eacute;v&amp;egrave;nements dans son calendrier avec des donn&amp;eacute;es personnalis&amp;eacute;es&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-5"&gt;Charger/T&amp;eacute;l&amp;eacute;charger un fichier sur OneDrive&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-6"&gt;T&amp;eacute;l&amp;eacute;charger une image prot&amp;eacute;g&amp;eacute;e&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-7"&gt;Astuce: les dates de Microsoft Graph&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/RichardC64/myTjm"&gt;Le projet sur Github&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mytjm.fr"&gt;La d&amp;eacute;mo sur www.myTjm.fr&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;Dans cet article, nous allons voir comment r&amp;eacute;cup&amp;eacute;rer la liste des calendriers Office365 de l'utilisateur. L'api pour cela est simple, c'est juste un GET vers l'URL /me/calendars. Mais pour avoir quelque chose de plus g&amp;eacute;n&amp;eacute;rique et qui va nous servir pour les autres appels, nous allons cr&amp;eacute;er une classe/service de base.&lt;/p&gt;
&lt;p&gt;Cette classe contiendra juste 2 m&amp;eacute;thodes, l'une qui encapsule les appels des m&amp;eacute;thodes REST qui ne renvoi qu'une seule entit&amp;eacute; et l'autre qui renvoi un tableau d'entit&amp;eacute; :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;import { HttpHeaders } from '@angular/common/http';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

import * as MicrosoftGraph from '@microsoft/microsoft-graph-types';

export const GraphUrl = 'https://graph.microsoft.com/v1.0/';

export class GraphBaseService {
  constructor(
    protected httpClient: HttpClient) { }

  protected getClient&amp;lt;T&amp;gt;(api: string, select: string = null, args: string = null): Observable&amp;lt;T&amp;gt; {
    const queryParams = [];
    if (select) { queryParams.push('$select=' + select); }
    if (args) { queryParams.push(args); }
    const url = GraphUrl + api + '?' + queryParams.join('&amp;amp;');
    return this.httpClient.get&amp;lt;T&amp;gt;(url);
  }

  protected getClientArray&amp;lt;T&amp;gt;(api: string, select: string = null, args: string[] = null): Observable&amp;lt;ArrayResponse&amp;lt;T&amp;gt;&amp;gt; {
    let queryParams = [];
    if (select) { queryParams.push('$select=' + select); }
    if (args) { queryParams = queryParams.concat(args); }

    const url = GraphUrl + api + '?' + queryParams.join('&amp;amp;');
    const headers = new HttpHeaders()
      .set('prefer', 'outlook.timezone=\"Europe/Paris\"');
    return this.httpClient.get&amp;lt;ArrayResponse&amp;lt;T&amp;gt;&amp;gt;(url, { headers: headers });
  }
}

export class ArrayResponse&amp;lt;T&amp;gt; {
  value: T[];
}&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;Ces m&amp;eacute;thodes ont des arguments optionnels pour laisser la libert&amp;eacute; au code client et ce sont des m&amp;eacute;thodes g&amp;eacute;n&amp;eacute;riques.&lt;/p&gt;
&lt;p&gt;De fa&amp;ccedil;on g&amp;eacute;n&amp;eacute;ral, quand on appele une api REST de MS Graph, elle retorune l'ensemble des propri&amp;eacute;t&amp;eacute;s directes de l'entit&amp;eacute;. Si l'on veut optimiser la requ&amp;ecirc;te et ne r&amp;eacute;cup&amp;eacute;rer qu'une partie des informations, il suffit d'ajouter $select=liste des propri&amp;eacute;t&amp;eacute;s voulues dans l'url.&lt;/p&gt;
&lt;p&gt;Pour les propri&amp;eacute;t&amp;eacute;s qui sont plus complexes (plus profondes dans l'arbre de l'objet), ce sont des arguments suppl&amp;eacute;mentaires que l'on doit passer (d'ou le param&amp;egrave;tre args).&lt;/p&gt;
&lt;p&gt;Regardons avec les calendriers le code client.&lt;/p&gt;
&lt;h2&gt;R&amp;eacute;cup&amp;eacute;ration des calendriers&lt;/h2&gt;
&lt;p&gt;Dans notre application, on n'a besoin que du nom du calendrier. D'ou notre classe calendarsService :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { GraphBaseService } from '@services/msgraph';
import { HttpClient } from '@angular/common/http';
import * as MicrosoftGraph from '@microsoft/microsoft-graph-types';
import { map } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class CalendarsService extends GraphBaseService {

  constructor(
    httpClient: HttpClient
  ) {
    super(httpClient);
  }

  /**Liste des calendriers du user connect&amp;eacute; */
  getCalendars(): Observable&amp;lt;MicrosoftGraph.Calendar[]&amp;gt; {
    return this.getClientArray&amp;lt;MicrosoftGraph.Calendar&amp;gt;('me/calendars', 'name, color, canEdit, owner, events')
      .pipe(map(r =&amp;gt; r.value));
  }
}
&lt;/pre&gt;
&lt;p&gt;Ce service h&amp;eacute;rite de notre service de base GraphBaseService, appele l'url me/calendars et demande les seulement les propri&amp;eacute;t&amp;eacute;s name, color, etc...&lt;/p&gt;
&lt;p&gt;Simple non ?&lt;/p&gt;
&lt;p&gt;Toutes les autres classes/services de notre application seront du m&amp;ecirc;me type.&lt;/p&gt;</description><pubDate>Wed, 29 Aug 2018 06:50:43 GMT</pubDate><guid isPermaLink="true">http://www.c2i.fr:80/articles/angular-microsoft-graph-part-3</guid></item><item><title>Angular &amp; Microsoft Graph [Part 4] : Evènements dans le calendrier avec données personnalisées</title><link>http://www.c2i.fr:80/articles/angular-microsoft-graph-part-4</link><description>&lt;div style="border: 1px solid black; margin: 10px 0 10px 0; padding: 10px;"&gt;
&lt;div style="text-align: center;"&gt;&lt;img src="/Media/Default/BlogPost/articles/angular-microsoft-graph-part-1/AngularMSGraph.png" alt="" width="800" /&gt;&lt;/div&gt;
&lt;h1&gt;Sommaire&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-1"&gt;Introduction - Cr&amp;eacute;er son app Office365&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-2"&gt;S'identifier aupr&amp;egrave;s de MS Graph&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-3"&gt;R&amp;eacute;cup&amp;eacute;ration des calendriers d'un utilisateur&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-4"&gt;Ajouter des &amp;eacute;v&amp;egrave;nements dans son calendrier avec des donn&amp;eacute;es personnalis&amp;eacute;es&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-5"&gt;Charger/T&amp;eacute;l&amp;eacute;charger un fichier sur OneDrive&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-6"&gt;T&amp;eacute;l&amp;eacute;charger une image prot&amp;eacute;g&amp;eacute;e&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-7"&gt;Astuce: les dates de Microsoft Graph&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/RichardC64/myTjm"&gt;Le projet sur Github&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mytjm.fr"&gt;La d&amp;eacute;mo sur www.myTjm.fr&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;Nous avons vu dans les pr&amp;eacute;c&amp;eacute;dents articles comment s'identifier aupr&amp;egrave;s de Microsoft Graph et r&amp;eacute;cup&amp;eacute;rer la liste de ses calendriers.&lt;/p&gt;
&lt;p&gt;Dans notre application, nous voulons pouvoir ajouter des &amp;eacute;v&amp;egrave;nements dans ce calendrier pour enregistrer nos interventions chez nos clients. Personnellement, je facture &amp;agrave; la demi journ&amp;eacute;e. Donc on va obliger l'application a ne cr&amp;eacute;er des rendez-vous que d'une demi-journ&amp;eacute;e (matin et/ou apr&amp;egrave;s-midi).&lt;/p&gt;
&lt;p&gt;Si l'on regarde dans Outlook, cela donnera cela :&lt;/p&gt;
&lt;p&gt;&lt;img src="/Media/Default/BlogPost/articles/msGraph8.JPG" alt="" width="802" height="595" /&gt;&lt;/p&gt;
&lt;p&gt;Mais dans notre application, cela apparaitra comme cela :&lt;/p&gt;
&lt;p&gt;&lt;img src="/Media/Default/BlogPost/articles/msGraph9.JPG" alt="" width="1039" height="232" /&gt;&lt;/p&gt;
&lt;p&gt;Il n'y a pas d'infos sur les heures de d&amp;eacute;but/fin, juste un code couleur qui indique que c'est le matin et l'apr&amp;egrave;s-midi.&lt;/p&gt;
&lt;p&gt;Mais le plus important, c'est que chaque rendez-vous/&amp;eacute;v&amp;egrave;nement poss&amp;egrave;de des informations compl&amp;eacute;mentaires importantes comme le nom du client et le co&amp;ucirc;t de l'intervention sur la demi-journ&amp;eacute;e. Ces informations suppl&amp;eacute;mentaires, Office365 nous propose plusieurs fa&amp;ccedil;on de proc&amp;eacute;der. Ici, j'ai choisi les&amp;nbsp;SingleValueLegacyExtendedProperty (&lt;a href="https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/resources/singlevaluelegacyextendedproperty"&gt;doc sur le site de MS&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;On peut ainsi ajouter des propri&amp;eacute;t&amp;eacute;s qui nous sont propres &amp;agrave; un &amp;eacute;v&amp;egrave;nement. Elles peuvent &amp;ecirc;tre de type String, Integer ou String Array ou Integer Array.&lt;/p&gt;
&lt;p&gt;Ici, j'ai choisi de n'ajouter qu'une seule propri&amp;eacute;t&amp;eacute; personnalis&amp;eacute; de type String, qui sera en r&amp;eacute;alit&amp;eacute; une s&amp;eacute;rialisation JSON de mon objet contenant les prorpi&amp;eacute;t&amp;eacute;s suppl&amp;eacute;mentaires.&lt;/p&gt;
&lt;p&gt;Pour g&amp;eacute;rer cette propri&amp;eacute;t&amp;eacute; suppl&amp;eacute;mentaire vous aurez besoin d'un String qui aura cet aspect l&amp;agrave; :&lt;/p&gt;
&lt;div style="font-family: Consolas, 'Courier New', monospace; font-size: 14px; line-height: 19px; white-space: pre;"&gt;&lt;span style="color: #0000ff;"&gt;const&lt;/span&gt; &lt;span style="color: #001080;"&gt;MyTjmProperty&lt;/span&gt; = &lt;span style="color: #a31515;"&gt;'String {7F477958-DE03-4B4F-89FC-284980AF6C92} Name MyTjmProperties'&lt;/span&gt;;&lt;/div&gt;
&lt;p&gt;On a donc le type suivi d'un Guid suivi de son nom (MyTjmProperties).&lt;/p&gt;
&lt;p&gt;Pour cr&amp;eacute;er un &amp;eacute;v&amp;egrave;nement dans un calendrier, il suffit d'appeler la m&amp;eacute;thode POST de l'URL /me/calendars/{id du calendar}/events avec dans le body, les propri&amp;eacute;t&amp;eacute;s de notre &amp;eacute;v&amp;egrave;nement. c'est l&amp;agrave; que l'on va ajouter les donn&amp;eacute;es sp&amp;eacute;cifiques :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;  private createEventImpl(startDate: Date, endDate: Date, tjmEvent: ITjmEventData) {
    const parameters = this.parametersService.load();

    // on utilise le calendrier par d&amp;eacute;faut ou le calendrier sp&amp;eacute;cifi&amp;eacute; dans les param&amp;egrave;tres
    const url = parameters == null ?
      'me/events' :
      GraphUrl + '/me/calendars/' + parameters.calendar.id + '/events';

      // on est en France que diable !
    const body = {
      subject: tjmEvent.client.name,
      start: {
        dateTime: startDate.toISOString(),
        timeZone: 'Europe/Paris'
      },
      end: {
        dateTime: endDate.toISOString(),
        timeZone: 'Europe/Paris'
      },
      isReminderOn: false,
      categories: ['myTjm'],
      singleValueExtendedProperties: [{
        id: MyTjmProperty,
        value: JSON.stringify(tjmEvent) // les donn&amp;eacute;es JSON s&amp;eacute;rialis&amp;eacute;es
      }
      ]
    };
    return this.httpClient.post(url, body);
  }&lt;/pre&gt;
&lt;p&gt;On voit que l'on sp&amp;eacute;cifie la propri&amp;eacute;t&amp;eacute; singleValueExtendedProperties avec comme id notre MyTjmProperty d&amp;eacute;finie plus haut.&lt;/p&gt;
&lt;p&gt;Pour r&amp;eacute;cup&amp;eacute;rer que les events du calendrier qui poss&amp;egrave;de cette propri&amp;eacute;t&amp;eacute; sp&amp;eacute;cifique, on r&amp;eacute;cup&amp;egrave;re l'ensemble des &amp;eacute;v&amp;egrave;nements du calendrier avec la m&amp;eacute;thode GET de l'URL /me/calendars/{id du calendrier}/calendarView et on filtre sur ceux poss&amp;eacute;dant cette propri&amp;eacute;t&amp;eacute; :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;getEvents(startDate: moment.Moment, endDate: moment.Moment): Observable&amp;lt;ITjmEvent[]&amp;gt; {
    const parameters = this.parametersService.load();

    const url = parameters == null ?
      'me/calendarView' :
      '/me/calendars/' + parameters.calendar.id + '/calendarView';

    return this.getClientArray&amp;lt;MicrosoftGraph.Event&amp;gt;(
      url,
      null,
      [
        'startdatetime=' + startDate.toISOString(),
        'enddatetime=' + endDate.toISOString(),
        '$expand=singleValueExtendedProperties($filter= id eq \'' + MyTjmProperty + '\')',
        '$top=100'
      ]).pipe(
        map(r =&amp;gt; {
          const result: ITjmEvent[] = [];
          r.value.forEach(item =&amp;amp;gt: {
            const tjmDatas = this.getTjmDatas(item);
            if (item != null &amp;amp;&amp;amp; tjmDatas != null) {

              result.push({
                outlookEvent: item,
                datas: tjmDatas
              });
            }
          });
          return result;
        })
      );
  }

  private getTjmDatas(event: any): ITjmEventData {
    const instance = event as ISingleValueExtendedPropertiesOwner;
    if (instance == null) { return null; }
    const props = instance.singleValueExtendedProperties;
    if (props == null || props.length === 0 || props[0].value == null) { return null; }

    return &amp;lt;ITjmEventData&amp;gt;JSON.parse(props[0].value);
  }&lt;/pre&gt;
&lt;p&gt;Vous remarquerez que j'ai utilis&amp;eacute; la composition pour la r&amp;eacute;cup&amp;eacute;ration des donn&amp;eacute;es, c'est &amp;agrave; dire que l'on a une entit&amp;eacute; qui a deux propri&amp;eacute;t&amp;eacute;, l'une de type Event de Microsoft, l'autre de type sp&amp;eacute;cifique &amp;agrave; mes donn&amp;eacute;es propres.&lt;/p&gt;</description><pubDate>Wed, 29 Aug 2018 06:50:32 GMT</pubDate><guid isPermaLink="true">http://www.c2i.fr:80/articles/angular-microsoft-graph-part-4</guid></item><item><title>Angular &amp; Microsoft Graph [Part 2] : Identification</title><link>http://www.c2i.fr:80/articles/angular-microsoft-graph-part-2</link><description>&lt;div style="border: 1px solid black; margin: 10px 0 10px 0; padding: 10px;"&gt;
&lt;div style="text-align: center;"&gt;&lt;img src="/Media/Default/BlogPost/articles/angular-microsoft-graph-part-1/AngularMSGraph.png" alt="" width="800" /&gt;&lt;/div&gt;
&lt;h1&gt;Sommaire&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-1"&gt;Introduction - Cr&amp;eacute;er son app Office365&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-2"&gt;S'identifier aupr&amp;egrave;s de MS Graph&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-3"&gt;R&amp;eacute;cup&amp;eacute;ration des calendriers d'un utilisateur&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-4"&gt;Ajouter des &amp;eacute;v&amp;egrave;nements dans son calendrier avec des donn&amp;eacute;es personnalis&amp;eacute;es&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-5"&gt;Charger/T&amp;eacute;l&amp;eacute;charger un fichier sur OneDrive&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-6"&gt;T&amp;eacute;l&amp;eacute;charger une image prot&amp;eacute;g&amp;eacute;e&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-7"&gt;Astuce: les dates de Microsoft Graph&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/RichardC64/MyTjm"&gt;Le projet sur Github&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mytjm.fr"&gt;La d&amp;eacute;mo sur www.myTjm.fr&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2&gt;R&amp;eacute;cup&amp;eacute;ration du token oAuth2&lt;/h2&gt;
&lt;p&gt;L'identification repose sur oAuth2. C'est-&amp;agrave;-dire que Microsoft va vous g&amp;eacute;n&amp;eacute;rer un token que vous devrez envoyer aux apis d'Office365 pour g&amp;eacute;rer la s&amp;eacute;curit&amp;eacute;.&lt;/p&gt;
&lt;p&gt;NB: Nous allons faire ici tout cela "&amp;agrave; la main", c'est-&amp;agrave;-dire que nous n'allons pas utiliser des librairies propos&amp;eacute;es par Microsoft ou par des &amp;eacute;diteurs/d&amp;eacute;veloppeurs tiers pour bien comprendre comment cela fonctionne. Dans les exemples fournis par Microsoft, ils utilisent la librairie hellojs qui est compatible avec d'autres services que MS Graph comme Google, Facebook &amp;amp; co.&lt;/p&gt;
&lt;p&gt;Pour cela, Microsoft met &amp;agrave; notre disposition une page Web h&amp;eacute;berg&amp;eacute;e sur leur serveur dont l'URL est du type&amp;nbsp;https://login.microsoftonline.com/common/oauth2/v2.0/authorize avec plein d'autres param&amp;egrave;tres. Nous allons stocker ces param&amp;egrave;tres dans le fichier environment.ts de notre application angular :&lt;/p&gt;
&lt;p&gt;&lt;img width="743" height="199" alt="" src="/Media/Default/BlogPost/articles/angular-microsoft-graph-part-2/msGraph5.JPG" /&gt;&lt;/p&gt;
&lt;p&gt;appId est le Guid g&amp;eacute;n&amp;eacute;r&amp;eacute; sur le site de param&amp;eacute;trage de votre application Office365 (cf article pr&amp;eacute;c&amp;eacute;dent).&lt;/p&gt;
&lt;p&gt;Dans le fichier environment.prod.ts, le contenu est identique sauf que l'URL du callback est l'URL d&amp;eacute;finitive de notre site en production (https://www.myTjm/callback).&lt;/p&gt;
&lt;p&gt;Quand on appelle la page de login de Microsoft avec les bons param&amp;egrave;tres, l'utilisateur est invit&amp;eacute; &amp;agrave; entrer ses identifiants. Quand il clique sur Login, Microsoft v&amp;eacute;rifie que ses identifiants sont les bons, puis redirige le navigateur vers votre page de callback avec dans l'URL, le token de s&amp;eacute;curit&amp;eacute; oAuth que nous allons devoir sauvegarder pour les appels suivants.&lt;/p&gt;
&lt;p&gt;J'ai donc cr&amp;eacute;er un service, AuthService, qui contient une m&amp;eacute;thode login :&lt;/p&gt;
&lt;pre class="brush: c#;"&gt;public login() {
    const url = this.getUrl();
    const width = 525,
      height = 525,
      screenX = window.screenX,
      screenY = window.screenY,
      outerWidth = window.outerWidth,
      outerHeight = window.outerHeight;

    const left = screenX + Math.max(outerWidth - width, 0) / 2;
    const top = screenY + Math.max(outerHeight - height, 0) / 2;

    const features = [
      'width=' + width,
      'height=' + height,
      'top=' + top,
      'left=' + left,
      'status=no',
      'resizable=yes',
      'toolbar=no',
      'menubar=no',
      'scrollbars=yes'];
    const popup = window.open(url, 'oauth', features.join(','));
    if (!popup) {
      alert('failed to pop up auth window');
    }
  }&lt;/pre&gt;
&lt;p&gt;Et la m&amp;eacute;thode getUrl() :&lt;/p&gt;
&lt;pre class="brush: c#;"&gt;private getUrl(): string {
    let url = 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize';
    url += '?client_id=' + environment.appId;
    url += '&amp;amp;response_type=token';
    url += '&amp;amp;redirect_uri=' + encodeURIComponent(environment.callback);
    url += '&amp;amp;scope=' + encodeURIComponent(environment.scope);
    return url;
  }&lt;/pre&gt;
&lt;p&gt;Vous voyez que dans les param&amp;egrave;tres de l'URL appel&amp;eacute;e, on passe l'appId, le type de r&amp;eacute;ponse attendu (token), l'URL de redirection (votre page callback) et les autorisations demand&amp;eacute;es (scope).&lt;/p&gt;
&lt;p&gt;&lt;img width="1171" height="672" alt="" src="/Media/Default/BlogPost/articles/angular-microsoft-graph-part-2/msGraph6.JPG" /&gt;&lt;/p&gt;
&lt;p&gt;Donc dans notre application, il suffit d'appeler cette m&amp;eacute;thode login qui fera apparaitre dans une fen&amp;ecirc;tre popup le site d'identification de Microsoft. Quand l'utilisateur se sera identifi&amp;eacute;, Microsoft redirigera cette fen&amp;ecirc;tre popup vers notre page callback.&lt;/p&gt;
&lt;p&gt;On va donc cr&amp;eacute;er un composant callback dans notre application dont le code ne fera qu'appeler la m&amp;eacute;thode onCallback de notre service authService :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;import { Component, OnInit } from '@angular/core';
import { AuthService } from '@services/msgraph';

@Component({
  selector: 'app-callback',
  template: ''
})
export class CallbackComponent implements OnInit {
  constructor(private authService: AuthService) { }

  ngOnInit() {
    this.authService.onCallback();
  }
}&lt;/pre&gt;
&lt;p&gt;La seule chose que fait ce composant, c'est d'appeler le service qui va :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;R&amp;eacute;cup&amp;eacute;rer le token de l'url et le sauvegarder dans le localstorage,&lt;/li&gt;
&lt;li&gt;fermer le popup qu'il avait ouvert,&lt;/li&gt;
&lt;li&gt;demander &amp;agrave; la fen&amp;ecirc;tre parente du popup de se rafraichir.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="brush: csharp;"&gt;  /** Appel&amp;eacute;e par le callbackComponent */
  public onCallback() {
    const token = this.getTokenFromURL();

    if (token) {
      this.saveAuthToken(token);
    }
    window.opener.location.reload();
    window.close();
  }

  private getTokenFromURL(): IMsGraphToken {
    if (window.location.hash) {
      const authResponse = window.location.hash.substring(1);
      const authInfo = JSON.parse(
        '{' + authResponse.replace(/([^=]+)=([^&amp;amp;]+)&amp;amp;?/g, '"$1":"$2",').slice(0, -1) + '}',
        (key, value) =&amp;gt; key === '' ? value : decodeURIComponent(value));
      return authInfo;
    } else {
      alert('failed to receive auth token');
    }
  }&lt;/pre&gt;
&lt;p&gt;NB: Le code de la m&amp;eacute;thode getAuthToken analyse les param&amp;egrave;tres de l'URL. J'avoue avoir piqu&amp;eacute; ce code sur l'un des exemples de Microsoft, mais je me souviens plus ou !!!&lt;/p&gt;
&lt;p&gt;Une fois l'utilisateur authentifi&amp;eacute;, on a bien dans le localstorage le token (et des informations suppl&amp;eacute;mentaires comme la date d'expiration) :&lt;/p&gt;
&lt;p&gt;&lt;img width="730" height="520" alt="" src="/Media/Default/BlogPost/articles/angular-microsoft-graph-part-2/msGraph7.JPG" /&gt;&lt;/p&gt;
&lt;h2&gt;Interception des appels&lt;/h2&gt;
&lt;p&gt;Maintenant qu'on a bien le token d'identification, d&amp;egrave;s que l'on va vouloir appeler une api REST de Microsoft Graph, il faudra passer dans le header de la requ&amp;ecirc;te ce token. Ce token sera mis &amp;agrave; disposition toujours par notre service AuthService avec la m&amp;eacute;thode getAuthToken qui renvoi une interface que j'ai d&amp;eacute;finie IMsGraphToken :&lt;/p&gt;
&lt;pre class="brush: csharp"&gt;  /** Retourne le token d'identification du user dans Graph */
  public getAuthToken(): IMsGraphToken {
    return &amp;lt;imsgraphtoken&amp;gt;JSON.parse(localStorage.getItem(this.tokenKey)); }

export interface IMsGraphToken {
    access_token: string;
    expires_in: number;
    scope: string;
    session_state: string;
}&lt;/pre&gt;
&lt;p&gt;Nous allons donc cr&amp;eacute;er un intercepteur angular "classique" :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
import { Observable } from 'rxjs';
import { AuthService, GraphUrl } from '@services/msgraph';

@Injectable({
  providedIn: 'root'
})
export class AuthInterceptorService implements HttpInterceptor {

  constructor(private authService: AuthService) { }

  intercept(req: HttpRequest&amp;lt;any&amp;gt;, next: HttpHandler):
    Observable&amp;lt;HttpEvent&amp;lt;any&amp;gt;&amp;gt; {
    const token = this.authService.getAuthToken();
    if (token == null) { return next.handle(req); }
    if (token.access_token == null) { return next.handle(req); }

    // ne change le header que si l'URL commence par https://graph.microsoft.com/v1.0/ d&amp;eacute;finie dans la constante GraphUrl
    if (!req.url.startsWith(GraphUrl)) {
      return next.handle(req);
    }

    const authReq = req.clone({
      headers:
        req.headers
          .set('Authorization', 'Bearer ' + token.access_token)
          .set('Content-Type', 'application/json')
    });

    return next.handle(authReq);
  }
}
&lt;/pre&gt;
&lt;p&gt;N'oublions pas de l'ajouter dans le providers de notre app.module.ts :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;  providers: [
    { provide: LOCALE_ID, useValue: 'fr' },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptorService,
      multi: true
    },
  ],&lt;/pre&gt;
&lt;p&gt;Et voila, d&amp;egrave;s que l'on dera une requ&amp;ecirc;te Http vers MS Graph, notre token sera ajout&amp;eacute; et l'on sera authentifi&amp;eacute;.&lt;/p&gt;</description><pubDate>Wed, 29 Aug 2018 06:49:40 GMT</pubDate><guid isPermaLink="true">http://www.c2i.fr:80/articles/angular-microsoft-graph-part-2</guid></item><item><title>Angular &amp; Microsoft Graph [Part 1]</title><link>http://www.c2i.fr:80/articles/angular-microsoft-graph-part-1</link><description>&lt;div style="border: 1px solid black; margin: 10px 0 10px 0; padding: 10px;"&gt;
&lt;div style="text-align: center;"&gt;&lt;img src="/Media/Default/BlogPost/articles/angular-microsoft-graph-part-1/AngularMSGraph.png" alt="" width="800" /&gt;&lt;/div&gt;
&lt;h1&gt;Sommaire&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-1"&gt;Introduction - Cr&amp;eacute;er son app Office365&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-2"&gt;S'identifier aupr&amp;egrave;s de MS Graph&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-3"&gt;R&amp;eacute;cup&amp;eacute;ration des calendriers d'un utilisateur&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-4"&gt;Ajouter des &amp;eacute;v&amp;egrave;nements dans son calendrier avec des donn&amp;eacute;es personnalis&amp;eacute;es&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-5"&gt;Charger/T&amp;eacute;l&amp;eacute;charger un fichier sur OneDrive&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-6"&gt;T&amp;eacute;l&amp;eacute;charger une image prot&amp;eacute;g&amp;eacute;e&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/angular-microsoft-graph-part-7"&gt;Astuce: les dates de Microsoft Graph&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/RichardC64/MyTjm"&gt;Le projet sur Github&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mytjm.fr"&gt;La d&amp;eacute;mo sur www.myTjm.fr&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;Comme beaucoup d'entre vous, je suis d&amp;eacute;veloppeur/architecte/maitre du monde ind&amp;eacute;pendant qui facture ses interventions &amp;agrave; la journ&amp;eacute;e, voire &amp;agrave; la demi-journ&amp;eacute;e.&lt;/p&gt;
&lt;p&gt;Toutes les fins de mois, je dois donc faire mes factures aupr&amp;egrave;s de mes fortun&amp;eacute;s clients pour leur envoyer ma modeste facture.&lt;/p&gt;
&lt;p&gt;J'avoue n'avoir par 250 000 clients en m&amp;ecirc;me temps et donc jusqu'&amp;agrave; pr&amp;eacute;sent, je g&amp;eacute;rais cela "&amp;agrave; la main".&lt;/p&gt;
&lt;p&gt;Avec Office365, il m'est venu l'id&amp;eacute;e d'exploiter la plateforme afin de g&amp;eacute;rer ces interventions. L'id&amp;eacute;e est de cr&amp;eacute;er une application 100% Angular (donc cliente) avec comme unique backend Office365.&lt;/p&gt;
&lt;p&gt;Bonne nouvelle, Microsoft propose des interfaces REST des donn&amp;eacute;es stock&amp;eacute;es dans Office365 via Microsoft Graph.&lt;/p&gt;
&lt;p&gt;Gr&amp;acirc;ce &amp;agrave; cette application, nous allons voir comment se connecter &amp;agrave; Microsoft Graph, r&amp;eacute;cup&amp;eacute;rer/ajouter des rendez-vous dans un calendrier Outlook, enregistrer/lire des fichiers dans OneDrive, etc.&lt;/p&gt;
&lt;p&gt;R&amp;eacute;gardons &amp;agrave; quoi l'application ressemble :&lt;/p&gt;
&lt;p&gt;&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/YRMbaJ476to" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen=""&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;h2&gt;Cr&amp;eacute;er son application Office365&lt;/h2&gt;
&lt;p&gt;La premi&amp;egrave;re &amp;eacute;tape consiste &amp;agrave; r&amp;eacute;f&amp;eacute;rencer notre application web aupr&amp;egrave;s d'Office365. Rendez-vous donc sur le site de Microsoft et identifiez-vous :&amp;nbsp;&lt;a href="https://developer.microsoft.com/en-us/graph"&gt;https://developer.microsoft.com/en-us/graph&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Puis allez dans my apps :&lt;/p&gt;
&lt;p&gt;&lt;img src="/Media/Default/BlogPost/articles/angular-microsoft-graph-part-1/msGraph1.JPG" alt="" width="800" height="240" /&gt;&lt;/p&gt;
&lt;p&gt;Dans la cat&amp;eacute;gorie Applications convergentes, cliquez sur Ajouter une application.&lt;/p&gt;
&lt;p&gt;C'est l&amp;agrave; que vous allez g&amp;eacute;rer ce qui sera accessible potentiellement pour votre application. Outre son nom, vous devrez renseigner les autorisations pour Microsoft Graph et le type de plateforme (le reste est pas tr&amp;egrave;s important pour l'instant). Donc ajoutez une platerofme de type Web :&lt;/p&gt;
&lt;p&gt;&lt;img src="/Media/Default/BlogPost/articles/angular-microsoft-graph-part-1/msGraph2.JPG" alt="" width="957" height="779" /&gt;&lt;/p&gt;
&lt;p&gt;Le param&amp;eacute;trage d'une plateforme de type Web demande de d&amp;eacute;finir (au moins) l'URL de redirection. Nous allons en d&amp;eacute;finir 2 :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;http://localhost:4200/callback pour nos tests de d&amp;eacute;veloppement,&lt;/li&gt;
&lt;li&gt;https://www.mytjm.fr/callback pour la mise en production.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;NB : l'URL de redirection demande maintenant d'&amp;ecirc;tre h&amp;eacute;berg&amp;eacute;e obligatoirement sur un site s&amp;eacute;curis&amp;eacute; de type https.&lt;/p&gt;
&lt;p&gt;Nous verrons dans l'article suivant comment fonctionne concr&amp;ecirc;tement l'identification et les autorisations MS Graph.&lt;/p&gt;
&lt;p&gt;C&amp;ocirc;t&amp;eacute; autorisation, comme notre application va g&amp;eacute;rer les calendriers Outlook, g&amp;eacute;rer des fichiers dans OneDrive, lire des informations sur l'utilisateur et &amp;eacute;ventuellement envoyer un email, j'ai donc le param&amp;eacute;trage suivant :&lt;/p&gt;
&lt;p&gt;&lt;img src="/Media/Default/BlogPost/articles/angular-microsoft-graph-part-1/msGraph3.JPG" alt="" width="836" height="322" /&gt;&lt;/p&gt;
&lt;p&gt;Sauvegardez le tout et voila, votre application est bien d&amp;eacute;finie, param&amp;eacute;tr&amp;eacute;e dans Office365.&lt;/p&gt;
&lt;p&gt;Remarque importante : tout en bas de cette page, vous avez la possibilit&amp;eacute; de voir le contenu du manifeste g&amp;eacute;n&amp;eacute;r&amp;eacute;e par cette page. C'est un fichier json et vous pouvez l'&amp;eacute;diter. La seule chose que vous devrez v&amp;eacute;rifier, c'est si les 2 param&amp;egrave;tres&amp;nbsp;oauth2AllowIdTokenImplicitFlow et&amp;nbsp;oauth2AllowImplicitFlow sont bien &amp;agrave; true :&lt;/p&gt;
&lt;p&gt;&lt;img src="/Media/Default/BlogPost/articles/angular-microsoft-graph-part-1/msGraph4.JPG" alt="" width="852" height="771" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;</description><pubDate>Wed, 29 Aug 2018 06:48:32 GMT</pubDate><guid isPermaLink="true">http://www.c2i.fr:80/articles/angular-microsoft-graph-part-1</guid></item><item><title>Blazor : Simple CRUD App Component, Component, Component</title><link>http://www.c2i.fr:80/articles/blazor-simple-crud-app-component-component-component</link><description>&lt;p&gt;Update v0.2&lt;/p&gt;
&lt;p&gt;S&amp;eacute;rie d'article sur Blazor :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/commencons-avec-les-webassembly-et-blazor"&gt;WebAssembly et Blazor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/blazor-ma-premiere-application-binding"&gt;Le binding&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/blazor-ma-premiere-application-javascript-et-service"&gt;Int&amp;eacute;raction javascript - services&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/blazor-simple-crud-app---httpclient"&gt;Simple CRUD : HttpClient&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/blazor-simple-crud-app-component-component-component"&gt;Component, component, component&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Dans le pr&amp;eacute;c&amp;eacute;dent article, on a vu comment cr&amp;eacute;er un service qui appele une WebAPI. Le but de ce nouvel article est d'assimiler la notion de component (composant in french) qui est au centre de Blazor et qui, si on le comprend bien, permettra d'optimiser les performances de notre application.&lt;/p&gt;
&lt;p&gt;Tout d'abord, il faut comprendre comment fonctionne le moteur de mise &amp;agrave; jour du DOM par Blazor.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Une page est un Component.&lt;/li&gt;
&lt;li&gt;Une page peut contenir plusieurs Component.&lt;/li&gt;
&lt;li&gt;Chaque Component est responsable de sa mise &amp;agrave; jour.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Le moteur de Blazor est suffisament intelligent pour ne mettre &amp;agrave; jour que component par component.&lt;/p&gt;
&lt;p&gt;Donc si vous avez une page qui comprend 15 components et que vous ne modifiez qu'un seul component, Blazor ne mettra &amp;agrave; jour le DOM de la page du navigateur que pour le fragment du component en question.&lt;/p&gt;
&lt;p&gt;Prenons l'exemple d'une grille, il est sans doute judicieux de d&amp;eacute;finir chaque ligne comme &amp;eacute;tat un component. Ainsi, si vous mettez &amp;agrave; jour une ligne, seule cette ligne sera mise &amp;agrave; jour dans le DOM par Blazor.&lt;/p&gt;
&lt;p&gt;Mettons cela en pratique.&lt;/p&gt;
&lt;p&gt;Dans mon projet, j'ai cr&amp;eacute;e un dossier PersonsComponent avec un PersonGrid et un PersonRow :&lt;/p&gt;
&lt;p&gt;&lt;img src="/Media/Default/BlogPost/articles/SimpleCrudBlazorPersonsComponent.JPG" alt="" width="266" height="315" /&gt;&lt;/p&gt;
&lt;p&gt;Visuellement parlant, cela va se traduire ainsi :&lt;/p&gt;
&lt;p&gt;&lt;img src="/Media/Default/BlogPost/articles/SimpleCrudBlazorPersonsGridStructure.jpg" alt="" width="814" height="356" /&gt;&lt;/p&gt;
&lt;p&gt;La zone rouge correspond &amp;agrave; mon composant PersonsGrid qui contient une collection de PersonRow (en vert). Quand je vais cliquer sur Edit ou Delete, seul le composant PersonRow correspondant sera affect&amp;eacute;.&lt;/p&gt;
&lt;h1&gt;PersonGrid&lt;/h1&gt;
&lt;p&gt;Regardons le code de PersonsGrid :&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre style="font-family: Consolas; font-size: 13; color: black; background: white;"&gt;&lt;span style="background: yellow;"&gt;@&lt;/span&gt;&lt;span style="color: blue;"&gt;using&lt;/span&gt;&amp;nbsp;System.Collections.Generic
&lt;span style="background: yellow;"&gt;@&lt;/span&gt;&lt;span style="color: blue;"&gt;using&lt;/span&gt;&amp;nbsp;System.Threading.Tasks
&lt;span style="background: yellow;"&gt;@&lt;/span&gt;&lt;span style="color: blue;"&gt;using&lt;/span&gt;&amp;nbsp;SimpleCrudBlazor.Client.Services;
&lt;span style="background: yellow;"&gt;@&lt;/span&gt;&lt;span style="color: blue;"&gt;using&lt;/span&gt;&amp;nbsp;SimpleCrudBlazor.Shared;
&lt;span style="background: yellow;"&gt;@inject&lt;/span&gt;&amp;nbsp;&lt;span style="color: #2b91af;"&gt;PersonService&lt;/span&gt;&amp;nbsp;_personService
 
&lt;span style="background: yellow;"&gt;@&lt;/span&gt;&lt;span style="color: blue;"&gt;if&lt;/span&gt;&amp;nbsp;(persons&amp;nbsp;!=&amp;nbsp;&lt;span style="color: blue;"&gt;null&lt;/span&gt;)
{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="background: yellow;"&gt;@&lt;/span&gt;&lt;span style="color: blue;"&gt;foreach&lt;/span&gt;&amp;nbsp;(&lt;span style="color: blue;"&gt;var&lt;/span&gt;&amp;nbsp;person&amp;nbsp;&lt;span style="color: blue;"&gt;in&lt;/span&gt;&amp;nbsp;persons)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="font-weight: bold; color: purple;"&gt;PersonRow&lt;/span&gt;&amp;nbsp;&lt;span style="font-weight: bold; color: purple;"&gt;Person&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;&lt;span style="color: blue;"&gt;"&lt;/span&gt;person&lt;span style="color: blue;"&gt;"&lt;/span&gt;&amp;nbsp;&lt;span style="font-weight: bold; color: purple;"&gt;OnIsSelectedChanged&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;&lt;span style="color: blue;"&gt;"&lt;/span&gt;OnIsSelectedChanged&lt;span style="color: blue;"&gt;"&lt;/span&gt;&amp;nbsp;&lt;span style="font-weight: bold; color: purple;"&gt;OnUpdate&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;&lt;span style="color: blue;"&gt;"&lt;/span&gt;OnUpdateAsync&lt;span style="color: blue;"&gt;"&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span style="font-weight: bold; color: purple;"&gt;PersonRow&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}
 
&lt;span style="background: yellow;"&gt;@functions&lt;/span&gt;&amp;nbsp;&lt;span style="background: yellow;"&gt;{&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;private&lt;/span&gt;&amp;nbsp;&lt;span style="color: #2b91af;"&gt;IEnumerable&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af;"&gt;Person&lt;/span&gt;&amp;gt;&amp;nbsp;persons;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;private&lt;/span&gt;&amp;nbsp;&lt;span style="color: #2b91af;"&gt;PersonRow&lt;/span&gt;&amp;nbsp;_selectedPersonRow;
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;protected&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;override&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;async&lt;/span&gt;&amp;nbsp;&lt;span style="color: #2b91af;"&gt;Task&lt;/span&gt;&amp;nbsp;OnInitAsync()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;await&lt;/span&gt;&amp;nbsp;RefreshAsync();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: gray;"&gt;///&lt;/span&gt;&lt;span style="color: green;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: gray;"&gt;summary&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: gray;"&gt;///&lt;/span&gt;&lt;span style="color: green;"&gt;&amp;nbsp;Charge&amp;nbsp;les&amp;nbsp;personnes&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: gray;"&gt;///&lt;/span&gt;&lt;span style="color: green;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: gray;"&gt;summary&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;async&lt;/span&gt;&amp;nbsp;&lt;span style="color: #2b91af;"&gt;Task&lt;/span&gt;&amp;nbsp;RefreshAsync()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;persons&amp;nbsp;=&amp;nbsp;&lt;span style="color: blue;"&gt;await&lt;/span&gt;&amp;nbsp;_personService.GetAllAsync();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: gray;"&gt;///&lt;/span&gt;&lt;span style="color: green;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: gray;"&gt;summary&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: gray;"&gt;///&lt;/span&gt;&lt;span style="color: green;"&gt;&amp;nbsp;Passe&amp;nbsp;en&amp;nbsp;mode&amp;nbsp;&amp;eacute;dition&amp;nbsp;une&amp;nbsp;personne&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: gray;"&gt;///&lt;/span&gt;&lt;span style="color: green;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: gray;"&gt;summary&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: gray;"&gt;///&lt;/span&gt;&lt;span style="color: green;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: gray;"&gt;param&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;nbsp;name&lt;/span&gt;&lt;span style="color: gray;"&gt;=&lt;/span&gt;&lt;span style="color: gray;"&gt;"&lt;/span&gt;personRow&lt;span style="color: gray;"&gt;"&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: green;"&gt;La&amp;nbsp;personne&amp;nbsp;a&amp;nbsp;mettre&amp;nbsp;en&amp;nbsp;mmode&amp;nbsp;&amp;eacute;dition&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: gray;"&gt;param&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;void&lt;/span&gt;&amp;nbsp;OnIsSelectedChanged(&lt;span style="color: #2b91af;"&gt;PersonRow&lt;/span&gt;&amp;nbsp;personRow)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;if&lt;/span&gt;&amp;nbsp;(_selectedPersonRow&amp;nbsp;==&amp;nbsp;personRow)&amp;nbsp;&lt;span style="color: blue;"&gt;return&lt;/span&gt;;
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;if&lt;/span&gt;&amp;nbsp;(_selectedPersonRow&amp;nbsp;!=&amp;nbsp;&lt;span style="color: blue;"&gt;null&lt;/span&gt;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_selectedPersonRow.IsSelected&amp;nbsp;=&amp;nbsp;&lt;span style="color: blue;"&gt;false&lt;/span&gt;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_selectedPersonRow&amp;nbsp;=&amp;nbsp;personRow;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: gray;"&gt;///&lt;/span&gt;&lt;span style="color: green;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: gray;"&gt;summary&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: gray;"&gt;///&lt;/span&gt;&lt;span style="color: green;"&gt;&amp;nbsp;Met&amp;nbsp;&amp;agrave;&amp;nbsp;jour&amp;nbsp;une&amp;nbsp;personne&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: gray;"&gt;///&lt;/span&gt;&lt;span style="color: green;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: gray;"&gt;summary&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;async&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;void&lt;/span&gt;&amp;nbsp;OnUpdateAsync(&lt;span style="color: #2b91af;"&gt;PersonRow&lt;/span&gt;&amp;nbsp;personRow)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;var&lt;/span&gt;&amp;nbsp;person&amp;nbsp;=&amp;nbsp;&lt;span style="color: blue;"&gt;await&lt;/span&gt;&amp;nbsp;_personService.UpdateAsync(personRow.Person);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;personRow.Person&amp;nbsp;=&amp;nbsp;person;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;personRow.NotifyStateHasChanged();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&lt;span style="background: yellow;"&gt;}&lt;/span&gt;
&amp;nbsp;&lt;/pre&gt;
&lt;p&gt;J'ai une propri&amp;eacute;t&amp;eacute; _persons qui contient la liste de mes personnes que je r&amp;eacute;cup&amp;egrave;re via mon service PersonService et la m&amp;eacute;thode RefreshAsync.&lt;/p&gt;
&lt;p&gt;J'ai aussi une propri&amp;eacute;t&amp;eacute; _selectedPersonRow qui m'indique quelle est la ligne s&amp;eacute;lectionn&amp;eacute;e, qui est en mode &amp;eacute;dition (il ne peut y en avoir qu'une).&lt;/p&gt;
&lt;p&gt;Pour chaque person de mes _persons, je cr&amp;eacute;e un component de type PersonRow qui a 2 propri&amp;eacute;t&amp;eacute;s de type Action&amp;lt;PersonRow&amp;gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;OnIsSelectedChanged qui m'indique que la ligne a &amp;eacute;t&amp;eacute; s&amp;eacute;lectionn&amp;eacute;e,&lt;/li&gt;
&lt;li&gt;OnUpdate qui m'indique que l'on a cliqu&amp;eacute; sur le bouton Update de la ligne.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;NB : je n'ai pas encore impl&amp;eacute;ment&amp;eacute; la m&amp;eacute;thode Delete.&lt;/p&gt;
&lt;p&gt;Quand la m&amp;eacute;thode OnIsSelectedChanged est appel&amp;eacute;e, je met &amp;agrave; jour le champ _selectedPersonRow pour garder la trace de la ligne s&amp;eacute;lectionn&amp;eacute;e. Si il y avait d&amp;eacute;j&amp;agrave; une ligne s&amp;eacute;lectionn&amp;eacute;e, j'affecte sa propri&amp;eacute;t&amp;eacute; IsSelected &amp;agrave; false.&lt;/p&gt;
&lt;p&gt;Quand la m&amp;eacute;thode OnUpdate est appel&amp;eacute;e, j'appele le service PersonService pour mettre &amp;agrave; jour la personne dans la base de donn&amp;eacute;e sur le serveur. Et surtout, je demande au composant de la ligne (le PersonRow) de se mettre &amp;agrave; jour avec NotifyStateHasChanged.&lt;/p&gt;
&lt;h1&gt;PersonRow&lt;/h1&gt;
&lt;p&gt;C&amp;ocirc;t&amp;eacute; PersonRow, c'est encore plus simple :&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre style="font-family: Consolas; font-size: 13; color: black; background: white;"&gt;&lt;span style="background: yellow;"&gt;@&lt;/span&gt;&lt;span style="color: blue;"&gt;using&lt;/span&gt;&amp;nbsp;System
&lt;span style="background: yellow;"&gt;@&lt;/span&gt;&lt;span style="color: blue;"&gt;using&lt;/span&gt;&amp;nbsp;SimpleCrudBlazor.Shared
&lt;span style="background: yellow;"&gt;@&lt;/span&gt;&lt;span style="color: blue;"&gt;using&lt;/span&gt;&amp;nbsp;SimpleCrudBlazor.Client.Services;
&lt;span style="background: yellow;"&gt;@&lt;/span&gt;&lt;span style="color: blue;"&gt;if&lt;/span&gt;&amp;nbsp;(_isSelected)
{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;div&lt;/span&gt;&amp;nbsp;&lt;span style="color: red;"&gt;class&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;&lt;span style="color: blue;"&gt;"row"&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;div&lt;/span&gt;&amp;nbsp;&lt;span style="color: red;"&gt;class&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;&lt;span style="color: blue;"&gt;"col-xs-1"&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;button&lt;/span&gt;&amp;nbsp;onclick="(()&amp;nbsp;=&amp;gt;&amp;nbsp;OnUpdatePerson())"&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;Update&lt;span style="color: blue;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;button&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;div&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;div&lt;/span&gt;&amp;nbsp;&lt;span style="color: red;"&gt;class&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;&lt;span style="color: blue;"&gt;"col-xs-1"&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;button&lt;/span&gt;&amp;nbsp;onclick="(()&amp;nbsp;=&amp;gt;&amp;nbsp;OnToggleEditMode())"&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;Cancel&lt;span style="color: blue;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;button&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;div&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;div&lt;/span&gt;&amp;nbsp;&lt;span style="color: red;"&gt;class&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;&lt;span style="color: blue;"&gt;"col-xs-5"&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;input&lt;/span&gt;&amp;nbsp;bind="@Person.FirstName"&amp;nbsp;&lt;span style="color: red;"&gt;type&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;&lt;span style="color: blue;"&gt;"text"&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;div&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;div&lt;/span&gt;&amp;nbsp;&lt;span style="color: red;"&gt;class&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;&lt;span style="color: blue;"&gt;"col-xs-5"&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;input&lt;/span&gt;&amp;nbsp;bind="@Person.LastName"&amp;nbsp;&lt;span style="color: red;"&gt;type&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;&lt;span style="color: blue;"&gt;"text"&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;div&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;div&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
}
&lt;span style="color: blue;"&gt;else&lt;/span&gt;
{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;div&lt;/span&gt;&amp;nbsp;&lt;span style="color: red;"&gt;class&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;&lt;span style="color: blue;"&gt;"row"&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;div&lt;/span&gt;&amp;nbsp;&lt;span style="color: red;"&gt;class&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;&lt;span style="color: blue;"&gt;"col-xs-1"&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;button&lt;/span&gt;&amp;nbsp;onclick="@(()&amp;nbsp;=&amp;gt;&amp;nbsp;OnToggleEditMode())"&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;Edit&lt;span style="color: blue;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;button&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;div&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;div&lt;/span&gt;&amp;nbsp;&lt;span style="color: red;"&gt;class&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;&lt;span style="color: blue;"&gt;"col-xs-1"&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;button&lt;/span&gt;&amp;nbsp;onclick="@(()=&amp;gt;&amp;nbsp;OnDelete())"&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;Delete&lt;span style="color: blue;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;button&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;div&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;div&lt;/span&gt;&amp;nbsp;&lt;span style="color: red;"&gt;class&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;&lt;span style="color: blue;"&gt;"col-xs-5"&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;&lt;span style="background: yellow;"&gt;@&lt;/span&gt;Person.FirstName&lt;span style="color: blue;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;div&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;div&lt;/span&gt;&amp;nbsp;&lt;span style="color: red;"&gt;class&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;&lt;span style="color: blue;"&gt;"col-xs-5"&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;&lt;span style="background: yellow;"&gt;@&lt;/span&gt;Person.LastName&lt;span style="color: blue;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;div&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;div&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
}
 
&lt;span style="background: yellow;"&gt;@functions&lt;/span&gt;&amp;nbsp;&lt;span style="background: yellow;"&gt;{&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;private&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;bool&lt;/span&gt;&amp;nbsp;_isSelected;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style="color: #2b91af;"&gt;Person&lt;/span&gt;&amp;nbsp;Person&amp;nbsp;{&lt;span style="color: blue;"&gt;get&lt;/span&gt;;&lt;span style="color: blue;"&gt;set&lt;/span&gt;;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style="color: #2b91af;"&gt;Action&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af;"&gt;PersonRow&lt;/span&gt;&amp;gt;&amp;nbsp;OnIsSelectedChanged&amp;nbsp;{&amp;nbsp;&lt;span style="color: blue;"&gt;get&lt;/span&gt;;&amp;nbsp;&lt;span style="color: blue;"&gt;set&lt;/span&gt;;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style="color: #2b91af;"&gt;Action&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af;"&gt;PersonRow&lt;/span&gt;&amp;gt;&amp;nbsp;OnUpdate&amp;nbsp;{&amp;nbsp;&lt;span style="color: blue;"&gt;get&lt;/span&gt;;&amp;nbsp;&lt;span style="color: blue;"&gt;set&lt;/span&gt;;&amp;nbsp;}
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;bool&lt;/span&gt;&amp;nbsp;IsSelected
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;get&lt;/span&gt;&amp;nbsp;=&amp;gt;&amp;nbsp;_isSelected;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;set&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;if&lt;/span&gt;&amp;nbsp;(IsSelected&amp;nbsp;==&amp;nbsp;&lt;span style="color: blue;"&gt;value&lt;/span&gt;)&amp;nbsp;&lt;span style="color: blue;"&gt;return&lt;/span&gt;;
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_isSelected&amp;nbsp;=&amp;nbsp;&lt;span style="color: blue;"&gt;value&lt;/span&gt;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;OnIsSelectedChanged(&lt;span style="color: blue;"&gt;this&lt;/span&gt;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;this&lt;/span&gt;.StateHasChanged();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;void&lt;/span&gt;&amp;nbsp;NotifyStateHasChanged()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;this&lt;/span&gt;.StateHasChanged();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;void&lt;/span&gt;&amp;nbsp;OnToggleEditMode()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;IsSelected&amp;nbsp;=&amp;nbsp;!IsSelected;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;void&lt;/span&gt;&amp;nbsp;OnDelete()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// TODO
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;void&lt;/span&gt;&amp;nbsp;OnUpdatePerson()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;OnUpdate(&lt;span style="color: blue;"&gt;this&lt;/span&gt;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;IsSelected&amp;nbsp;=&amp;nbsp;&lt;span style="color: blue;"&gt;false&lt;/span&gt;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&lt;span style="background: yellow;"&gt;}&lt;/span&gt;
&amp;nbsp;&lt;/pre&gt;
&lt;p&gt;Je passe sur le contenu de l'UI mais vous voyez que quand on clique sur le bouton Update par exemple, la m&amp;eacute;thode OnUpdatePerson est appel&amp;eacute;e, et cette derni&amp;egrave;re appele l'Action&amp;lt;PersonRow&amp;gt; OnUpdate (cf le PersonsGrid).&lt;/p&gt;
&lt;p&gt;Vous remarquerez que dans la propri&amp;eacute;t&amp;eacute; IsSelected, j'appele la m&amp;eacute;thode StateHasChanged : c'est pour indiquer au moteur Blazor que l'UI de ce composant doit &amp;ecirc;tre mis &amp;agrave; jour. Comme cette m&amp;eacute;thode est protected, je l'expose via la m&amp;eacute;thode NotifyStateHasChanged qui est appel&amp;eacute;e par le PersonsGrid quand un update est fait.&lt;/p&gt;
&lt;p&gt;Voil&amp;agrave;!&lt;/p&gt;
&lt;p&gt;Simple mais il est tr&amp;egrave;s important avec Balzor dez bien comprendre que tout est Component pour encore optimiser les performances de votre application.&lt;/p&gt;</description><pubDate>Wed, 18 Apr 2018 15:47:16 GMT</pubDate><guid isPermaLink="true">http://www.c2i.fr:80/articles/blazor-simple-crud-app-component-component-component</guid></item><item><title>Blazor : ma première application (javascript et service)</title><link>http://www.c2i.fr:80/articles/blazor-ma-premiere-application-javascript-et-service</link><description>&lt;p&gt;Update v0.2&lt;/p&gt;
&lt;p&gt;S&amp;eacute;rie d'article sur Blazor :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/commencons-avec-les-webassembly-et-blazor"&gt;WebAssembly et Blazor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/blazor-ma-premiere-application-binding"&gt;Le binding&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/blazor-ma-premiere-application-javascript-et-service"&gt;Int&amp;eacute;raction javascript - services&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/blazor-simple-crud-app---httpclient"&gt;Simple CRUD : HttpClient&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/blazor-simple-crud-app-component-component-component"&gt;Component, component, component&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Avant d'aller plus avant dans les diff&amp;eacute;rents concepts de Blazor, un point important est : comment d&amp;eacute;velopper sereinement ?&lt;/p&gt;
&lt;p&gt;Je dis ca car pour l'instant, impossible de mettre le moindre point d'arr&amp;ecirc;t dans votre code .NET de votre appli Blazor. Vous allez me dire : fais des logs, des traces, c'est mieux.&lt;/p&gt;
&lt;p&gt;Ok, puisque vous le prenez comme cela.&lt;/p&gt;
&lt;p&gt;Donc personnellement, pour "deboguer" une appli javascript/typescript, j'utilise &amp;agrave; fond la console du navigateur. Cela permet de ne pas interrompre le d&amp;eacute;roulement de l'application et de voir ce qu'il se passe dans le panel des outils du d&amp;eacute;veloppeur du navigateur (F12).&lt;/p&gt;
&lt;p&gt;Il faut donc pouvoir appeler les m&amp;eacute;thodes de l'objet javascript console, donc g&amp;eacute;n&amp;eacute;ralement savoir appeler une m&amp;eacute;thode javascript depuis son code C#.&lt;/p&gt;
&lt;h1&gt;Interop Javascript&lt;/h1&gt;
&lt;p&gt;Pour cela, Blazor vous permet d'enregistrer un script, une fonction :&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre style="font-family: Consolas; font-size: 13; color: black; background: white;"&gt;&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;script&lt;/span&gt;&amp;nbsp;&lt;span style="color: red;"&gt;type&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;&lt;span style="color: blue;"&gt;"text/javascript"&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Blazor.registerFunction(&lt;span style="color: #a31515;"&gt;'mafonction'&lt;/span&gt;,&amp;nbsp;()&amp;nbsp;=&amp;gt;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;console.log(&lt;span style="color: #a31515;"&gt;'coucou'&lt;/span&gt;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;return&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;true&lt;/span&gt;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});&lt;br /&gt;&amp;lt;/&lt;span style="color: maroon;"&gt;script&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;On enregistre ici la fonction javascript 'mafonction' qui sera appelable (invokable ;-)) depuis le code C# ainsi :&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre style="font-family: Consolas; font-size: 13; color: black; background: white;"&gt;&lt;span style="color: blue;"&gt;void&lt;/span&gt;&amp;nbsp;AppelJs()
{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Microsoft.AspNetCore.Blazor.Browser.Interop.&lt;span style="color: #2b91af;"&gt;RegisteredFunction&lt;/span&gt;.Invoke&amp;lt;&lt;span style="color: blue;"&gt;bool&lt;/span&gt;&amp;gt;(&lt;span style="color: #a31515;"&gt;"mafonction"&lt;/span&gt;);
}&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;NB : En interne, la m&amp;eacute;thode Invoke appele une m&amp;eacute;thode du runtime de Mono qui se charge r&amp;eacute;ellement de l'interop.&lt;/p&gt;
&lt;h1&gt;Service&lt;/h1&gt;
&lt;p&gt;C'est bien beau mais c'est un peu "craspouille" comme code. On va donc essayer de le "professionaliser". Pour cela, je vais dans un premier temps cr&amp;eacute;er un service dans mon application que je nommerais ConsoleService :&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre style="font-family: Consolas; font-size: 13; color: black; background: white;"&gt;&lt;span style="color: blue;"&gt;using&lt;/span&gt;&amp;nbsp;Microsoft.AspNetCore.Blazor.Browser.Interop;
 
&lt;span style="color: blue;"&gt;namespace&lt;/span&gt;&amp;nbsp;MaPremiereAppliBlazor.Services
{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;class&lt;/span&gt;&amp;nbsp;&lt;span style="color: #2b91af;"&gt;ConsoleService&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: green;"&gt;//&amp;nbsp;retourne&amp;nbsp;MaPremiereAppliBlazor.Services.ConsoleService&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;private&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;string&lt;/span&gt;&amp;nbsp;Prefix&amp;nbsp;=&amp;gt;&amp;nbsp;GetType().FullName;
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: gray;"&gt;///&lt;/span&gt;&lt;span style="color: green;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: gray;"&gt;summary&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: gray;"&gt;///&lt;/span&gt;&lt;span style="color: green;"&gt;&amp;nbsp;Permet&amp;nbsp;d'ajouter&amp;nbsp;un&amp;nbsp;log&amp;nbsp;dans&amp;nbsp;la&amp;nbsp;console&amp;nbsp;du&amp;nbsp;navigateur&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: gray;"&gt;///&lt;/span&gt;&lt;span style="color: green;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: gray;"&gt;summary&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: gray;"&gt;///&lt;/span&gt;&lt;span style="color: green;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: gray;"&gt;param&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;nbsp;name&lt;/span&gt;&lt;span style="color: gray;"&gt;=&lt;/span&gt;&lt;span style="color: gray;"&gt;"&lt;/span&gt;message&lt;span style="color: gray;"&gt;"&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: green;"&gt;Le&amp;nbsp;message&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: gray;"&gt;param&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;void&lt;/span&gt;&amp;nbsp;Log(&lt;span style="color: blue;"&gt;string&lt;/span&gt;&amp;nbsp;message)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;if&lt;/span&gt;&amp;nbsp;(&lt;span style="color: blue;"&gt;string&lt;/span&gt;.IsNullOrEmpty(message))&amp;nbsp;&lt;span style="color: blue;"&gt;return&lt;/span&gt;;
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #2b91af;"&gt;RegisteredFunction&lt;/span&gt;.Invoke&amp;lt;&lt;span style="color: blue;"&gt;bool&lt;/span&gt;&amp;gt;(Prefix&amp;nbsp;+&amp;nbsp;&lt;span style="color: #a31515;"&gt;".log"&lt;/span&gt;,&amp;nbsp;message);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: gray;"&gt;///&lt;/span&gt;&lt;span style="color: green;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: gray;"&gt;summary&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: gray;"&gt;///&lt;/span&gt;&lt;span style="color: green;"&gt;&amp;nbsp;Permet&amp;nbsp;d'ajouter&amp;nbsp;un&amp;nbsp;log&amp;nbsp;dans&amp;nbsp;la&amp;nbsp;console&amp;nbsp;du&amp;nbsp;navigateur&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: gray;"&gt;///&lt;/span&gt;&lt;span style="color: green;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: gray;"&gt;summary&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: gray;"&gt;///&lt;/span&gt;&lt;span style="color: green;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: gray;"&gt;param&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;nbsp;name&lt;/span&gt;&lt;span style="color: gray;"&gt;=&lt;/span&gt;&lt;span style="color: gray;"&gt;"&lt;/span&gt;sender&lt;span style="color: gray;"&gt;"&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: green;"&gt;L'objet&amp;nbsp;appelant&amp;nbsp;par&amp;nbsp;convention&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: gray;"&gt;param&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: gray;"&gt;///&lt;/span&gt;&lt;span style="color: green;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: gray;"&gt;param&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;nbsp;name&lt;/span&gt;&lt;span style="color: gray;"&gt;=&lt;/span&gt;&lt;span style="color: gray;"&gt;"&lt;/span&gt;message&lt;span style="color: gray;"&gt;"&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: green;"&gt;Le&amp;nbsp;message&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: gray;"&gt;param&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: gray;"&gt;///&lt;/span&gt;&lt;span style="color: green;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: gray;"&gt;remarks&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: green;"&gt;Log&amp;nbsp;dans&amp;nbsp;la&amp;nbsp;console:&amp;nbsp;[NomDuTypeAppelant]&amp;nbsp;message&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: gray;"&gt;remarks&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;void&lt;/span&gt;&amp;nbsp;Log(&lt;span style="color: blue;"&gt;object&lt;/span&gt;&amp;nbsp;sender,&amp;nbsp;&lt;span style="color: blue;"&gt;string&lt;/span&gt;&amp;nbsp;message)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;if&lt;/span&gt;&amp;nbsp;(message&amp;nbsp;==&amp;nbsp;&lt;span style="color: blue;"&gt;null&lt;/span&gt;)&amp;nbsp;&lt;span style="color: blue;"&gt;return&lt;/span&gt;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;if&lt;/span&gt;&amp;nbsp;(sender&amp;nbsp;==&amp;nbsp;&lt;span style="color: blue;"&gt;null&lt;/span&gt;)&amp;nbsp;Log(message);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Log(&lt;span style="color: #a31515;"&gt;$"[&lt;/span&gt;{sender.GetType().Name}&lt;span style="color: #a31515;"&gt;]&amp;nbsp;&lt;/span&gt;{message}&lt;span style="color: #a31515;"&gt;"&lt;/span&gt;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: gray;"&gt;///&lt;/span&gt;&lt;span style="color: green;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: gray;"&gt;summary&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: gray;"&gt;///&lt;/span&gt;&lt;span style="color: green;"&gt;&amp;nbsp;Vide&amp;nbsp;la&amp;nbsp;console&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: gray;"&gt;///&lt;/span&gt;&lt;span style="color: green;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: gray;"&gt;summary&lt;/span&gt;&lt;span style="color: gray;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;void&lt;/span&gt;&amp;nbsp;Clear()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #2b91af;"&gt;RegisteredFunction&lt;/span&gt;.Invoke&amp;lt;&lt;span style="color: blue;"&gt;bool&lt;/span&gt;&amp;gt;(Prefix&amp;nbsp;+&amp;nbsp;&lt;span style="color: #a31515;"&gt;".clear"&lt;/span&gt;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Remarquez que ce service invoque des m&amp;eacute;thodes javascript dont les noms sont :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MaPremiereAppliBlazor.Services.ConsoleService.log(message)&lt;/li&gt;
&lt;li&gt;MaPremiereAppliBlazor.Services.ConsoleService.clear()&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Je vais ensuite enregistrer ces m&amp;eacute;thodes dans la liste des m&amp;eacute;thodes invocables en les ajoutant dans la page MainLayout.cshtml :&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre style="font-family: Consolas; font-size: 13; color: black; background: white;"&gt;&lt;span style="background: yellow;"&gt;@implements&lt;/span&gt;&amp;nbsp;&lt;span style="color: #2b91af;"&gt;ILayoutComponent&lt;/span&gt;
 
&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;div&lt;/span&gt;&amp;nbsp;&lt;span style="color: red;"&gt;class&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;&lt;span style="color: blue;"&gt;'container-fluid'&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;div&lt;/span&gt;&amp;nbsp;&lt;span style="color: red;"&gt;class&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;&lt;span style="color: blue;"&gt;'row'&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;div&lt;/span&gt;&amp;nbsp;&lt;span style="color: red;"&gt;class&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;&lt;span style="color: blue;"&gt;'col-sm-3'&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="font-weight: bold; color: purple;"&gt;NavMenu&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;/&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;div&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;div&lt;/span&gt;&amp;nbsp;&lt;span style="color: red;"&gt;class&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;&lt;span style="color: blue;"&gt;'col-sm-9'&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="background: yellow;"&gt;@&lt;/span&gt;Body
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;div&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;div&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
&lt;span style="color: blue;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;div&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
 
&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;script&lt;/span&gt;&amp;nbsp;&lt;span style="color: red;"&gt;type&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;&lt;span style="color: blue;"&gt;"text/javascript"&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;var&lt;/span&gt;&amp;nbsp;prefix&amp;nbsp;=&amp;nbsp;&lt;span style="color: #a31515;"&gt;"SimpleCrudBlazor.Client.Services.ConsoleService"&lt;/span&gt;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Blazor.registerFunction(prefix&amp;nbsp;+&amp;nbsp;&lt;span style="color: #a31515;"&gt;'.log'&lt;/span&gt;,&amp;nbsp;message&amp;nbsp;=&amp;gt;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;console.log(message);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;return&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;true&lt;/span&gt;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Blazor.registerFunction(prefix&amp;nbsp;+&amp;nbsp;&lt;span style="color: #a31515;"&gt;'.clear'&lt;/span&gt;,&amp;nbsp;()&amp;nbsp;=&amp;gt;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;console.clear();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;return&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;true&lt;/span&gt;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
&lt;span style="color: blue;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;script&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
 
&lt;span style="background: yellow;"&gt;@functions&lt;/span&gt;&amp;nbsp;&lt;span style="background: yellow;"&gt;{&lt;/span&gt;
&lt;span style="color: blue;"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style="color: #2b91af;"&gt;RenderFragment&lt;/span&gt;&amp;nbsp;Body&amp;nbsp;{&amp;nbsp;&lt;span style="color: blue;"&gt;get&lt;/span&gt;;&amp;nbsp;&lt;span style="color: blue;"&gt;set&lt;/span&gt;;&amp;nbsp;}
&lt;span style="background: yellow;"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h1&gt;Injection de d&amp;eacute;pendance&lt;/h1&gt;
&lt;p&gt;Reste maintenant &amp;agrave; enregistrer notre service dans le moteur d'injection de d&amp;eacute;pendance dans le Main de notre Program :&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre style="font-family: Consolas; font-size: 13; color: black; background: white;"&gt;&lt;span style="color: blue;"&gt;using&lt;/span&gt;&amp;nbsp;Microsoft.AspNetCore.Blazor.Browser.Rendering;
&lt;span style="color: blue;"&gt;using&lt;/span&gt;&amp;nbsp;Microsoft.AspNetCore.Blazor.Browser.Services;
&lt;span style="color: blue;"&gt;using&lt;/span&gt;&amp;nbsp;MaPremiereAppliBlazor.Services;
&lt;span style="color: blue;"&gt;using&lt;/span&gt;&amp;nbsp;Microsoft.Extensions.DependencyInjection;
 
&lt;span style="color: blue;"&gt;namespace&lt;/span&gt;&amp;nbsp;MaPremiereAppliBlazor
{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;class&lt;/span&gt;&amp;nbsp;&lt;span style="color: #2b91af;"&gt;Program&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;static&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;void&lt;/span&gt;&amp;nbsp;Main(&lt;span style="color: blue;"&gt;string&lt;/span&gt;[]&amp;nbsp;args)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;var&lt;/span&gt;&amp;nbsp;serviceProvider&amp;nbsp;=&amp;nbsp;&lt;span style="color: blue;"&gt;new&lt;/span&gt;&amp;nbsp;&lt;span style="color: #2b91af;"&gt;BrowserServiceProvider&lt;/span&gt;(configure&amp;nbsp;=&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;configure.Add(&lt;span style="color: #2b91af;"&gt;ServiceDescriptor&lt;/span&gt;.Singleton&amp;lt;&lt;span style="color: #2b91af;"&gt;ConsoleService&lt;/span&gt;,&amp;nbsp;&lt;span style="color: #2b91af;"&gt;ConsoleService&lt;/span&gt;&amp;gt;());
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;new&lt;/span&gt;&amp;nbsp;&lt;span style="color: #2b91af;"&gt;BrowserRenderer&lt;/span&gt;(serviceProvider).AddComponent&amp;lt;&lt;span style="color: #2b91af;"&gt;App&lt;/span&gt;&amp;gt;(&lt;span style="color: #a31515;"&gt;"app"&lt;/span&gt;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Voila, tout est pr&amp;ecirc;t. Il suffit dans n'importe quel composant de notre application d'utiliser le moteur d'injection pour utiliser notre service. Dans une page, un Component quelconque, le framework Blazor nous permet d'utiliser la directive @inject :&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre style="font-family: Consolas; font-size: 13; color: black; background: white;"&gt;&lt;span style="background: yellow;"&gt;@&lt;/span&gt;&lt;span style="color: blue;"&gt;using&lt;/span&gt;&amp;nbsp;System.Globalization
&lt;span style="background: yellow;"&gt;@&lt;/span&gt;&lt;span style="color: blue;"&gt;using&lt;/span&gt;&amp;nbsp;MaPremiereAppliBlazor.Services
&lt;span style="background: yellow;"&gt;@page&lt;/span&gt;&amp;nbsp;&lt;span style="color: #a31515;"&gt;"/counter"&lt;/span&gt;
&lt;span style="background: yellow;"&gt;@inject&lt;/span&gt;&amp;nbsp;&lt;span style="color: #2b91af;"&gt;ConsoleService&lt;/span&gt;&amp;nbsp;ConsoleService
&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;h1&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;Counter&lt;span style="color: blue;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;h1&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
 
&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;p&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;Current&amp;nbsp;count:&amp;nbsp;&lt;span style="background: yellow;"&gt;@&lt;/span&gt;currentCount&lt;span style="color: blue;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;p&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;p&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;Increment&amp;nbsp;value:&amp;nbsp;&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;input&lt;/span&gt;&amp;nbsp;bind="@IncrementValue"&amp;nbsp;&lt;span style="color: red;"&gt;type&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;&lt;span style="color: blue;"&gt;"number"&lt;/span&gt;&amp;nbsp;/&lt;span style="color: blue;"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;p&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
 
&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;button&lt;/span&gt;&amp;nbsp;onclick="@(()&amp;nbsp;=&amp;gt;&amp;nbsp;IncrementCount())"&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;Click&amp;nbsp;me&lt;span style="color: blue;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;button&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
 
&lt;span style="background: yellow;"&gt;@functions&lt;/span&gt;&amp;nbsp;&lt;span style="background: yellow;"&gt;{&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;int&lt;/span&gt;&amp;nbsp;currentCount&amp;nbsp;=&amp;nbsp;0;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;private&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;int&lt;/span&gt;&amp;nbsp;_incrementValue&amp;nbsp;=&amp;nbsp;1;
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;string&lt;/span&gt;&amp;nbsp;IncrementValue
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;get&lt;/span&gt;&amp;nbsp;{&amp;nbsp;&lt;span style="color: blue;"&gt;return&lt;/span&gt;&amp;nbsp;_incrementValue.ToString(&lt;span style="color: #2b91af;"&gt;CultureInfo&lt;/span&gt;.InvariantCulture);&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;set&lt;/span&gt;&amp;nbsp;{&amp;nbsp;&lt;span style="color: blue;"&gt;int&lt;/span&gt;.TryParse(&lt;span style="color: blue;"&gt;value&lt;/span&gt;,&amp;nbsp;&lt;span style="color: blue;"&gt;out&lt;/span&gt;&amp;nbsp;_incrementValue);&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;void&lt;/span&gt;&amp;nbsp;IncrementCount()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ConsoleService.Log(&lt;span style="color: #a31515;"&gt;$"Ancienne&amp;nbsp;valeur&amp;nbsp;de&amp;nbsp;currentCount:&amp;nbsp;&lt;/span&gt;{currentCount}&lt;span style="color: #a31515;"&gt;"&lt;/span&gt;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;currentCount+=&amp;nbsp;_incrementValue;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ConsoleService.Log(&lt;span style="color: #a31515;"&gt;$"Nouvelle&amp;nbsp;valeur&amp;nbsp;de&amp;nbsp;currentCount:&amp;nbsp;&lt;/span&gt;{currentCount}&lt;span style="color: #a31515;"&gt;"&lt;/span&gt;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&lt;span style="background: yellow;"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Gr&amp;acirc;ce &amp;agrave; mes 2 signatures de la m&amp;eacute;thode log, je peux m&amp;ecirc;me faire plus explicite en tapant :&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre style="font-family: Consolas; font-size: 13; color: black; background: white;"&gt;&lt;span style="color: blue;"&gt;void&lt;/span&gt;&amp;nbsp;IncrementCount()
{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ConsoleService.Log(&lt;span style="color: blue;"&gt;this&lt;/span&gt;,&amp;nbsp;&lt;span style="color: #a31515;"&gt;$"Ancienne&amp;nbsp;valeur&amp;nbsp;de&amp;nbsp;currentCount:&amp;nbsp;&lt;/span&gt;{currentCount}&lt;span style="color: #a31515;"&gt;"&lt;/span&gt;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;currentCount+=&amp;nbsp;_incrementValue;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ConsoleService.Log(&lt;span style="color: blue;"&gt;this&lt;/span&gt;,&amp;nbsp;&lt;span style="color: #a31515;"&gt;$"Nouvelle&amp;nbsp;valeur&amp;nbsp;de&amp;nbsp;currentCount:&amp;nbsp;&lt;/span&gt;{currentCount}&lt;span style="color: #a31515;"&gt;"&lt;/span&gt;);
}&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Ce qui donne dans le navigateur :&lt;/p&gt;
&lt;p&gt;&lt;img src="/Media/Default/BlogPost/articles/blazor-ma-premiere-application-javascript-et-service/mapremiereAppliBlazorConsoleService1.JPG" alt="" width="779" height="361" /&gt;&lt;/p&gt;
&lt;p&gt;C'est plus cool non ?&lt;/p&gt;</description><pubDate>Wed, 18 Apr 2018 15:45:05 GMT</pubDate><guid isPermaLink="true">http://www.c2i.fr:80/articles/blazor-ma-premiere-application-javascript-et-service</guid></item><item><title>Blazor : ma première application (binding)</title><link>http://www.c2i.fr:80/articles/blazor-ma-premiere-application-binding</link><description>&lt;p&gt;&lt;span style="color: #434343; font-family: Tahoma, 'Helvetica Neue', Arial, Helvetica, sans-serif; font-size: 13.008px;"&gt;Update v0.2&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style="color: #434343; font-family: Tahoma, 'Helvetica Neue', Arial, Helvetica, sans-serif; font-size: 13.008px;"&gt;&lt;/span&gt;S&amp;eacute;rie d'article sur Blazor :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/commencons-avec-les-webassembly-et-blazor"&gt;WebAssembly et Blazor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/blazor-ma-premiere-application-binding"&gt;Le binding&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/blazor-ma-premiere-application-javascript-et-service"&gt;Int&amp;eacute;raction javascript - services&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/blazor-simple-crud-app---httpclient"&gt;Simple CRUD : HttpClient&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/blazor-simple-crud-app-component-component-component"&gt;Component, component, component&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Si vous ne l'avez d&amp;eacute;j&amp;agrave; fait, je vous invite &amp;agrave; lire l'&lt;a href="http://www.c2i.fr/articles/commencons-avec-les-webassembly-et-blazor"&gt;article pr&amp;eacute;c&amp;eacute;dent&lt;/a&gt; sur l'installation et la pr&amp;eacute;sentation de Blazor.&lt;/p&gt;
&lt;p&gt;Remarque important : ce article est bas&amp;eacute; sur la version 0.1 de Blazor. Donc beaucoup de choses risque de changer dans les semaines qui viennent.&lt;/p&gt;
&lt;h1&gt;&lt;span style="font-size: 2em;"&gt;So, let's go!&lt;/span&gt;&lt;/h1&gt;
&lt;p&gt;Maintenant que vous avez cr&amp;eacute;&amp;eacute; votre premi&amp;egrave;re application Blazor, regardons plus avant son contenu :&lt;/p&gt;
&lt;p&gt;&lt;img width="428" height="463" alt="" src="/Media/Default/BlogPost/articles/blazor-ma-premiere-application/premierAppliBlazor1.JPG" /&gt;&lt;/p&gt;
&lt;p&gt;On a l'impression de se retrouver face &amp;agrave; une application ASP .NET Core MVC "classique". Mais rappelez vous qu'ici, il n'y est pas question de serveur, de WebApi ou similaire : tout s'ex&amp;eacute;cute c&amp;ocirc;t&amp;eacute; client, dans le navigateur.&lt;/p&gt;
&lt;h1&gt;@page&lt;/h1&gt;
&lt;p&gt;Regardons le contenu de la page index.cshtml :&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre style="font-family: Consolas; font-size: 13; color: black; background: white;"&gt;&lt;span style="background: yellow;"&gt;@page&lt;/span&gt;&amp;nbsp;&lt;span style="color: #a31515;"&gt;"/"&lt;/span&gt;
&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;h1&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;Hello,&amp;nbsp;world!&lt;span style="color: blue;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;h1&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
Welcome&amp;nbsp;to&amp;nbsp;your&amp;nbsp;new&amp;nbsp;app.
&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="font-weight: bold; color: purple;"&gt;SurveyPrompt&lt;/span&gt;&amp;nbsp;&lt;span style="font-weight: bold; color: purple;"&gt;Title&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;&lt;span style="color: blue;"&gt;"How&amp;nbsp;is&amp;nbsp;Blazor&amp;nbsp;working&amp;nbsp;for&amp;nbsp;you?"&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;/&amp;gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Tout dans Blazor (au niveau UI) est un Component. Une page est un Component, une page contient des Component qui peuvent contenir des Components, etc.&lt;/p&gt;
&lt;p&gt;Ici, on a un Component un peu particulier puisqu'il poss&amp;egrave;de la nouvelle directive @page qui indique au moteur de Blazor que l'on peut naviguer vers cette page. Le syst&amp;egrave;me de routage de Blazor peut faire lz sujet d'un article complet aussi la seule chose importante &amp;agrave; retenir ici, c'est que l'argument de la directive donne le lien relatif. Comme dans notre page Component Index il n'y a rien c'est donc la page d'accueil de notre site.&lt;/p&gt;
&lt;p&gt;NB: un Component Blazor est juste une classe .NET qui h&amp;eacute;rite de Microsoft.AspNetCore.Blazor.Components.IComponent. Le framework Blazor ajoute une impl&amp;eacute;mentation avec le BlazorComponent.&lt;/p&gt;
&lt;p&gt;Ensuite on a du code html "classique" jusqu'au tag&amp;nbsp;SurveyPrompt avec son attribut Title.&lt;/p&gt;
&lt;h1&gt;Component&lt;/h1&gt;
&lt;p&gt;Ce tag est en r&amp;eacute;alit&amp;eacute; un autre Component de notre application qui poss&amp;egrave;de une propri&amp;eacute;t&amp;eacute; public Title. Si l'on regarde le code de notre Component&amp;nbsp;SurveyPrompt :&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre style="font-family: Consolas; font-size: 13; color: black; background: white;"&gt;&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;div&lt;/span&gt;&amp;nbsp;&lt;span style="color: red;"&gt;class&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;&lt;span style="color: blue;"&gt;"alert&amp;nbsp;alert-survey"&lt;/span&gt;&amp;nbsp;&lt;span style="color: red;"&gt;role&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;&lt;span style="color: blue;"&gt;"alert"&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;span&lt;/span&gt;&amp;nbsp;&lt;span style="color: red;"&gt;class&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;&lt;span style="color: blue;"&gt;"glyphicon&amp;nbsp;glyphicon-ok-circle"&lt;/span&gt;&amp;nbsp;&lt;span style="color: red;"&gt;aria-hidden&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;&lt;span style="color: blue;"&gt;"true"&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;span&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;strong&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;&lt;span style="background: yellow;"&gt;@&lt;/span&gt;Title&lt;span style="color: blue;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;strong&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Please&amp;nbsp;take&amp;nbsp;our
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;a&lt;/span&gt;&amp;nbsp;&lt;span style="color: red;"&gt;target&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;&lt;span style="color: blue;"&gt;"_blank"&lt;/span&gt;&amp;nbsp;&lt;span style="color: red;"&gt;class&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;&lt;span style="color: blue;"&gt;"alert-link"&lt;/span&gt;&amp;nbsp;&lt;span style="color: red;"&gt;href&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;&lt;span style="color: blue;"&gt;"https://go.microsoft.com/fwlink/?linkid=870381"&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;brief&amp;nbsp;survey
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;a&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;and&amp;nbsp;tell&amp;nbsp;us&amp;nbsp;what&amp;nbsp;you&amp;nbsp;think.
&lt;span style="color: blue;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;div&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
 
&lt;span style="background: yellow;"&gt;@functions&lt;/span&gt;
&lt;span style="background: yellow;"&gt;{&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: green;"&gt;//&amp;nbsp;This&amp;nbsp;is&amp;nbsp;to&amp;nbsp;demonstrate&amp;nbsp;how&amp;nbsp;a&amp;nbsp;parent&amp;nbsp;component&amp;nbsp;can&amp;nbsp;supply&amp;nbsp;parameters&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;string&lt;/span&gt;&amp;nbsp;Title&amp;nbsp;{&amp;nbsp;&lt;span style="color: blue;"&gt;get&lt;/span&gt;;&amp;nbsp;&lt;span style="color: blue;"&gt;set&lt;/span&gt;;&amp;nbsp;}
&lt;span style="background: yellow;"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Cette fois, comme on n'a pas la directive @page, ce composant est destin&amp;eacute; a &amp;ecirc;tre inclu dans un autre composant. Mais surtout, on voit la directive @function (classique dans Razor) pour indiquer que l'on a du code C# &amp;agrave; cet endroit.&lt;/p&gt;
&lt;p&gt;On retrouve notre propri&amp;eacute;t&amp;eacute; Title que l'on a d&amp;eacute;finit dans notre page index.&lt;/p&gt;
&lt;p&gt;NB : le code "behind" est directement inclu dans la page .cshtml. Il est pr&amp;eacute;vu qu'&amp;agrave; terme il y ai la possibilit&amp;eacute; d'avoir 2 fichiers par component, un pour l'UI (code Razor) et un autre pour le code behinf en .NET (C#, VB, F#, etc.).&lt;/p&gt;
&lt;h1&gt;Binding Code =&amp;gt; UI (One-Way binding)&lt;/h1&gt;
&lt;p&gt;On a donc notre propri&amp;eacute;t&amp;eacute; Title que l'on veut afficher dans l'UI de notre composant SurveyPrompt. Le binding est ici unidirectionnel du code vers l'UI. Ie si l'on change la valeur de la propri&amp;eacute;t&amp;eacute; Title, l'UI sera modifi&amp;eacute;e. Ce binding se fait gr&amp;acirc;ce &amp;agrave; la syntaxe suivante :&lt;/p&gt;
&lt;pre style="font-family: Consolas; font-size: 13; color: black; background: white;"&gt;&lt;span style="background: yellow;"&gt;@&lt;/span&gt;Title
&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;(&amp;eacute;quivalent de {{title}} en Angular)&lt;/em&gt;.&lt;/p&gt;
&lt;h1&gt;Binding UI =&amp;gt; Code (Event binding)&lt;/h1&gt;
&lt;p&gt;Pour voir ce type de binding, allons voir le code du composant Counter (Counter.cshtml) :&lt;/p&gt;
&lt;pre style="font-family: Consolas; font-size: 13; color: black; background: white;"&gt;&lt;span style="background: yellow;"&gt;@page&lt;/span&gt;&amp;nbsp;&lt;span style="color: #a31515;"&gt;"/counter"&lt;/span&gt;
&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;h1&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;Counter&lt;span style="color: blue;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;h1&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
 
&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;p&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;Current&amp;nbsp;count:&amp;nbsp;&lt;span style="background: yellow;"&gt;@&lt;/span&gt;currentCount&lt;span style="color: blue;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;p&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
 
&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;button&lt;/span&gt;&amp;nbsp;onclick="@IncrementCount"&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;Click&amp;nbsp;me&lt;span style="color: blue;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;button&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;
&lt;pre style="font-family: Consolas; font-size: 13; color: black; background: white;"&gt; 
&lt;span style="background: yellow;"&gt;@functions&lt;/span&gt;&amp;nbsp;&lt;span style="background: yellow;"&gt;{&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;int&lt;/span&gt;&amp;nbsp;currentCount&amp;nbsp;=&amp;nbsp;0;
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;void&lt;/span&gt;&amp;nbsp;IncrementCount()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;currentCount++;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&lt;span style="background: yellow;"&gt;}&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;C'est une page que l'on acc&amp;egrave;de via l'url http://localhost:4242/counter (directive @page). Dans le code behind, on a une m&amp;eacute;thode IncrementCount. Cette m&amp;eacute;thode est appel&amp;eacute;e quand on clique sur le bouton. Vous pouvez &amp;eacute;crire ce code de la fa&amp;ccedil;on suivante :&lt;/p&gt;
&lt;pre style="font-family: Consolas; font-size: 13; color: black; background: white;"&gt;onclick="@(()&amp;nbsp;=&amp;gt;&amp;nbsp;IncrementCount())"
&lt;/pre&gt;
&lt;p&gt;Pour l'instant, on n'a pas beaucoup d'&amp;eacute;v&amp;egrave;nement auxquels on peut r&amp;eacute;agir :&amp;nbsp;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;onclick&lt;/li&gt;
&lt;li&gt;onchange&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;OK, c'est vraiment l&amp;eacute;ger mais ce n'est que le d&amp;eacute;but ;-)&lt;/div&gt;
&lt;p&gt;&lt;em&gt;(&amp;eacute;quivalent de (click)='onclick()' en Angular).&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;Binding UI &amp;lt;=&amp;gt; Code (Two-Way binding)&lt;/h1&gt;
&lt;p&gt;Cette fois, on est dans le sc&amp;eacute;nario ou l'on veut li&amp;eacute; une propri&amp;eacute;t&amp;eacute; de notre composant avec un &amp;eacute;l&amp;eacute;ment de l'UI de notre composant. Par exemple, une zone de saisie comme un &amp;lt;input ...&amp;gt;.&lt;/p&gt;
&lt;p&gt;Dans le template "client only" de la v0.1 de Blazor, il n'y a pas d'exemple de binding two way. Qu'&amp;agrave; cela tienne, on va modifier notre exemple de la page counter :&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre style="font-family: Consolas; font-size: 13; color: black; background: white;"&gt;&lt;span style="background: yellow;"&gt;@&lt;/span&gt;&lt;span style="color: blue;"&gt;using&lt;/span&gt;&amp;nbsp;System.Globalization
&lt;span style="background: yellow;"&gt;@page&lt;/span&gt;&amp;nbsp;&lt;span style="color: #a31515;"&gt;"/counter"&lt;/span&gt;
&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;h1&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;Counter&lt;span style="color: blue;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;h1&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
 
&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;p&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;Current&amp;nbsp;count:&amp;nbsp;&lt;span style="background: yellow;"&gt;@&lt;/span&gt;currentCount&lt;span style="color: blue;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;p&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;p&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;Increment&amp;nbsp;value:&amp;nbsp;&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;input&lt;/span&gt;&amp;nbsp;bind="@IncrementValue"&amp;nbsp;&lt;span style="color: red;"&gt;type&lt;/span&gt;&lt;span style="color: blue;"&gt;=&lt;/span&gt;&lt;span style="color: blue;"&gt;"number"&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;p&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
 
&lt;span style="color: blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon;"&gt;button&lt;/span&gt;&amp;nbsp;&lt;span style="background: yellow;"&gt;@&lt;/span&gt;onclick(()&amp;nbsp;=&amp;gt;&amp;nbsp;IncrementCount())&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;Click&amp;nbsp;me&lt;span style="color: blue;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon;"&gt;button&lt;/span&gt;&lt;span style="color: blue;"&gt;&amp;gt;&lt;/span&gt;
 
&lt;span style="background: yellow;"&gt;@functions&lt;/span&gt;&amp;nbsp;&lt;span style="background: yellow;"&gt;{&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;int&lt;/span&gt;&amp;nbsp;currentCount&amp;nbsp;=&amp;nbsp;0;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;private&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;int&lt;/span&gt;&amp;nbsp;_incrementValue&amp;nbsp;=&amp;nbsp;1;
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;string&lt;/span&gt;&amp;nbsp;IncrementValue
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;get&lt;/span&gt;&amp;nbsp;{&amp;nbsp;&lt;span style="color: blue;"&gt;return&lt;/span&gt;&amp;nbsp;_incrementValue.ToString(&lt;span style="color: #2b91af;"&gt;CultureInfo&lt;/span&gt;.InvariantCulture);&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;set&lt;/span&gt;&amp;nbsp;{&amp;nbsp;&lt;span style="color: blue;"&gt;int&lt;/span&gt;.TryParse(&lt;span style="color: blue;"&gt;value&lt;/span&gt;,&amp;nbsp;&lt;span style="color: blue;"&gt;out&lt;/span&gt;&amp;nbsp;_incrementValue);&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;void&lt;/span&gt;&amp;nbsp;IncrementCount()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;currentCount+=&amp;nbsp;_incrementValue;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&lt;span style="background: yellow;"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;&lt;img src="/Media/Default/BlogPost/articles/blazor-ma-premiere-application/maPremiereAppBlazorBindingTwoWay.JPG" alt="" width="408" height="224" /&gt;&lt;/p&gt;
&lt;p&gt;Le binding entre la propri&amp;eacute;t&amp;eacute; IncrementValue et le tag &amp;lt;input&amp;gt; s'effectue gr&amp;acirc;ce &amp;agrave; la directive @bind tout simplement.&lt;/p&gt;
&lt;p&gt;IMPORTANT : cette directive @bind est appel&amp;eacute;e &amp;agrave; disparaitre avec une nouvelle syntaxe qui permettra des sc&amp;eacute;narios de binding plus complexe. Cela devrait apparaitre d&amp;egrave;s la version v0.2 du framework Blazor.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;</description><pubDate>Wed, 18 Apr 2018 15:43:06 GMT</pubDate><guid isPermaLink="true">http://www.c2i.fr:80/articles/blazor-ma-premiere-application-binding</guid></item><item><title>Commençons avec les WebAssembly et Blazor</title><link>http://www.c2i.fr:80/articles/commencons-avec-les-webassembly-et-blazor</link><description>&lt;p&gt;S&amp;eacute;rie d'article sur Blazor :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/commencons-avec-les-webassembly-et-blazor"&gt;WebAssembly et Blazor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/blazor-ma-premiere-application-binding"&gt;Le binding&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/blazor-ma-premiere-application-javascript-et-service"&gt;Int&amp;eacute;raction javascript - services&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/blazor-simple-crud-app---httpclient"&gt;Simple CRUD : HttpClient&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/blazor-simple-crud-app-component-component-component"&gt;Component, component, component&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;La notion de WebAssembly est toute neuve puisqu'elle ne date que de Mars 2015. Le principe est parti d'une interrogation : comment faire pour acc&amp;eacute;l&amp;eacute;rer les performances des applications web ?&lt;/p&gt;
&lt;p&gt;&lt;img width="800" height="450" alt="" src="/Media/Default/BlogPost/articles/commencons-avec-les-webassembly-et-blazor/fusee.jpg" /&gt;a&lt;/p&gt;
&lt;p&gt;Premi&amp;egrave;re solution, am&amp;eacute;liorer les moteurs d'interpr&amp;eacute;tation javascript (mais vous aurez remarqu&amp;eacute; que depuis quelques ann&amp;eacute;es, les Google, Microsoft &amp;amp; Firefox se sont calm&amp;eacute;s sur leurs communications sur les perfs de leurs moteurs respectifs). On peut certainement encore aller plus vite, mais il n'y aura pas de gap monstrueux.&lt;/p&gt;
&lt;p&gt;Donc l'autre solution est d'imaginer que les navigateurs puissent ex&amp;eacute;cuter du code "natif" (ou presque). D'ou l'id&amp;eacute;e g&amp;eacute;niale (faut pas bourrer, c'est comme Javas et .NET) de cr&amp;eacute;er un langage interm&amp;eacute;diaire, proche de l'assembleur qui pourra &amp;ecirc;tre int&amp;eacute;rpr&amp;eacute;t&amp;eacute; par le navigateur.&lt;/p&gt;
&lt;p&gt;D'ou le projet WebAssembly (&lt;a href="http://webassembly.org/"&gt;http://webassembly.org/&lt;/a&gt;), &lt;a href="https://www.w3.org/wasm/"&gt;reconnu par le W3C&lt;/a&gt;, dont la principale fonction est de d&amp;eacute;finir ce langage interm&amp;eacute;diaire (&lt;a href="https://www.w3.org/TR/2018/WD-wasm-core-1-20180215/"&gt;le 1er public working draft&lt;/a&gt; ne date que du 15 F&amp;eacute;vrier dernier).&lt;/p&gt;
&lt;p&gt;Bizarrement, ce projet a imm&amp;eacute;diatement &amp;eacute;t&amp;eacute; suivi par les principaux navigateurs puisqu'il est support&amp;eacute; par Chrome, Firefox, Safari et Edge.&lt;/p&gt;
&lt;p&gt;Donc maintenant qu'on a ce langage interm&amp;eacute;diaire, il faut pouvoir l'&amp;eacute;crire. Alors tout comme en .NET ou vous pouvez vous amuser &amp;agrave; programmer en IL, vous pouvez programmer en WAT.&lt;/p&gt;
&lt;h1&gt;What is WAT ?&lt;/h1&gt;
&lt;p&gt;WAT est la repr&amp;eacute;sentation textuelle du code binaire. Il y a une &amp;eacute;quivalence parfaite en le code WAT et le WASM (il existe des "traducteurs", d&amp;eacute;compilateurs qui traduisent le code binaire en WAT et inversement).&lt;/p&gt;
&lt;p&gt;Par exemple, voici du WAT :&lt;/p&gt;
&lt;p&gt;(module&lt;br /&gt;&amp;nbsp;(table 0 anyfunc)&lt;br /&gt;&amp;nbsp;(memory $0 1)&lt;br /&gt;&amp;nbsp;(export "memory" (memory $0))&lt;br /&gt;&amp;nbsp;(export "main" (func $main))&lt;br /&gt;&amp;nbsp;(func $main (; 0 ;) (result i32)&lt;br /&gt;&amp;nbsp; (i32.const 42)&lt;br /&gt;&amp;nbsp;)&lt;br /&gt;)&lt;/p&gt;
&lt;p&gt;Sympa non ? Son &amp;eacute;quivalent en C est :&lt;/p&gt;
&lt;p&gt;int main() { &lt;br /&gt;&amp;nbsp; return 42;&lt;br /&gt;}&lt;/p&gt;
&lt;p&gt;Alors m&amp;ecirc;me si j'aime pas le C, je le pr&amp;eacute;f&amp;egrave;re largement au WAT ;-) D'ou l'id&amp;eacute;e de cr&amp;eacute;er des "compilateurs", qui traduisent votre code en langage de "haut niveau" en WAT/WASM.&lt;/p&gt;
&lt;p&gt;Je r&amp;eacute;sume :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Vous &amp;eacute;crivez dans votre langage de pr&amp;eacute;dilection (C, C++, C#, VB .NET, F#, etc.),&lt;/li&gt;
&lt;li&gt;Un compilateur le traduit en code binaire WASM,&lt;/li&gt;
&lt;li&gt;Le navigateur charge ce code WASM et le compile en code natif&lt;/li&gt;
&lt;li&gt;Le navigateur ex&amp;eacute;cute le code natif (dans une sandbox) &amp;agrave; la vitesse de l'&amp;eacute;clair.&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;NB : Dans les specs des WebAssembly, il y a la d&amp;eacute;finition du code interm&amp;eacute;diaire mais aussi comment ce code est charg&amp;eacute; par le navigateur et comment il peut int&amp;eacute;ragir avec le DOM et le code javascript de l'application.&lt;/div&gt;
&lt;p&gt;Et la bonne nouvelle, c'est qu'en .NET, l'&amp;eacute;quipe de Mono chez Microsoft a eu la bonne id&amp;eacute;e de cr&amp;eacute;er un compilateur Wasm (ce n'est pas vieux, cela a &amp;eacute;t&amp;eacute; rendu public d&amp;eacute;but de cette ann&amp;eacute;e). Vous pouvez m&amp;ecirc;me participer &amp;agrave; ce &lt;a href="https://github.com/lrz/mono-wasm"&gt;projet Open Source sur Github&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;OK, j'ai un compilateur qui me g&amp;eacute;n&amp;egrave;re du WASM &amp;agrave; partir de mon code C#, mais comment je fais une appli web moi ?&lt;/p&gt;
&lt;h1&gt;Blazor, les grands principes&lt;/h1&gt;
&lt;p&gt;C'est l&amp;agrave; qu'apparait notre projet Blazor.&lt;/p&gt;
&lt;p&gt;Blazor est un framework SPA (Single Page Application) qui vous permet d'&amp;eacute;crire du code en .NET, code qui sera ex&amp;eacute;cut&amp;eacute; c&amp;ocirc;t&amp;eacute; client, dans le navigateur. Cool non ?&lt;/p&gt;
&lt;p&gt;Plus la peine d'apprendre le javascript et profitez de la puissance de .NET pour &amp;eacute;crire des applications SPA de la mort qui tue.&lt;/p&gt;
&lt;p&gt;Comme son nom l'indique, Blazor est bas&amp;eacute; sur le moteur de rendu Razor, moteur bien connu des d&amp;eacute;veloppeurs d'application ASP .NET MVC.&lt;/p&gt;
&lt;p&gt;Tout comme une application ASP .NET MVC, vous allez cr&amp;eacute;er des pages .cshtml (ou .vbhtml) avec du code behind. La diff&amp;eacute;rence majeure, c'est que le code que vous aurez &amp;eacute;crit s'ex&amp;eacute;cutera c&amp;ocirc;t&amp;eacute; client, dans le navigateur et non c&amp;ocirc;t&amp;eacute; serveur.&lt;/p&gt;
&lt;p&gt;Magique ? Non, juste une application de tout ce qu'on a expos&amp;eacute; ci-dessus :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Votre application Web est compil&amp;eacute;e dans une assembly .NET standard (Razor g&amp;eacute;n&amp;egrave;re des classes &amp;agrave; partir de la page .cshtml que vous avez &amp;eacute;crite),&lt;/li&gt;
&lt;li&gt;Quand le navigateur charge la page index.html, il charge le framework .NET de Mono (compil&amp;eacute; en wasm) qui charge votre dll (.NET standard),&lt;/li&gt;
&lt;li&gt;Le framework Mono .NET WASM ex&amp;eacute;cute alors votre appli,&lt;/li&gt;
&lt;li&gt;Cette derni&amp;egrave;re g&amp;eacute;n&amp;egrave;re dynamiquement le code html (issu de l'interpr&amp;eacute;tation de Razor) et l'injecte dans votre page index.html.&lt;/li&gt;
&lt;li&gt;Quand par exemple on clique sur un bouton, l'&amp;eacute;v&amp;egrave;nement est transmis par le framework Blazor &amp;agrave; votre dll qui peut r&amp;eacute;agir, mettre &amp;agrave; jour l'UI qui est inject&amp;eacute;e &amp;agrave; nouveau dans le DOM.&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;/div&gt;
&lt;p&gt;Nous verrons plus tard plus en d&amp;eacute;tail tout ce que contient Blazor car il y a pas mal de petites subtilit&amp;eacute;s (comme par exemple le wrapper HttpClient).&lt;/p&gt;
&lt;p&gt;Voyons maintenant comment tester Balzor.&lt;/p&gt;
&lt;h1&gt;Pr&amp;eacute;requis&lt;/h1&gt;
&lt;p&gt;Au jour ou j'&amp;eacute;cris ces lignes, Blazor en est &amp;agrave; sa version 0.1 : c'est dire si c'est le d&amp;eacute;but. Comme se plait &amp;agrave; le dire Microsoft, c'est un projet exp&amp;eacute;rimental, propos&amp;eacute; en Open Source &amp;agrave; la communaut&amp;eacute;. On verra quel est son accueil. (m&amp;ecirc;me au sein de Microsoft, tout le monde n'est pas du m&amp;ecirc;me avis sur l'avenir, la pertinence de Blazor).&lt;/p&gt;
&lt;p&gt;Toujours est-il que les WebAssembly sont elles en plein boom et ce serait dommage de rater le train (&amp;lt;BlagueA2Balles&amp;gt;surtout qu'il y en a pas beaucoup aujourd'hui en France, jours de gr&amp;ecirc;ve de la SNCF&lt;span style="display: inline !important; float: none; background-color: transparent; color: #000000; font-family: Verdana,Arial,Helvetica,sans-serif; font-size: 10px; font-style: normal; font-variant: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-decoration: none; text-indent: 0px; text-transform: none; -webkit-text-stroke-width: 0px; white-space: normal; word-spacing: 0px;"&gt;&amp;lt;/BlagueA2Balles&amp;gt;&lt;/span&gt;).&lt;/p&gt;
&lt;p&gt;La premi&amp;egrave;re &amp;eacute;tape est d'installer &lt;a href="https://www.microsoft.com/net/download/dotnet-core/sdk-2.1.300-preview1"&gt;le .NET Core 2.1 SDK&lt;/a&gt;. Il est encore en version preview aujourd'hui.&lt;/p&gt;
&lt;p&gt;&lt;img width="656" height="486" alt="" src="/Media/Default/BlogPost/articles/commencons-avec-les-webassembly-et-blazor/.NET%20Core%20SDK%202.1.300%20Preview.PNG" /&gt;&lt;/p&gt;
&lt;p&gt;Deuxi&amp;egrave;me &amp;eacute;tape (car les lignes de commande, c'est bien, mais Visual Studio c'est mieux), installer la version 15.7 de Visual Studio.&lt;/p&gt;
&lt;p&gt;En mode Preview aujourd'hui, vous pouvez &lt;a href="https://www.visualstudio.com/vs/preview/"&gt;la charger gratuitement ici.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Pendant l'installation, pensez bien &amp;agrave; cocher l'option D&amp;eacute;veloppement web et ASP .NET ainsi que le d&amp;eacute;veloppement multiplateforme .NET Core.&lt;/p&gt;
&lt;p&gt;&lt;img width="800" height="433" alt="" src="/Media/Default/BlogPost/articles/commencons-avec-les-webassembly-et-blazor/installVisualStudio.PNG" /&gt;&lt;/p&gt;
&lt;p&gt;Rassurez-vous : Vous pouvez installer cette version Preview en m&amp;ecirc;me temps que la version "officielle" de Visual Studio. LEs 2 environnements de d&amp;eacute;veloppement coexistent sans probl&amp;egrave;mes.&lt;/p&gt;
&lt;p&gt;&lt;img width="800" height="429" alt="" src="/Media/Default/BlogPost/articles/commencons-avec-les-webassembly-et-blazor/installVisualStudio2.PNG" /&gt;&lt;/p&gt;
&lt;p&gt;Une fois l'installation termin&amp;eacute;e, comme Blazor ajoute des nouvelles fonctionnalit&amp;eacute;s &amp;agrave; Razor (des nouvelles languages extensions), il faut que Visual Studio les reconnaisse.&lt;/p&gt;
&lt;p&gt;Donc t&amp;eacute;l&amp;eacute;chargez et installez &lt;a href="https://marketplace.visualstudio.com/items?itemName=aspnet.blazor"&gt;ASP .NET Core Blazor Language Services Extension&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That's it! Vous &amp;ecirc;tes pr&amp;ecirc;t pour tester votre premi&amp;egrave;re application Blazor.&lt;/p&gt;
&lt;h1&gt;Ma premi&amp;egrave;re appli Blazor&lt;/h1&gt;
&lt;p&gt;Lancez donc Visual Studio 2017 15.7 Preview, puis nouveau projet de type Web Core :&lt;/p&gt;
&lt;p&gt;&lt;img width="800" height="578" alt="" src="/Media/Default/BlogPost/articles/commencons-avec-les-webassembly-et-blazor/Blazor1.PNG" /&gt;&lt;/p&gt;
&lt;p&gt;Vous devez ensuite choisir le template qui vous plait :&lt;/p&gt;
&lt;p&gt;&lt;img width="786" height="513" alt="" src="/Media/Default/BlogPost/articles/commencons-avec-les-webassembly-et-blazor/Blazor2.PNG" /&gt;&lt;/p&gt;
&lt;p&gt;Le 1er template (Blazor seul) cr&amp;eacute;e une appli cliente uniquement (comme un site web avec que du javascript, css et html).&lt;/p&gt;
&lt;p&gt;Le second, plus complet, cr&amp;eacute;e une appli cliente, une application serveur h&amp;eacute;bergeant les Web API consomm&amp;eacute;s par l'appli cliente et un projet partag&amp;eacute; entre les 2 (c'est l&amp;agrave; un des gros avantages de Blazor).&lt;/p&gt;
&lt;p&gt;Faites comme vous voulez mais essayez tout d'abord le 1er template puis lancez le et regardez comment cela se passe dans le navigateur (activez le mode D&amp;eacute;veloppeur, F12).&lt;/p&gt;
&lt;p&gt;Dans un premier temps vous verrez que le navigateur charge la webassembly mono.wasm :&lt;/p&gt;
&lt;p&gt;&lt;img width="359" height="197" style="background-color: transparent; border-image-outset: 0; border-image-repeat: stretch; border-image-slice: 100%; border-image-source: none; border-image-width: 1; color: #000000; font-family: Verdana,Arial,Helvetica,sans-serif; font-size: 10px; font-style: normal; font-variant: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-decoration: none; text-indent: 0px; text-transform: none; -webkit-text-stroke-width: 0px; white-space: normal; word-spacing: 0px; border-width: 0px; border-color: #000000; border-style: none;" alt="" src="http://www.c2i.fr/Media/Default/BlogPost/articles/commencons-avec-les-webassembly-et-blazor/bmapremiereAppliBlazor2.JPG" /&gt;&lt;b&gt;&lt;/b&gt;&lt;i&gt;&lt;/i&gt;&lt;span style="text-decoration: underline;"&gt;&lt;/span&gt;&lt;sub&gt;&lt;/sub&gt;&lt;sup&gt;&lt;/sup&gt;&lt;span style="text-decoration: line-through;"&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;/b&gt;&lt;i&gt;&lt;/i&gt;&lt;span style="text-decoration: underline;"&gt;&lt;/span&gt;&lt;sub&gt;&lt;/sub&gt;&lt;sup&gt;&lt;/sup&gt;&lt;span style="text-decoration: line-through;"&gt;&lt;/span&gt;Puis cette derni&amp;egrave;re t&amp;eacute;l&amp;eacute;charge et interpr&amp;egrave;te les dlls .NET Standard de votre application avec ses d&amp;eacute;pendances (dont le framework) :&lt;/p&gt;
&lt;p&gt;&lt;img width="675" height="516" alt="" src="/Media/Default/BlogPost/articles/commencons-avec-les-webassembly-et-blazor/bmapremiereAppliBlazor1.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Nous verrons dans prochain article en d&amp;eacute;tail le contenu de cette premi&amp;egrave;re application.&lt;/p&gt;
&lt;p&gt;Bon dev!&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;</description><pubDate>Thu, 05 Apr 2018 16:32:45 GMT</pubDate><guid isPermaLink="true">http://www.c2i.fr:80/articles/commencons-avec-les-webassembly-et-blazor</guid></item><item><title>Blazor : Simple CRUD App - HttpClient</title><link>http://www.c2i.fr:80/articles/blazor-simple-crud-app---httpclient</link><description>&lt;p&gt;S&amp;eacute;rie d'article sur Blazor :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/commencons-avec-les-webassembly-et-blazor"&gt;WebAssembly et Blazor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/blazor-ma-premiere-application-binding"&gt;Le binding&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/blazor-ma-premiere-application-javascript-et-service"&gt;Int&amp;eacute;raction javascript - services&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/blazor-simple-crud-app---httpclient"&gt;Simple CRUD : HttpClient&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.c2i.fr/articles/blazor-simple-crud-app-component-component-component"&gt;Component, component, component&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Jusqu'&amp;agrave; pr&amp;eacute;sent, je ne vous ai pr&amp;eacute;sent&amp;eacute; Blazor que c&amp;ocirc;t&amp;eacute; client. Cette fois, nous allons voir un sc&amp;eacute;nario plus complet qui consiste &amp;agrave; interagir avec une WebAPI, donc c&amp;ocirc;t&amp;eacute; serveur. Le gros avantage (de mon point de vue) c'est que comme c&amp;ocirc;t&amp;eacute; serveur et c&amp;ocirc;t&amp;eacute; client on a du code C# (.NET pour ne pas vexer les d&amp;eacute;veloppeurs VB .NET, F# &amp;amp; co), on peut partager du code entre le client et le serveur.&lt;/p&gt;
&lt;p&gt;Prenons l'exemple d'une application Angular qui consomme une WebAPI. Si la WebAPI retourne un objet Person d&amp;eacute;finie dans la classe Person.cs, il faut que c&amp;ocirc;t&amp;eacute; Angular, on d&amp;eacute;finisse l'interface IPerson.ts avec les m&amp;ecirc;me propri&amp;eacute;t&amp;eacute;s.&lt;/p&gt;
&lt;p&gt;L&amp;agrave;, avec Blazor, comme la classe Person.cs sera partag&amp;eacute;e entre le serveur et le client, le probl&amp;egrave;me de redondance d'information (et possibilit&amp;eacute; d'erreur) ne se pose plus.&lt;/p&gt;
&lt;p&gt;Donc,&lt;/p&gt;
&lt;p&gt;Lancez Visual Studio 2017 15.7 (preview) et cr&amp;eacute;ez un nouveau projet ASP .NET. Cette fois, dans les templates Blazor, choisissez l'option Blazor ASP .NET Core Hosted :&lt;/p&gt;
&lt;p&gt;&lt;img src="/Media/Default/BlogPost/articles/blazor-simple-crud-app---httpclient/SimpleCrudBlazorInit.JPG" alt="" width="786" height="513" /&gt;&lt;/p&gt;
&lt;p&gt;Cette fois, le template comprends 3 projets :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SimpleCrudBlazor.Client : l'application cliente WebAssembly,&lt;/li&gt;
&lt;li&gt;SimpleCrudBlazor.Server : l'application serveur de WebAPI,&lt;/li&gt;
&lt;li&gt;SimpleCrudBlazor.Shared : la dll avec des classes utilis&amp;eacute;es par le serveur ET le client.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Dans Shared, j'ai cr&amp;eacute;&amp;eacute; une classe Person b&amp;ecirc;te et m&amp;eacute;chante. Moralit&amp;eacute;, dans mes pages ex&amp;eacute;cut&amp;eacute;es c&amp;ocirc;t&amp;eacute; client je peux instancier un objet Person tout comme dans mes controllers de mes WebAPI.&lt;/p&gt;
&lt;p&gt;Dans Server, j'ai cr&amp;eacute;e un controller qui me retourne la liste de mes clients via /api/Persons.&lt;/p&gt;
&lt;p&gt;Dans Client, j'ai cr&amp;eacute;e un service qui va appeler la WebApi /api/Persons.&lt;/p&gt;
&lt;p&gt;&lt;img src="/Media/Default/BlogPost/articles/blazor-simple-crud-app---httpclient/SimpleCrudStructure1.JPG" alt="" width="266" height="501" /&gt;&lt;/p&gt;
&lt;p&gt;Je passe sur l'impl&amp;eacute;mentation du controller du serveur qui est tout ce qu'il y a de plus classique. Regardons plut&amp;ocirc;t notre classe PersonService dans le client :&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre style="font-family: Consolas; font-size: 13; color: black; background: white;"&gt;&lt;span style="color: blue;"&gt;using&lt;/span&gt;&amp;nbsp;System;
&lt;span style="color: blue;"&gt;using&lt;/span&gt;&amp;nbsp;SimpleCrudBlazor.Shared;
&lt;span style="color: blue;"&gt;using&lt;/span&gt;&amp;nbsp;System.Collections.Generic;
&lt;span style="color: blue;"&gt;using&lt;/span&gt;&amp;nbsp;System.Net.Http;
&lt;span style="color: blue;"&gt;using&lt;/span&gt;&amp;nbsp;System.Threading.Tasks;
&lt;span style="color: blue;"&gt;using&lt;/span&gt;&amp;nbsp;Microsoft.AspNetCore.Blazor;
 
&lt;span style="color: blue;"&gt;namespace&lt;/span&gt;&amp;nbsp;SimpleCrudBlazor.Client.Services
{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;class&lt;/span&gt;&amp;nbsp;&lt;span style="color: #2b91af;"&gt;PersonService&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;private&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;readonly&lt;/span&gt;&amp;nbsp;&lt;span style="color: #2b91af;"&gt;HttpClient&lt;/span&gt;&amp;nbsp;_client;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;private&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;readonly&lt;/span&gt;&amp;nbsp;&lt;span style="color: #2b91af;"&gt;ConsoleService&lt;/span&gt;&amp;nbsp;_consoleService;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;private&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;readonly&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;string&lt;/span&gt;&amp;nbsp;_apiUrl&amp;nbsp;=&amp;nbsp;&lt;span style="color: #a31515;"&gt;"/api/Persons"&lt;/span&gt;;
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;public&lt;/span&gt;&amp;nbsp;PersonService(&lt;span style="color: #2b91af;"&gt;HttpClient&lt;/span&gt;&amp;nbsp;client,&amp;nbsp;&lt;span style="color: #2b91af;"&gt;ConsoleService&lt;/span&gt;&amp;nbsp;consoleService)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_client&amp;nbsp;=&amp;nbsp;client;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_consoleService&amp;nbsp;=&amp;nbsp;consoleService;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;async&lt;/span&gt;&amp;nbsp;&lt;span style="color: #2b91af;"&gt;Task&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af;"&gt;IEnumerable&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af;"&gt;Person&lt;/span&gt;&amp;gt;&amp;gt;&amp;nbsp;GetAllAsync()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_consoleService.Log(&lt;span style="color: blue;"&gt;this&lt;/span&gt;,&amp;nbsp;&lt;span style="color: #a31515;"&gt;"GetAllPersons"&lt;/span&gt;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;var&lt;/span&gt;&amp;nbsp;persons&amp;nbsp;=&amp;nbsp;&lt;span style="color: blue;"&gt;await&lt;/span&gt;&amp;nbsp;_client.GetJsonAsync&amp;lt;&lt;span style="color: #2b91af;"&gt;Person&lt;/span&gt;[]&amp;gt;(_apiUrl);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;return&lt;/span&gt;&amp;nbsp;persons;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;async&lt;/span&gt;&amp;nbsp;&lt;span style="color: #2b91af;"&gt;Task&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af;"&gt;Person&lt;/span&gt;&amp;gt;&amp;nbsp;UpdateAsync(&lt;span style="color: #2b91af;"&gt;Person&lt;/span&gt;&amp;nbsp;person)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_consoleService.Log(&lt;span style="color: blue;"&gt;this&lt;/span&gt;,&amp;nbsp;&lt;span style="color: #a31515;"&gt;"UpdateAsync"&lt;/span&gt;&amp;nbsp;+&amp;nbsp;person.Id);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;return&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;await&lt;/span&gt;&amp;nbsp;_client.PutJsonAsync&amp;lt;&lt;span style="color: #2b91af;"&gt;Person&lt;/span&gt;&amp;gt;(&lt;span style="color: #a31515;"&gt;$"&lt;/span&gt;{_apiUrl}&lt;span style="color: #a31515;"&gt;/&lt;/span&gt;{person.Id}&lt;span style="color: #a31515;"&gt;"&lt;/span&gt;,&amp;nbsp;person);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;async&lt;/span&gt;&amp;nbsp;&lt;span style="color: #2b91af;"&gt;Task&lt;/span&gt;&amp;lt;&lt;span style="color: blue;"&gt;int&lt;/span&gt;&amp;gt;&amp;nbsp;DeleteAsync(&lt;span style="color: blue;"&gt;int&lt;/span&gt;&amp;nbsp;personId)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;throw&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;new&lt;/span&gt;&amp;nbsp;&lt;span style="font-weight: bold; color: #008dd9;"&gt;NotImplementedException&lt;/span&gt;();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;async&lt;/span&gt;&amp;nbsp;&lt;span style="color: #2b91af;"&gt;Task&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af;"&gt;Person&lt;/span&gt;&amp;gt;&amp;nbsp;Insert(&lt;span style="color: #2b91af;"&gt;Person&lt;/span&gt;&amp;nbsp;person)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;throw&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;new&lt;/span&gt;&amp;nbsp;&lt;span style="font-weight: bold; color: #008dd9;"&gt;NotImplementedException&lt;/span&gt;();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}
&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;On voit que dans le constructeur de notre service, on attend 2 autres services (qui seront inject&amp;eacute;s via le moteur d'injection de d&amp;eacute;pendance). On a notre ConsoleService (cf article pr&amp;eacute;c&amp;eacute;dent) et surtout le HttpClient.&lt;/p&gt;
&lt;p&gt;Le code est "classique" en .NET sauf qu'il y a un gros probl&amp;egrave;me : ici, notre code doit faire un appel http vers une ressource. Or, faire un appel http depuis un navigateur, c'est interdit pour des raisons de s&amp;eacute;curit&amp;eacute;. Seul le navigateur a le droit d'effectuer des requ&amp;ecirc;tes Http.&lt;/p&gt;
&lt;p&gt;Et c'est l&amp;agrave; ou l'on a une grosse astuce.&lt;/p&gt;
&lt;p&gt;Le moteur d'injection de Blazor est un BrowserServiceProvider et ce dernier par d&amp;eacute;faut r&amp;eacute;f&amp;eacute;rence deux services, le IUriHelper (on s'en fou pour le moment) et un HttpClient avec un MessageHandler bien particulier. Ce messageHandler est un&amp;nbsp;BrowserHttpMessageHandler qui intercepter donc tout appel avec cette classe.&lt;/p&gt;
&lt;p&gt;Que fait-il ? Tout simplement (je dis ca mais c'est pas trivial du tout), il r&amp;eacute;cup&amp;egrave;re l'appel "http .NET", la s&amp;eacute;rialise et fait un appel &amp;agrave; une m&amp;eacute;thode javascript d&amp;eacute;j&amp;agrave; enregistr&amp;eacute;e par le framework Blazor qui elle appelle la m&amp;eacute;thode fetch du navigateur. Vous avez compris l'astuce ?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Blazor par d&amp;eacute;faut enregistre une m&amp;eacute;thode javascript (Blazor.registerfunction) comme on a vu dans l'article pr&amp;eacute;c&amp;eacute;dent dont le nom est&amp;nbsp;Microsoft.AspNetCore.Blazor.Browser.BrowserHttpMessageHandler.&lt;/li&gt;
&lt;li&gt;Dans cette m&amp;eacute;thode javascript qui a plein d'arguments, il utilise la m&amp;eacute;thode fetch du navigateur pour faire l'appel vers la WebAPI,&lt;/li&gt;
&lt;li&gt;Quand on utilise un HttpClient via le moteur d'injection, ce dernier a un handler,&lt;/li&gt;
&lt;li&gt;Et ce handler appele la m&amp;eacute;thode javascript avec la m&amp;eacute;thode Invoke.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cool, simple, efficace.&lt;/p&gt;
&lt;p&gt;Moralit&amp;eacute;, c'est bien le navigateur qui fait l'appel Http et non l'impl&amp;eacute;mentation initiale du HttpClient .NET.&lt;/p&gt;
&lt;p&gt;Il ne reste plus qu'&amp;agrave; utiliser le moteur d'injection de Blazor pour ajouter notre service PersonService et le rendre ainsi disponible pour toute notre application :&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre style="font-family: Consolas; font-size: 13; color: black; background: white;"&gt;&lt;span style="color: blue;"&gt;static&lt;/span&gt;&amp;nbsp;&lt;span style="color: blue;"&gt;void&lt;/span&gt;&amp;nbsp;Main(&lt;span style="color: blue;"&gt;string&lt;/span&gt;[]&amp;nbsp;args)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;var&lt;/span&gt;&amp;nbsp;serviceProvider&amp;nbsp;=&amp;nbsp;&lt;span style="color: blue;"&gt;new&lt;/span&gt;&amp;nbsp;&lt;span style="color: #2b91af;"&gt;BrowserServiceProvider&lt;/span&gt;(configure&amp;nbsp;=&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;configure.Add(&lt;span style="color: #2b91af;"&gt;ServiceDescriptor&lt;/span&gt;.Singleton&amp;lt;&lt;span style="color: #2b91af;"&gt;PersonService&lt;/span&gt;,&amp;nbsp;&lt;span style="color: #2b91af;"&gt;PersonService&lt;/span&gt;&amp;gt;());
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;configure.Add(&lt;span style="color: #2b91af;"&gt;ServiceDescriptor&lt;/span&gt;.Singleton&amp;lt;&lt;span style="color: #2b91af;"&gt;ConsoleService&lt;/span&gt;,&amp;nbsp;&lt;span style="color: #2b91af;"&gt;ConsoleService&lt;/span&gt;&amp;gt;());
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: blue;"&gt;new&lt;/span&gt;&amp;nbsp;&lt;span style="color: #2b91af;"&gt;BrowserRenderer&lt;/span&gt;(serviceProvider).AddComponent&amp;lt;&lt;span style="color: #2b91af;"&gt;App&lt;/span&gt;&amp;gt;(&lt;span style="color: #a31515;"&gt;"app"&lt;/span&gt;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;</description><pubDate>Thu, 05 Apr 2018 16:31:54 GMT</pubDate><guid isPermaLink="true">http://www.c2i.fr:80/articles/blazor-simple-crud-app---httpclient</guid></item><item><title>Amusons nous avec Angular, BabylonJs et la réalité virtuelle (partie 2)</title><link>http://www.c2i.fr:80/articles/amusons-nous-avec-angular-babylonjs-et-la-realite-virtuelle-partie-2</link><description>&lt;p&gt;&lt;/p&gt;
&lt;ul style="background-attachment: scroll; background-clip: border-box; background-color: transparent; background-image: none; background-origin: padding-box; background-position-x: 0%; background-position-y: 0%; background-repeat: repeat; background-size: auto; color: #000000; font-family: Verdana,Arial,Helvetica,sans-serif; font-size: 10px; font-style: normal; font-variant: normal; font-weight: 400; letter-spacing: normal; margin-bottom: 10px; margin-left: 0px; margin-right: 0px; margin-top: 10px; orphans: 2; outline-color: transparent; outline-style: none; outline-width: 0px; text-align: left; text-decoration: none; text-indent: 0px; text-transform: none; -webkit-text-stroke-width: 0px; white-space: normal; word-spacing: 0px;"&gt;
&lt;li style="background-attachment: scroll; background-clip: border-box; background-color: transparent; background-image: none; background-origin: padding-box; background-position-x: 0%; background-position-y: 0%; background-repeat: repeat; background-size: auto; color: #000000; font-family: Verdana,Arial,Helvetica,sans-serif; font-size: 10px; outline-color: transparent; outline-style: none; outline-width: 0px; margin: 0px;"&gt;&lt;a style="background-attachment: scroll; background-clip: border-box; background-color: transparent; background-image: none; background-origin: padding-box; background-position-x: 0%; background-position-y: 0%; background-repeat: repeat; background-size: auto; color: #0066cc; font-family: Verdana,Arial,Helvetica,sans-serif; font-size: 10px; outline-color: transparent; outline-style: none; outline-width: 0px; margin: 0px;" href="http://www.c2i.fr/articles/amusons-nous-avec-angular-babylonjs-et-la-realite-virtuelle"&gt;Partie 1&lt;/a&gt;&lt;/li&gt;
&lt;li style="background-attachment: scroll; background-clip: border-box; background-color: transparent; background-image: none; background-origin: padding-box; background-position-x: 0%; background-position-y: 0%; background-repeat: repeat; background-size: auto; color: #000000; font-family: Verdana,Arial,Helvetica,sans-serif; font-size: 10px; outline-color: transparent; outline-style: none; outline-width: 0px; margin: 0px;"&gt;&lt;a style="background-attachment: scroll; background-clip: border-box; background-color: transparent; background-image: none; background-origin: padding-box; background-position-x: 0%; background-position-y: 0%; background-repeat: repeat; background-size: auto; color: #0066cc; font-family: Verdana,Arial,Helvetica,sans-serif; font-size: 10px; outline-color: transparent; outline-style: none; outline-width: 0px; margin: 0px;" href="http://www.c2i.fr/articles/amusons-nous-avec-angular-babylonjs-et-la-realite-virtuelle-partie-2"&gt;Partie 2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.c2i.fr/articles/amusons-nous-avec-angular-babylonjs-et-la-realite-virtuelle"&gt;Dans la premi&amp;egrave;re partie de cet article&lt;/a&gt;, nous avons vu comment utiliser angular-cli pour cr&amp;eacute;er un projet angular "classique" et int&amp;eacute;grer babylonjs pour mettre de la 3D dans notre page web.&lt;/p&gt;
&lt;p&gt;Le titre de cette s&amp;eacute;rie d'article est : ".... et la r&amp;eacute;alit&amp;eacute; virtuelle".&lt;/p&gt;
&lt;p&gt;Vous allez me dire : pour l'instant, il y a autant de r&amp;eacute;alit&amp;eacute; virtuelle dans ton projet que de frites dans un axoa !!!&lt;/p&gt;
&lt;p&gt;D D&lt;img width="400" height="266" alt="" src="/Media/Default/BlogPost/articles/axoa.png" /&gt;&lt;/p&gt;
&lt;p&gt;(j'habite dans le pays basque et je cotoie des belges, d'ou la blague. Ok, je sors !!!)&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Bref, voyons comment ajouter de la VR dans tout ca. Vous allez voir, c'est super compliqu&amp;eacute;.&lt;/p&gt;
&lt;h1&gt;VRExperience&lt;/h1&gt;
&lt;p&gt;Avec la nouvelle version de BabylonJs, on a une nouvelle m&amp;eacute;thode de notre sc&amp;egrave;ne qui nous permet de cr&amp;eacute;er ce qu'ils appelent une exp&amp;eacute;rience VR.&lt;/p&gt;
&lt;div style="color: #d4d4d4; background-color: #1e1e1e; font-family: Consolas, 'Courier New', monospace; font-weight: normal; font-size: 14px; line-height: 19px; white-space: pre;"&gt;
&lt;div&gt;&lt;span style="color: #d4d4d4;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;span style="color: #569cd6;"&gt;this&lt;/span&gt;&lt;span style="color: #d4d4d4;"&gt;.vrExperienHelper &lt;/span&gt;&lt;span style="color: #d4d4d4;"&gt;=&lt;/span&gt;&lt;span style="color: #d4d4d4;"&gt; &lt;/span&gt;&lt;span style="color: #569cd6;"&gt;this&lt;/span&gt;&lt;span style="color: #d4d4d4;"&gt;.scene.createDefaultVRExperience();&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style="color: #d4d4d4;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;span style="color: #569cd6;"&gt;this&lt;/span&gt;&lt;span style="color: #d4d4d4;"&gt;.vrExperienHelper.enableInteractions();&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style="color: #d4d4d4;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;span style="color: #569cd6;"&gt;this&lt;/span&gt;&lt;span style="color: #d4d4d4;"&gt;.vrExperienHelper.displayLaserPointer &lt;/span&gt;&lt;span style="color: #d4d4d4;"&gt;=&lt;/span&gt;&lt;span style="color: #d4d4d4;"&gt; &lt;/span&gt;&lt;span style="color: #569cd6;"&gt;true&lt;/span&gt;&lt;span style="color: #d4d4d4;"&gt;;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;b&gt;&lt;/b&gt;&lt;i&gt;&lt;/i&gt;&lt;span style="text-decoration: underline;"&gt;&lt;/span&gt;&lt;sub&gt;&lt;/sub&gt;&lt;sup&gt;&lt;/sup&gt;&lt;span style="text-decoration: line-through;"&gt;&lt;/span&gt;Ici j'ai 3 lignes de codes, mais derri&amp;egrave;re (under the hood comme ils disent l&amp;agrave; bas), il y a plein de truc qui sont fait.&lt;/p&gt;
&lt;p&gt;Quand on appele la m&amp;eacute;thode createDefaultVRExperience, Babylonjs se charge de cr&amp;eacute;er une nouvelle cam&amp;eacute;ra (non active pour l'instant) et d'ajouter un nouveau bouton (personnalisable) en bas &amp;agrave; droite de votre &amp;eacute;cran qui permet &amp;agrave; l'utilisateur d'entrer dans le monde de la VR :&lt;/p&gt;
&lt;p&gt;&lt;img width="208" height="142" alt="" src="/Media/Default/BlogPost/articles/camVR.PNG" /&gt;&lt;/p&gt;
&lt;p&gt;Les deux lignes suivantes informent le helper VR que l'on veut &amp;ecirc;tre interactif et que l'on aime les rayons lasers (enfin que l'on ai un rayon sortant de notre manette VR).&lt;/p&gt;
&lt;p&gt;That's it ! C'est tout, c'est fini, ca roule, ca marche. Elle est pas cool la vie ?&lt;/p&gt;
&lt;p&gt;Enfin presque.&lt;/p&gt;
&lt;p&gt;Quand l'utilisateur clique sur ce fameux bouton, tout va bien, on entre alors dans l'univers VR : on est immerg&amp;eacute; dans notre image 360.&lt;/p&gt;
&lt;p&gt;MAIS&lt;/p&gt;
&lt;p&gt;Les manettes, sont pas terribles, et si vous regardez la console du navigateur, vous constaterez qu'il y a quelques petites erreurs.&lt;/p&gt;
&lt;p&gt;Ces erreurs sont d&amp;ucirc; au probl&amp;egrave;mes de chargement des mod&amp;egrave;les 3D qui repr&amp;eacute;sentent les manettes.&lt;/p&gt;
&lt;p&gt;Car ce qu'il y a de cool avec BabylonJs, c'est qu'il vous affiche ausi les manettes dans votre univers 3D.&lt;/p&gt;
&lt;h1&gt;Les manettes VR&lt;/h1&gt;
&lt;p&gt;Donc il faut s'assurer que ces mod&amp;egrave;les 3D soient bien charg&amp;eacute;es. Concr&amp;egrave;tement, elles sont sur le site de MS (https://controllers.babylonjs.com/microsoft/) au format glb. Il faut donc &amp;ecirc;tre sur de pouvoir lire le format glb. Pour cela, BabylonJs fournit diff&amp;eacute;rents "loaders" (Paint 3D, 3DS Max, etc.). Cool.&lt;/p&gt;
&lt;p&gt;Mais il faut int&amp;eacute;grer ces loaders dans notre projet. D'ou, dans notre package.json, l'ajout du package babylonjs-loaders :&lt;/p&gt;
&lt;div style="color: #d4d4d4; background-color: #1e1e1e; font-family: Consolas, 'Courier New', monospace; font-weight: normal; font-size: 14px; line-height: 19px; white-space: pre;"&gt;
&lt;div&gt;&lt;span style="color: #9cdcfe;"&gt;"babylonjs-loaders"&lt;/span&gt;&lt;span style="color: #d4d4d4;"&gt;: &lt;/span&gt;&lt;span style="color: #ce9178;"&gt;"^3.2.0-alpha0"&lt;/span&gt;&lt;span style="color: #d4d4d4;"&gt;,&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;b&gt;&lt;/b&gt;&lt;i&gt;&lt;/i&gt;&lt;span style="text-decoration: underline;"&gt;&lt;/span&gt;&lt;sub&gt;&lt;/sub&gt;&lt;sup&gt;&lt;/sup&gt;&lt;span style="text-decoration: line-through;"&gt;&lt;/span&gt;La deuxi&amp;egrave;me &amp;eacute;tape est de s'assurer que l'on ajoute cette r&amp;eacute;f&amp;eacute;rence dans notre projet. Donc dans notre app.module.ts :&lt;/p&gt;
&lt;div style="color: #d4d4d4; background-color: #1e1e1e; font-family: Consolas, 'Courier New', monospace; font-weight: normal; font-size: 14px; line-height: 19px; white-space: pre;"&gt;
&lt;div&gt;&lt;span style="color: #569cd6;"&gt;import&lt;/span&gt;&lt;span style="color: #d4d4d4;"&gt; &lt;/span&gt;&lt;span style="color: #ce9178;"&gt;'babylonjs-loaders'&lt;/span&gt;&lt;span style="color: #d4d4d4;"&gt;;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Le plugin qui permet de charger des fichiers glb est donc r&amp;eacute;f&amp;eacute;renc&amp;eacute; et charg&amp;eacute;.&lt;/p&gt;
&lt;p&gt;Moralit&amp;eacute; :&lt;/p&gt;
&lt;p&gt;Quand l'utilisateur clique sur le bouton VR, si il a bien entendu un casque Windows Mixed Reality branch&amp;eacute; avec ses controlleurs, il les verra s'afficher &amp;agrave; l'&amp;eacute;cran (ou plut&amp;ocirc;t dans son casque) :&lt;/p&gt;
&lt;p&gt;&lt;img width="928" height="928" alt="" src="/Media/Default/BlogPost/articles/amusons-nous-avec-angular-babylonjs-et-la-realite-virtuelle-partie-2/20180110_184538_MixedReality.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;L'&amp;eacute;tape suivante consistera &amp;agrave; rajouter un menu int&amp;eacute;ractif (que l'on fera pparaitre gr&amp;acirc;ce aux boutons de nos controlleurs).&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;</description><pubDate>Sat, 20 Jan 2018 10:29:29 GMT</pubDate><guid isPermaLink="true">http://www.c2i.fr:80/articles/amusons-nous-avec-angular-babylonjs-et-la-realite-virtuelle-partie-2</guid></item></channel></rss>