Angular Universal

Angular Universal

Angular Universal

La platform universal a été ajouté à angular dans la version 4 d’angular. Elle permet maintenant de générer les pages depuis le serveur.

La méthode « magique » de la plateforme universal est le renderModuleFactory provenant de @angular/platfom-server

La définition de ce renderModulefactory dans la doc d’Angular est

renderModuleFactory(
    moduleFactory: NgModuleFactory,
    options: PlatformOptions
): Promise

moduleFactory : Le premier argument de cette méthode est moduleFactory, qui est pour faire court une version compilé d’angular. Ce moduleFactory doit être buildé une seule fois.

options : quant à lui contient les informations sur la manière dont on souhaite voir notre application: PlatformOptions

PlatformOptions : Le type de PlatformOptions est définit dans les sources par l’interface suivante

interface PlatformOptions {
    document?: string;
    url?: string;
    extraProviders?: Provider[]
}

– le document indique le contenu du fichier HTML que l’on souhaite avoir dans notre app.
– l’urlL indique la page de notre application que l’on souhaite rendre

Installation

# on génere le projet avec un rooting de base

ng new sn-universal --rooting
cd sn-universal

# on installe ensuite plateform server nécéssaire pour le rendu coté serveur et pour la génération des pages html

npm install --save @angular/platform-server

# il faudra mettre à jour angular et tous les packages liés vers la version 4 minimum et tous les autres packages tel que core-js, rxjs, zone.js ( sinon nous aurons une erreur au build)

Création du module serveur

Nous allons maintenant créer le fichier qui va boostraper (initialiser) l’app depuis le serveur: src/app.server.module.ts

On crée un nouveau fichier nommé app.server.module.ts. Il est assez similaire au fichier app.module.ts de notre application, la grosse différence est que ce module est crée pour le serveur

import { NgModule } from '@angular/core';
import { ServerModule } from '@angular/platform-server';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';
 
@NgModule({
    imports: [
        ServerModule,
        AppModule
    ],
    bootstrap: [AppComponent]
})
export class AppServerModule {}

Mise à jour du module principal

Nous aurons aussi besoin d’indiquer à l’AppModule que tout le rendu se fera depuis le serveur.
Normalement, on bootstrap une app Angular depuis un AppModule qui importe le BrowserModule (qui met à disposition toute sorte de services et directives tel que par exemple ngIf )
PS: c’est platformBrowserDynamic qui est la méthode utilisée pour bootstrapper l’application pour l’app.
Mise à jour de app module afin de pouvoir utiliser le serveur

// dans /src/app/app.modules.ts
// BrowserModule devient
BrowserModule.withServerTransition({ appId : 'sn-universal' })

withServerTransition prend un appId et c’est lui qui est partagé entre le client et le serveur. withServerTransition permet à universal de remplacer le html généré par le sien.

A ce point on peut compiler notre app avec la commande ngc

$ node_modules/.bin/ngc

Si on ouvre le dossier dist, on remarquera que dans le dossier out-tsc/app se trouve dans les fichiers générés src/app.server.module.ngfactory.ts un fichier qui déclare et exporte un AppServerModuleNgFactory de type NgModuleFactory.

export const AppServerModuleNgFactory:i0.NgModuleFactory = i0.?cmf(i1.AppServerModule,
[i2.AppComponent],(_l:any) => {

Création du module server de l’application sous express

Il nous faut créer un fichier main.server.ts qui va s’occuper de prendre une url en argument te de faire le rendu de la bonne page de notre application.

import 'reflect-metadata';
import 'zone.js/dist/zone-node';
import { platformServer, renderModuleFactory } from '@angular/platform-server'
import { enableProdMode } from '@angular/core'
import { AppServerModuleNgFactory } from '../dist/ngfactory/src/app/app.server.module.ngfactory'
import * as express from 'express';
import { readFileSync } from 'fs';
import { join } from 'path';
  
const PORT = 4000;
  
enableProdMode();
  
const app = express();
  
let template = readFileSync(join(__dirname, '..', 'dist', 'index.html')).toString();
  
app.engine('html', (_, options, callback) => {
const opts = { document: template, url: options.req.url };
  
renderModuleFactory(AppServerModuleNgFactory, opts)
.then(html => callback(null, html));
});
  
app.set('view engine', 'html');
app.set('views', 'src')
  
app.get('*.*', express.static(join(__dirname, '..', 'dist')));
  
app.get('*', (req, res) => {
    res.render('index', { req });
});
  
app.listen(PORT, () => {
    console.log(`listening on http://localhost:${PORT}!`);
});

HS
La création du serveur sous express est hors sujet, on ne s’y attardera pas donc.

Mise à jour de de la config typescript

Dans le fichier de config /src/tsconfig.app.json on ajoutera server.ts dans les fichiers à exclure

"exclude": [
    "server.ts", // ICI
    "test.ts",
    "**/*.spec.ts"
]

Ajouter ensuite dans tysconfig.json angularCompilerOptions

{
    "compileOnSave": false,
    "compilerOptions": {
        ...
    ],
    "lib": [
        ...
    ]
},
    "angularCompilerOptions": { // ICI
        "genDir": "./dist/ngfactory",
        "entryModule": "./src/app/app.module#AppModule"
    }
}

angularCompilerOptions : permet de spécifier une propriété genDir qui correspond au dossier dans lequel les fichiers ngfactory générés seront exportés.
entryModule : on lui indique le chemin vers le module principal sur le lequel l’app bootstrap ( dans ce cas : AppModule)
Mise à jour du Script NPM

dans le package.json

"scripts": {
    "ng": "ng",
    "prestart": "ng build --prod && ngc", // on ajoute cette ligne
    "start": "ng serve", // on supprime celle ci
    "start": "ts-node src/server.ts" // et la remplace par celle ci
    "build": "ng build", // on supprime celle ci
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
},

prestart lancera d’abord ce script avant le start (grace au pre ajouté avant) ng build –prod && ngc puis le server est lancé

et zoo

on lance l’app

npm run start

et si tout ce passe bien vous devriez avoir un quelque chose comme ca

Mac-mini-de-Samuel:sn-universal saminou$ npm run start
  
    > sn-universal@0.0.0 prestart /Users/saminou/SitesSaminou/angular/sn-universal
    > ng build --prod && ngc
   
    Date: 2017-09-08T15:09:38.658Z
    Hash: 6c329e8c267199f5dbe3
    Time: 14528ms
    chunk {0} polyfills.c9b879328f3396b2bbe8.bundle.js (polyfills) 64.1 kB {4} [initial] [rendered]
    chunk {1} main.0f96096c1bb38373edb0.bundle.js (main) 5.15 kB {3} [initial] [rendered]
    chunk {2} styles.d41d8cd98f00b204e980.bundle.css (styles) 0 bytes {4} [initial] [rendered]
    chunk {3} vendor.3f618d3b9434d244b572.bundle.js (vendor) 307 kB [initial] [rendered]
    chunk {4} inline.d6e0fc0a02fc19c9d76f.bundle.js (inline) 1.45 kB [entry] [rendered]
   
    > sn-universal@0.0.0 start /Users/saminou/SitesSaminou/angular/sn-universal
    > ts-node src/server.ts
   
 listening on http://localhost:4000!

Si vous visitez http://localhost:4000! , dans le code source de la page vous devriez avoir le code correspondant à ce que vous avez en visu plutot que l’habituelle « app works »

angular universal source code

Générer un composant

Utiliser le cli pour générer un composant est assez facile avec les commandes fournies par le cli.

ng g c home
 
# équivalent de
ng generate component home

malheureusement avec nos modifications on aura une erreur du type

> ng g c home
    Error: More than one module matches. Use skip-import option to skip importing the component into the closest module.
    More than one module matches. Use skip-import option to skip importing the component into the closest module.

Pour éviter cela il suffit de spécifier le module en ajoutant un flag.

ng g c home  --module=app.module.ts
ng g c about --module=app.modue.ts

Définition des routes

Ouvrons maintenant le fichier routing app-routing.module.ts généré lors de la création de notre projet via le flag –routing

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
// on ajoute nos 2 nouveaux modules
import { HomeComponent} from './home/home.component';
import { AboutComponent } from './about/about.component';
 
// on met à jour le tableau des routes
const routes: Routes = [
    {
        path: '',
        component: HomeComponent
    },
    {
        path: 'about',
        component: AboutComponent
    }
];
 
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

Mise à jour des templates

Ouvrons le ficher /src/app.component.html et mettons le à jour avec les liens

 

 

 

Ajout des titles et meta tags à nos components

C’est assez simple , il suffit d’importer Meta et Title depuis @angular/platform-browser

puis de mettre à jour le constructeur

import { Meta, Title } from '@angular/platform-browser';
 
# dans le constructeur
 
export class HomeComponent implements OnInit {
// on utilise l'injection de dépendance pour créer des instances de Meta et Title
constructor(meta: Meta, title: Title) {
 
 
title.setTitle('Supa Karma Page');
meta.addTags([
        { name: 'author', content:'Samsam'},
        { name: 'keywords', content:'angular, seo, universal'},
        { name: 'description', content:'tada une page avec un code source qui est à jour'},
        ])
    }
 
    ngOnInit() {
 
    }
}

Et voila

 

Les pièges à éviter

Universal ne peut utiliser les objets globaux (window, document, localstorage) existants au runtime du navigateur.

Le DOM doit être manipulé par Angular et non de façon externe (vanilla Js, jQuery : par exemple document.domMethod() ou encore $(‘dom-element’))..

Le server side rendering n’est pas compatible avec le lazy loading des modules.

 

Angular-cli et Sass

Angular Cli

Démarrer un projet Angular sous Sass

Angular Cli

Pour tous ceux qui souhaitent utiliser Angular-cli avec Sass facilement voir la démarche:

Lorsqu’on travaille avec le cli, la feuille de style par défaut se termine par .css. Ce qui nous intéresse c’est d’utiliser l’extension scss sur nos fichiers.

Tout d’abord, pour démarrer un nouveau projet angular utilisant .sass , il nous suffit d’utiliser le flag –style

ng new mon-projet-sous-sass --style=scss

On pourrait aussi utiliser du sass ou less mais ce n’est pas ce qui nous intéresse : –style =less

Convertir un projet angular vers Sass

Si vous aviez déjà créé un projet utilisant simplement .css. Cela va nécéssiter un tout petit plus de travail pour le convertir en scss.

Tout d’abord on demande à angular de convertir notre projet pour utiliser le scss

ng set default.styleExt scss

On peut aussi le faire en modifiant le fichier .angular-cli.json

"defaults": {
    "styleExt": "scss",
    "component": {}
  }

Malheusement cela ne modifiera pas les fichier créés. Pour les anciens fichier .css pour devrez les modifier à la main, chager l’extension .css en .scss et penser à modifier dans le component le stylesUrls

@Component({
  styleUrls: ['./mon-component.scss'],
})

Utiliser l’import Sass plus simplement

Si vous créer des mixins ou des variables, le plus souvent , ceux ci sont dans des fichiers externes dans votre dossier sass

par exemple:
|- src/
|- assets/
|- sass/
|- _variables.scss
|- _mixins.scss
|- styles.scss

Pour utiliser nos mixins et variables dans le fichiers styles.scss, on les importe:

/ fichier styles.scss
  
@import './assets/sass/variable';
@import './assets/sass/mixins';

Rien de fou ici, ca commence à devenir plus contraignant lorsqu’on souhaite utiliser ses mêmes variables dans des composants angular qu’on aurait créé.
Exemple de cas
|- src/
|- app/
|- header
|- social/
|- social.component.html
|- social.component.scss
|- social.component.ts
|- menu/
|- menu.component.html
|- menu.component.scss
|- menu.component.ts
|- search/
|- search.component.html
|- search.component.scss
|- search.component.ts
|- assets/
|- sass/
|- _variables.scss
|- _mixins.scss
|- styles.scss

Si nous devions importer variables et mixins dans nos 3 fichiers de styles, il aurait fallu y accéder en prenant en compte le chemin relatif du dossier sass par rapport au component.

// fichier component.scss
  
@import "../../../assets/sass/mixins";
@import "../../../assets/sass/variables";

Heureusement Angular-cli propose une manière simplifié d’importer des fichiers sass: il faut utiliser ~

// fichier component.scss
  
@import "~assets/sass/mixins";
@import "~assets/sass/variables";

Angular cli + Bootstrap 4

Bootstrap 4

Pour ceux qui utilisent bootstrap.
On installe d’abord bootstrap via npm

npm install --save boostrap@4.0.0-beta

Ajout du css bootstrap dans le fichier config : .angular-cli.json

"styles": [
  "../node_modules/bootstrap/dist/css/bootstrap.css",
  "./styles.scss"
],

On utilise ../node_modules car le point de départ du cli est le dossier src/

Ajout des fichiers sass

Nous n’avons pas toujours besoin d’importer tous les fichiers .
Dans le dossier bootstrap, nous avons tous ces fichiers :

      Bootstrap
      /*!
       * Bootstrap v4.0.0-beta (https://getbootstrap.com)
       * Copyright 2011-2017 The Bootstrap Authors
       * Copyright 2011-2017 Twitter, Inc.
       * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
       */

      @import "functions";
      @import "variables";
      @import "mixins";
      @import "print";
      @import "reboot";
      @import "type";
      @import "images";
      @import "code";
      @import "grid";
      @import "tables";
      @import "forms";
      @import "buttons";
      @import "transitions";
      @import "dropdown";
      @import "button-group";
      @import "input-group";
      @import "custom-forms";
      @import "nav";
      @import "navbar";
      @import "card";
      @import "breadcrumb";
      @import "pagination";
      @import "badge";
      @import "jumbotron";
      @import "alert";
      @import "progress";
      @import "media";
      @import "list-group";
      @import "close";
      @import "modal";
      @import "tooltip";
      @import "popover";
      @import "carousel";
      @import "utilities";

Beaucoup de fichiers non nécéssaire pour vos projets

Pour utiliser bootstrap, nous n’avons besoin que de ce strict minimum

// src/styles.scss
 
@import
  '~bootstrap/scss/functions',
  '~bootstrap/scss/variables',
  '~bootstrap/scss/grid',
  '~bootstrap/scss/mixins',
  '~bootstrap/scss/print',
  '~bootstrap/scss/reboot',
  '~bootstrap/scss/type';

Avec le ~, importé des fichiers sass devient très facile.
+++

 

sass

Angular Cli – angular-cli.json

Pour les projets sous angular2, j’utilise angular-cli , j’ai eu besoin de modifier la configuration de base.

Dans le fichier angular-cli.json

"apps": [
 
// utiliser votre préfixe avec  vos components
 "prefix": "ns",
// ajouter des js externe
"scripts": [
        "../node_modules/moment/moment.js"
      ],
// Ajouter des css spécifiques
 "styles": [
        "../node_modules/font-awesome/css/font-awesome.css",
        "./styles.scss"
      ],
]
 
// Ajouter des typos
 
"addons": [
"../node_modules/font-awesome/fonts/*.+(otf|eot|svg|ttf|woff|woff2)",
"./assets/fonts/glyphicons/*.+(otf|eot|svg|ttf|woff|woff2)",
],

Dans le terminal

 
    # Choisir un port spécifique pour le serve
    ng serve --port 4201
 
    # mettre à jour angular cli
    # sur votre machine (le cli étant installé en global)
    npm uninstall -g angular-cli
    npm cache clean
    npm install -g angular-cli@latest
 
    # le projet ou le cli est utilisé
    rm -rf node_modules dist tmp
    npm install --save-dev angular-cli@latest
    npm install
    ng init

Mettre à jour NPM et gérer les versions de node.js

Node JS

Node.js est mis à jour très régulièrement. Il devient vite difficile de suivre et de garder les bonnes versions sur sa machine surtout si vous travailler avec différentes versions.

Installer Node.js

Si vous installer Node pour la première fois, vous pouvez l’installer depuis le site internet nodejs.org avec l’installateur proposé.

Node Js
Node Js

Installer, mettre à jour, désinstaller via le terminal

Si vous souhaitez l’installer via le terminal, il suffit d’utiliser Homebrew. Vous aurez aussi besoin d’avoir xCode à jour.

# Installer Homebrew
# gestionnaire de package pour mac
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
 
# Installer nodeJs
brew install node
 
# verifier si node et npm sont installer
node -v
 
npm -v
 
# mettre à jour brew
brew update
 
# mettre à jour node
brew upgrade node
 
# désinstaller Node et NPM
brew uninstall node

Mettre à jour Node

Mise à jour depuis le site nodejs.org

Il suffit de récupérer l’installateur et de le réinstaller….

Gérer plusieurs versions de Node.js

A un moment ou un autre vous aurez besoin de switcher entre les différentes versions de node. Pour le faire facilement il y a deux solution : Node version Manager (nvm) et n.

Node version manager (nvm)

https://github.com/creationix/nvm

NVM est un script bash permettant

  • d’installer multiple versions de node
  • mettre à jour vers la dernière version
  • choisir la version par défaut du système
  • lister toutes les versions de node sur le système
# installer nvm 
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.1/install.sh | bash
 
# voir quelles versions de node sont installer
nvm ls
 
# voir les versions disponible
nvm ls-remote
 
# installer une version
nvm install v7.3.0
 
# définir une version comme étant celle par défaut
# nvm alias default v7.3.0

n

https://github.com/tj/n

n est gestionnaire de package similaire à nvm.

La grosse différence est qu’il faut déjà avoir node pour l’installer.

Ou alors cloner le dépot et faire un make install

 

# installer n 
npm install -g n
 
# voir quelles versions de node sont installer
n
 
# installer la dernière version
n latest
 
# installer la dernière version stable
n stable
 
# installer une version
n 7.3.0
 
# supprimer une version
n rm 7.3.0 
 
# ou
n - 7.3.0

[Visual Studio Code] Lancer VSC depuis le terminal

Visual Studio Code

Pour pouvoir lancez directement Visual Studio depuis le terminal en tapant « code », il faut :

1 – Aller dans Visual Studio Code.
2 – Taper (maj cmd P) puis shell command.
3 – Activer l’option et voilà !

Ensuite vous pourrez directement le lancez avec la variable code.

# ouvrir tout un répertoire dans vsc

cd mon-projet .

# ouvrir tout un fichier dans vsc

cd mon-projet index.scss

 

Ajouter un beau hover à la galerie par défaut de WordPress avec jquery

jquery

Pour mon portofolio, j’avais besoin d’améliorer la galerie photo de base de WordPress.
Elle me convenait, toutefois je souhaitais que sur le roll hover apparaisse un fond blanc sur lequel le nom de la référence soit marqué.
L’idée est de récupérer le title de l’image et de le coller dans le h3 d’un div caché qui ne s’afficherait qu’au passage de la souris.

L’exemple se trouve ici : http://webdesign.niums.com/clients/

Pour rappel voilà de quoi est composée la galerie créée par WordPress
.gallery : class de la galerie
.gallery-item : dl : conteneur
.gallery-icon :dt : élément à définir
.attachment-thumbnail : notre miniature

Voilà ce que nous allons y ajouter grâce à jquery
.hide : un div qui contiendra notre texte
h3 : une balise de titre
XXX : notre texte en le copiant depuis la balise title de l’image

Préparons nos fichiers

Nous aurons besoin
– de créer un fichier javascript externe que nous chargerons : site.js
– de modifier le fichier css de wordpress : style.css

Tout d’abord créons un fichier javascript que nous enregistrerons dans un dossier js de notre dossier de thème.

Nous appellerons ce fichier dans le header.php ou le footer.php selon notre convenance : soit avec un chargement en mémoire avec un enqueue soit directement dans le fichier header

Charger les scripts en mémoire : Version wp_enqueue_scripts

Dans le fichier function php, ajoutons le code ci-dessous

 
/**
 * chargement de jquery, de la feuille de style et du fichier js
 */
function niums_scripts() {
 
 /* 
 chargeons jquery 
 wp_enqueue_scripts action hook charge seulement en front end 
 si les scripts sont nécessaires en admin on peut aussi 
 utiliser admin_enqueue_scripts 
 */	
 
   // l'appel à jquery qui est déjà inclus dans wordpress
   wp_enqueue_script('jquery'); 
   // appel du fichier styles.css
   wp_enqueue_style( 'style', get_stylesheet_uri() );
 
   // chargement de site.js en faisant attention à ce que jquery soit chargé avant 
   // (en tant que dépendance) puis on le charge dans le footer
   //wp_enqueue_script( $handle    ,$src    ,$deps    ,$ver    ,$in_footer );
    wp_enqueue_script( 'site.js', get_template_directory_uri() . '/js/site.js', array( 'jquery' ), '' , TRUE );
 
}
 
add_action( 'wp_enqueue_scripts', 'niums_scripts' );

Charger directement les scripts dans le header

Si pour une raison ou une autre, le chargement en mémoire ne vous convient pas, vous pouvez aussi charger directement les fichiers dans header.php entre head et /head

 
// Par défaut, jQuery est chargé dans le thème, pas besoin de le recharger
// charger le fichier js
<script type="text/javascript" src="<?php bloginfo('template_url'); ?>/js/site.js"></script>
 
// votre fichier style est normalement déjà chargé comme suit
 <link rel="stylesheet" href="<?php bloginfo('stylesheet_url'); ?>" type="text/css" media="screen" />

Création de notre js

Maintenant que tous nos fichiers sont chargés, nous pouvons créer nos fonctions.

PS : J’ai modifié mon appel à la fonction principale qui se fait lorsque le document est prêt afin qu’on puisse utiliser $() comme alias et cela sans conflit.

jQuery(document).ready(function($){
    // ici on peut utiliser $() avec la certitude que c'est jQuery, sans conflit possible
   // le code qui suit se placera donc ici
});

Pour commencer, ajoutons la classe wrap à chaque liste de définitions.
Cette class permettra aux listes d’avoir des positions « relative » afin que le futur div .hide, qui contient notre texte, puisse être positionné au-dessus des images car en position « absolute »

 
$(".gallery-item").addClass("wrap");
 
// puis attachons un gestionnaire d'évènement à la class wrap
 
$(".wrap").on({
   mouseover: function() {
      	$(".hide", this).stop().fadeTo(300, 1.0); // on définit le hover à 100%
   },
     mouseout: function() {
      	$(".hide", this).stop().fadeTo(300, 0); //  on définit le hover à 0%
  }
 });
 
//.puis nous ajoutons notre div .hide :  après  .gallery-icon 
 
$(".gallery .gallery-item .gallery-icon").append('<div class="\'hide\'"></div>');
 
// puis nous créons un h3 dans le .hide
 
$(".gallery .gallery-item .gallery-icon .hide").wrapInner('<h3>'); 
 
// puis nous copions le title dans le h3 en le recopiant depuis la balise title de l'image 
 
$(".gallery .gallery-item .gallery-icon").each(function (index) { 
var leTitre = $(this).find(".attachment-thumbnail").attr('alt');
$(this).find(".hide h3").text(leTitre);

Pour infos jQuery peut accepter un second paramètre afin d’écraser le contexte de la sélection

 $("img", this);
// est la même chose que
 $(this).find("img");

Et si l’image est un descendant direct de l’élément cliqué alors nous pouvons aussi écrire :

$(this).children("img");

En ce qui concerne le css, rien de spécial

Ajoutez le code ci-dessous dans votre fichier style.css

/* Wraps */
.wrap {
	position:relative;
	width:100%;
	height:auto;
}
 
.hide {
        display:none;
	background-color:#fff;
	background-color:rgba(255,255,255,.9);
	position:absolute;
	top:0;
	left:0;
	width:100%;
	height:100%;
	z-index:2;
}

Pour info

Dans WordPress il est possible de choisir le nombre d’images par ligne.
Dans le cas contraire, pour décider du nombre d’éléments par ligne, on pourrait aussi écrire :

 
dl dt:nth-child(4n){clear:left;} 
//ou
ul li:nth-child(4n){clear:left;}

Voilà +++