Skip to content

Meilleures pratiques pour une application angulaire propre et performante

Notifications You must be signed in to change notification settings

mohamedDev/Application-Angular-propre-et-performante

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 

Repository files navigation

Meilleures pratiques pour une application Angular propre et performante

Cet article décrit les pratiques que nous utilisons dans notre application et concerne Angular, Typescript, RxJs et @ ngrx / store. Nous allons également passer en revue certaines directives générales de codage pour aider à rendre l'application plus propre.

1) trackBy

Lorsque vous utilisez ngForpour parcourir en boucle un tableau dans les modèles, utilisez-le avec une trackByfonction qui renverra un identifiant unique pour chaque élément.

Pourquoi?

Lorsqu'un tableau est modifié, Angular restitue l'ensemble de l'arborescence DOM. Mais si vous utilisez trackBy, Angular saura quel élément a été modifié et n'apportera que des modifications du DOM pour cet élément en particulier.

Pour une explication détaillée à ce sujet, veuillez vous reporter à cet article de Netanel Basal .

Avant

<li *ngFor="let item of items;">{{ item }}</li>

Après

// in the template

<li *ngFor="let item of items; trackBy: trackByFn">{{ item }}</li>

// in the component

trackByFn(index, item) {    
   return item.id; // unique id corresponding to the item
}

2) const vs let

Lors de la déclaration de variables, utilisez const lorsque la valeur ne sera pas réaffectée.

Pourquoi?

Utiliser letet, le constcas échéant, clarifie l’intention des déclarations. Cela aidera également à identifier les problèmes lorsqu'une valeur est réaffectée accidentellement à une constante en générant une erreur de temps de compilation. Cela contribue également à améliorer la lisibilité du code.

Avant

let car = 'ludicrous car';

let myCar = `My ${car}`;
let yourCar = `Your ${car};

if (iHaveMoreThanOneCar) {
   myCar = `${myCar}s`;
}

if (youHaveMoreThanOneCar) {
   yourCar = `${youCar}s`;
}

Après

// the value of car is not reassigned, so we can make it a const
const car = 'ludicrous car';

let myCar = `My ${car}`;
let yourCar = `Your ${car};

if (iHaveMoreThanOneCar) {
   myCar = `${myCar}s`;
}

if (youHaveMoreThanOneCar) {
   yourCar = `${youCar}s`;
}

3) opérateurs canalisés

Utilisez des opérateurs canalisables lorsque vous utilisez des opérateurs RxJs.

Pourquoi?

Les opérateurs pipeables sont arborescents, ce qui signifie que seul le code que nous devons exécuter sera inclus lors de leur importation.

Cela facilite également l'identification des opérateurs non utilisés dans les fichiers.

Remarque: Cela nécessite la version 5.5 + angulaire.

Avant

import 'rxjs/add/operator/map';
import 'rxjs/add/operator/take';

iAmAnObservable
    .map(value => value.item)
    .take(1);

Après

import { map, take } from 'rxjs/operators';

iAmAnObservable
    .pipe(
       map(value => value.item),
       take(1)
     );

4) Isoler les hacks de l'API

Toutes les API ne sont pas à l'épreuve des balles - nous avons parfois besoin d'ajouter une certaine logique dans le code pour remédier aux bogues dans les API. Au lieu d’avoir les hacks dans les composants où ils sont nécessaires, il est préférable de les isoler en un seul endroit - comme dans un service et d’utiliser le service à partir du composant.

Pourquoi?

Cela aide à garder les hacks «plus proches de l'API», aussi près que possible de l'endroit où la requête réseau est faite. De cette façon, moins de votre code traite du code non piraté. En outre, c’est un endroit où vivent tous les hacks et il est plus facile de les trouver. Lorsque vous corrigez les bogues dans les API, il est plus facile de les rechercher dans un seul fichier plutôt que de rechercher les hacks pouvant être répartis dans la base de code.

Vous pouvez également créer des balises personnalisées, telles que API_FIX, similaires à TODO, et baliser les correctifs avec elle afin de faciliter la recherche.

5) S'abonner au modèle

Évitez de vous abonner à des observables à partir de composants, mais souscrivez plutôt aux observables à partir du modèle.

Pourquoi?

asyncLes tubes se désabonnent automatiquement et cela simplifie le code en éliminant la nécessité de gérer manuellement les abonnements. Cela réduit également le risque d'oubli accidentel de la désabonnement d'un composant dans le composant, ce qui provoquerait une fuite de mémoire. Ce risque peut également être atténué en utilisant une règle de protection pour détecter les éléments observables non souscrits.

Cela empêche également les composants d'être dynamiques et d'introduire des bogues où les données sont mutées en dehors de l'abonnement.

Avant

// // template

<p>{{ textToDisplay }}</p>

// component

iAmAnObservable
    .pipe(
       map(value => value.item),
       takeUntil(this._destroyed$)
     )
    .subscribe(item => this.textToDisplay = item);

Après

// template

<p>{{ textToDisplay$ | async }}</p>

// component

this.textToDisplay$ = iAmAnObservable
    .pipe(
       map(value => value.item)
     );

6) Nettoyer les abonnements

Lors de la souscription à des observables, assurez - vous toujours vous vous désabonnez de manière appropriée en utilisant des opérateurs comme take, takeUntil, etc.

Pourquoi?

Si vous ne vous désabonnez pas des données observables, des fuites de mémoire indésirables se produiront si le flux d'observables reste ouvert, même après la destruction d'un composant ou la navigation de l'utilisateur vers une autre page.

Mieux encore, créez une règle anti-peluches pour détecter les éléments observables non abonnés.

Avant

iAmAnObservable
    .pipe(
       map(value => value.item)     
     )
    .subscribe(item => this.textToDisplay = item);

Après

Utiliser takeUntilquand vous voulez écouter les changements jusqu'à ce qu'un autre observable émette une valeur:

private _destroyed$ = new Subject();

public ngOnInit (): void {
    iAmAnObservable
    .pipe(
       map(value => value.item)
      // We want to listen to iAmAnObservable until the component is destroyed,
       takeUntil(this._destroyed$)
     )
    .subscribe(item => this.textToDisplay = item);
}

public ngOnDestroy (): void {
    this._destroyed$.next();
    this._destroyed$.complete();
}

L'utilisation d'un sujet privé comme celui-ci constitue un modèle permettant de gérer la désinscription de nombreux observables dans le composant.

Utiliser takequand vous voulez seulement la première valeur émise par l'observable:

iAmAnObservable
    .pipe(
       map(value => value.item),
       take(1),
       takeUntil(this._destroyed$)
    )
    .subscribe(item => this.textToDisplay = item);

Notez l'utilisation de takeUntilavec takeici. Cela permet d'éviter les fuites de mémoire lorsque l'abonnement n'a pas reçu de valeur avant la destruction du composant. Sans cette takeUntiloption, l'abonnement resterait en attente jusqu'à ce qu'il obtienne la première valeur, mais comme le composant est déjà détruit, il ne recevra jamais de valeur, ce qui entraînerait une fuite de mémoire.

7) Utiliser des opérateurs appropriés

Lorsque vous utilisez des opérateurs d'aplatissement avec vos observables, utilisez l'opérateur approprié à la situation.

switchMap: quand vous voulez ignorer les émissions précédentes quand il y a une nouvelle émission

mergeMap: quand vous voulez gérer simultanément toutes les émissions

concatMap: quand vous voulez gérer les émissions l'une après l'autre au fur et à mesure de leur émission

exhaustMap: lorsque vous souhaitez annuler toutes les nouvelles émissions tout en traitant une émission précédente

Pour une explication plus détaillée à ce sujet, veuillez vous référer à cet article de Nicholas Jamieson .

Pourquoi?

Utiliser un seul opérateur lorsque cela est possible au lieu de chaîner plusieurs autres opérateurs pour obtenir le même effet peut entraîner l'envoi de moins de code à l'utilisateur. L'utilisation d'opérateurs incorrects peut entraîner un comportement indésirable, car différents opérateurs traitent les observables de différentes manières.

8) charge paresseuse

Dans la mesure du possible, essayez de charger paresseusement les modules dans votre application Angular. Le chargement différé consiste à charger quelque chose uniquement lorsqu'il est utilisé, par exemple à charger un composant uniquement lorsqu'il doit être vu.

Pourquoi?

Cela réduira la taille de l'application à charger et peut améliorer le temps de démarrage de l'application en ne chargeant pas les modules non utilisés.

Avant

// app.routing.ts

{ path: 'not-lazy-loaded', component: NotLazyLoadedComponent }

Après

// app.routing.ts

{ 
  path: 'lazy-load',
  loadChildren: 'lazy-load.module#LazyLoadModule' 
}

// lazy-load.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { LazyLoadComponent }   from './lazy-load.component';

@NgModule({
  imports: [
    CommonModule,
    RouterModule.forChild([
         { 
             path: '',
             component: LazyLoadComponent 
         }
    ])
  ],
  declarations: [
    LazyLoadComponent
  ]
})
export class LazyModule {}

9) Évitez d'avoir des abonnements à l'intérieur d'abonnements

Parfois, vous voudrez peut-être utiliser les valeurs de plusieurs valeurs pour effectuer une action. Dans ce cas, évitez de vous abonner à un observable dans le bloc d'abonnement d'un autre observable. Utilisez plutôt des opérateurs de chaînage appropriés. Les opérateurs de chaînage fonctionnent sur des observables de l'opérateur qui les précède. Certains opérateurs sont enchaînant: withLatestFrom, combineLatest, etc.

Avant

firstObservable$.pipe(
   take(1)
)
.subscribe(firstValue => {
    secondObservable$.pipe(
        take(1)
    )
    .subscribe(secondValue => {
        console.log(`Combined values are: ${firstValue} & ${secondValue}`);
    });
});

Après

firstObservable$.pipe(
    withLatestFrom(secondObservable$),
    first()
)
.subscribe(([firstValue, secondValue]) => {
    console.log(`Combined values are: ${firstValue} & ${secondValue}`);
});

Pourquoi?

Odeur de code / lisibilité / complexité : ne pas utiliser pleinement les fichiers RxJ, suggère au développeur de ne pas connaître la surface de l'API RxJs.

Performance : Si les observables sont froides, il s’abonnera à firstObservable, attendez qu’il soit terminé, puis lancez le travail du deuxième observable. S'il s'agissait de demandes de réseau, le résultat serait synchrone / cascade.

10) éviter tout; tapez tout;

Déclarez toujours les variables ou les constantes avec un type autre que any.

Pourquoi?

Lors de la déclaration de variables ou de constantes dans Typescript sans saisie, la saisie de la variable / constante est déduite de la valeur qui lui est affectée. Cela causera des problèmes inattendus. Un exemple classique est:

const x = 1;
const y = 'a';
const z = x + y;

console.log(`Value of z is: ${z}`

// Output
Value of z is 1a

Cela peut entraîner des problèmes indésirables lorsque vous vous attendez à ce qu’un nombre soit également défini. Ces problèmes peuvent être évités en tapant les variables de manière appropriée.

const x: number = 1;
const y: number = 'a';
const z: number = x + y;

// This will give a compile error saying:

Type '"a"' is not assignable to type 'number'.

const y:number

De cette façon, nous pouvons éviter les bugs causés par des types manquants.

Un bon avantage à avoir de bonnes frappe dans votre application est que cela rend le refactoring plus facile et plus sûr.

Considérons cet exemple:

public ngOnInit (): void {
    let myFlashObject = {
        name: 'My cool name',
        age: 'My cool age',
        loc: 'My cool location'
    }
    this.processObject(myFlashObject);
}

public processObject(myObject: any): void {
    console.log(`Name: ${myObject.name}`);
    console.log(`Age: ${myObject.age}`);
    console.log(`Location: ${myObject.loc}`);
}

// Output
Name: My cool name
Age: My cool age
Location: My cool location

Disons que nous voulons renommer la propriété locà locationen myFlashObject:

public ngOnInit (): void {
    let myFlashObject = {
        name: 'My cool name',
        age: 'My cool age',
        location: 'My cool location'
    }
    this.processObject(myFlashObject);
}

public processObject(myObject: any): void {
    console.log(`Name: ${myObject.name}`);
    console.log(`Age: ${myObject.age}`);
    console.log(`Location: ${myObject.loc}`);
}

// Output
Name: My cool name
Age: My cool age
Location: undefined

Si nous n'avons pas de taper sur myFlashObject, il pense que la propriété locsur myFlashObjectest tout simplement indéfini plutôt que ce n'est pas une propriété valide.

Si nous tapions pour myFlashObject, nous aurions une belle erreur de compilation, comme indiqué ci-dessous:

type FlashObject = {
    name: string,
    age: string,
    location: string
}

public ngOnInit (): void {
    let myFlashObject: FlashObject = {
        name: 'My cool name',
        age: 'My cool age',
        // Compilation error
        Type '{ name: string; age: string; loc: string; }' is not assignable to type 'FlashObjectType'.
        Object literal may only specify known properties, and 'loc' does not exist in type 'FlashObjectType'.
        loc: 'My cool location'
    }
    this.processObject(myFlashObject);
}

public processObject(myObject: FlashObject): void {
    console.log(`Name: ${myObject.name}`);
    console.log(`Age: ${myObject.age}`)
    // Compilation error
    Property 'loc' does not exist on type 'FlashObjectType'.
    console.log(`Location: ${myObject.loc}`);
}

Si vous démarrez un nouveau projet, il est utile de définir strict:truedans le tsconfig.jsonfichier toutes les options de vérification de type strictes.

11) Utiliser les règles de la charpie

[tslint](https://palantir.github.io/tslint/)a différentes options intégrées dans déjà comme [no-any](https://palantir.github.io/tslint/rules/no-any), [no-magic-numbers](https://palantir.github.io/tslint/rules/no-magic-numbers), [no-console](https://palantir.github.io/tslint/rules/no-console), etc que vous pouvez configurer dans votre tslint.jsonpour faire respecter certaines règles dans votre base de code.

Pourquoi?

Disposer de règles anti-peluches signifie que vous obtiendrez une belle erreur lorsque vous ferez quelque chose que vous ne devriez pas être. Cela assurera la cohérence de votre application et sa lisibilité. Veuillez vous référer ici pour plus de règles que vous pouvez configurer.

Certaines règles relatives à la charpie viennent même avec des corrections pour résoudre l’erreur de charpie. Si vous souhaitez configurer votre propre règle de charpie personnalisée, vous pouvez également le faire. Veuillez vous référer à cet article de Craig Spence pour savoir comment écrire vos propres règles personnalisées à l'aide de TSQuery .

Avant

public ngOnInit (): void {
    console.log('I am a naughty console log message');
    console.warn('I am a naughty console warning message');
    console.error('I am a naughty console error message');
}

// Output
No errors, prints the below on console window:
I am a naughty console message
I am a naughty console warning message
I am a naughty console error message

Après

// tslint.json
{
    "rules": {
        .......
        "no-console": [
             true,
             "log",    // no console.log allowed
             "warn"    // no console.warn allowed
        ]
   }
}

// ..component.ts

public ngOnInit (): void {
    console.log('I am a naughty console log message');
    console.warn('I am a naughty console warning message');
    console.error('I am a naughty console error message');
}

// Output
Lint errors for console.log and console.warn statements and no error for console.error as it is not mentioned in the config

Calls to 'console.log' are not allowed.
Calls to 'console.warn' are not allowed.

12) Petits composants réutilisables

Extrayez les pièces pouvant être réutilisées dans un composant et créez-en un nouveau. Faites le composant aussi bête que possible, car cela le fera fonctionner dans plus de scénarios. Rendre un composant muet signifie que le composant ne contient aucune logique particulière et fonctionne uniquement en fonction des entrées et des sorties qui lui sont fournies.

En règle générale, le dernier enfant de l'arborescence des composants sera le plus stupide de tous.

Pourquoi?

Les composants réutilisables réduisent la duplication du code, facilitant ainsi la maintenance et les modifications.

Les composants muets sont plus simples, ils sont donc moins susceptibles d'avoir des bogues. Les composants stupides vous incitent à réfléchir plus sérieusement à l'API de composant public et vous aident à détecter les problèmes divers.

13) Les composants ne doivent traiter que de la logique d'affichage

Évitez toute logique autre que la logique d'affichage dans votre composant et faites en sorte que le composant ne traite que de la logique d'affichage.

Pourquoi?

Les composants sont conçus à des fins de présentation et contrôlent ce que la vue doit faire. Toute logique métier doit être extraite dans ses propres méthodes / services, le cas échéant, en séparant la logique métier de la logique de vue.

La logique métier est généralement plus facile à tester à l'unité lorsqu'elle est extraite vers un service et peut être réutilisée par tout autre composant nécessitant l'application de la même logique métier.

14) Évitez les longues méthodes

Les méthodes longues indiquent généralement qu'ils font trop de choses. Essayez d'utiliser le principe de responsabilité unique. La méthode elle-même dans son ensemble pourrait faire une chose, mais à l'intérieur de celle-ci, il y a quelques autres opérations qui pourraient se produire. Nous pouvons extraire ces méthodes dans leur propre méthode et leur demander de faire une chose chacune et de les utiliser à la place.

Pourquoi?

Les méthodes longues sont difficiles à lire, à comprendre et à maintenir. Ils sont également sujets aux bugs, car changer une chose peut affecter beaucoup d'autres choses dans cette méthode. Ils rendent également difficile la refactorisation (qui est un élément clé dans toute application).

Ceci est parfois mesuré en tant que " complexité cyclomatique ". Il existe également certaines règles TSLint pour détecter la complexité cyclomatique / cognitive, que vous pouvez utiliser dans votre projet pour éviter les bogues et détecter les odeurs de code et les problèmes de maintenabilité.

15) SEC

Ne te répète pas. Assurez-vous de ne pas copier le même code à différents endroits de la base de code. Extrayez le code répété et utilisez-le à la place du code répété.

Pourquoi?

Avoir le même code à plusieurs endroits signifie que si nous voulons modifier la logique de ce code, nous devons le faire à plusieurs endroits. Cela le rend difficile à maintenir et est également sujet à des bogues pour lesquels nous pourrions manquer de le mettre à jour dans tous les cas. Il faut plus de temps pour apporter des modifications à la logique et le tester est également un processus long.

Dans ces cas, extrayez le code répétitif et utilisez-le à la place. Cela signifie qu'un seul endroit pour changer et une chose à tester. Avoir moins de code en double envoyé aux utilisateurs signifie que l'application sera plus rapide.

16) Ajouter des mécanismes de cache

Lors des appels API, les réponses de certaines d’entre elles ne changent pas souvent. Dans ces cas, vous pouvez ajouter un mécanisme de mise en cache et stocker la valeur de l'API. Quand une autre demande est faite à la même API, vérifiez si elle a une valeur dans le cache et si oui, utilisez-la. Sinon, appelez l'API et mettez le résultat en cache.

Si les valeurs changent mais pas fréquemment, vous pouvez introduire une heure de cache afin de vérifier la date de la dernière mise en cache et décider d'appeler ou non l'API.

Pourquoi?

Avoir un mécanisme de cache signifie éviter les appels d'API indésirables. En effectuant uniquement les appels d'API lorsque cela est nécessaire et en évitant les doublons, la vitesse de l'application s'améliore, car nous n'avons pas à attendre le réseau. Cela signifie également que nous ne téléchargeons pas la même information, encore et encore.

17) Évitez la logique dans les modèles

Si vos modèles contiennent une quelconque logique, même s’il s’agit d’une simple &&clause, il est bon de l’extraire dans son composant.

Pourquoi?

Avoir une logique dans le modèle signifie qu'il n'est pas possible de le tester à l'unité et qu'il est donc plus sujet aux bugs lors de la modification du code du modèle.

Avant

// template
<p *ngIf="role==='developer'"> Status: Developer </p>

// component
public ngOnInit (): void {
    this.role = 'developer';
}

Après

// template
<p *ngIf="showDeveloperStatus"> Status: Developer </p>

// component
public ngOnInit (): void {
    this.role = 'developer';
    this.showDeveloperStatus = true;
}

18) Les cordes doivent être en sécurité

Si vous avez une variable de type chaîne pouvant uniquement contenir un ensemble de valeurs, vous pouvez déclarer la liste des valeurs possibles au lieu de la déclarer en tant que type de chaîne.

Pourquoi?

En déclarant le type de variable de manière appropriée, nous pouvons éviter les bogues lors de l'écriture du code pendant la compilation plutôt que pendant l'exécution.

Avant

private myStringValue: string;

if (itShouldHaveFirstValue) {
   myStringValue = 'First';
} else {
   myStringValue = 'Second'
}

Après

private myStringValue: 'First' | 'Second';

if (itShouldHaveFirstValue) {
   myStringValue = 'First';
} else {
   myStringValue = 'Other'
}

// This will give the below error
Type '"Other"' is not assignable to type '"First" | "Second"'
(property) AppComponent.myValue: "First" | "Second"

Image plus grande

Gestion d'état

Pensez à utiliser @ ngrx / store pour conserver l'état de votre application et à @ ngrx / effects comme modèle des effets secondaires pour le magasin. Les changements d'état sont décrits par les actions et les changements sont effectués par des fonctions pures, appelées réducteurs.

Pourquoi?

@ ngrx / store isole toute la logique liée à l'état en un seul endroit et la rend cohérente dans toute l'application. Il a également mis en place un mécanisme de mémorisation lors de l’accès aux informations du magasin, ce qui conduit à une application plus performante. @ ngrx / store, associé à la stratégie de détection des modifications d’Anngular, permet une application plus rapide.

État immuable

Lorsque vous utilisez @ ngrx / store , envisagez d’utiliser ngrx-store-freeze pour rendre l’état immuable. ngrx-store-freeze empêche la mutation de l'état en lançant une exception. Cela évite une mutation accidentelle de l'état entraînant des conséquences indésirables.

Pourquoi?

La mutation de l'état des composants conduit à un comportement incohérent de l'application en fonction de l'ordre de chargement des composants. Cela brise le modèle mental du modèle de redux. Les modifications peuvent être annulées si l’état du magasin est modifié et réémis. Séparation des problèmes - les composants sont des couches de vue, ils ne doivent pas savoir comment changer d'état.

Plaisanter

Jest est le framework de tests unitaires de Facebook pour JavaScript. Il accélère les tests unitaires en parallélisant les tests sur la base de code. Avec son mode de surveillance, seuls les tests liés aux modifications apportées sont exécutés, ce qui raccourcit la boucle de rétroaction. Jest fournit également une couverture de code des tests et est pris en charge sur VS Code et Webstorm.

Vous pouvez utiliser un paramètre prédéfini pour Jest qui fera le gros du travail lourd lors de la configuration de Jest dans votre projet.

Karma

Karma est un coureur de test développé par l'équipe AngularJS. Il faut un vrai navigateur / DOM pour exécuter les tests. Il peut également fonctionner sur différents navigateurs. Jest n'a pas besoin de chrome headless / phantomjs pour exécuter les tests et s'exécute en pur nœud.

Universel

Si vous n'avez pas fait de votre application une application universelle , le moment est venu de le faire. Angular Universal vous permet d’exécuter votre application Angular sur le serveur et effectue le rendu côté serveur (SSR), qui sert des pages HTML statiques pré-rendues. Cela rend l'application super rapide car elle affiche le contenu à l'écran presque instantanément, sans avoir à attendre le chargement et l'analyse des bundles JS, ni à démarrer Angular.

Il est également convivial pour le référencement, car Angular Universal génère un contenu statique et facilite l’indexation de l’application par les robots d'exploration de Web et la rend consultable sans exécuter JavaScript.

Pourquoi?

Universal améliore considérablement les performances de votre application. Nous avons récemment mis à jour notre application pour effectuer le rendu côté serveur et le temps de chargement du site est passé de quelques secondes à des dizaines de millisecondes !!

Cela permet également à votre site de s'afficher correctement dans les extraits de prévisualisation des médias sociaux. La première peinture significative est très rapide et rend le contenu visible pour les utilisateurs sans aucun retard indésirable.

Conclusion

Construire des applications est un voyage constant et il y a toujours place à l'amélioration. Cette liste d’optimisations est un bon point de départ et l’application constante de ces modèles rendra votre équipe heureuse. Vos utilisateurs vous apprécieront également pour la bonne expérience de votre application moins boguée et performante.

About

Meilleures pratiques pour une application angulaire propre et performante

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published