1072 votes

Angular/RxJS Quand dois-je me désabonner de `Subscription` ?

Quand dois-je stocker le Subscription et invoquer unsubscribe() pendant le ngOnDestroy cycle de vie et quand puis-je simplement les ignorer ?

La sauvegarde de tous les abonnements introduit beaucoup de désordre dans le code des composants.

Guide du client HTTP ignorez les abonnements de ce type :

getHeroes() {
  this.heroService.getHeroes()
                  .subscribe(
                     heroes => this.heroes = heroes,
                     error =>  this.errorMessage = <any>error);
}

Dans le même temps Guide d'itinéraire et de navigation dit cela :

Finalement, nous naviguerons ailleurs. Le routeur va retirer ce composant du DOM et le détruire. Nous devons nettoyer après nous-mêmes avant que cela ne se produise. Plus précisément, nous devons nous désinscrire avant qu'Angular ne détruise le composant. Si nous ne le faisons pas, cela pourrait créer une fuite de mémoire.

Nous nous désinscrivons de notre Observable dans le ngOnDestroy méthode.

private sub: any;

ngOnInit() {
  this.sub = this.route.params.subscribe(params => {
     let id = +params['id']; // (+) converts string 'id' to a number
     this.service.getHero(id).then(hero => this.hero = hero);
   });
}

ngOnDestroy() {
  this.sub.unsubscribe();
}

33 votes

Je suppose Subscription s à http-requests peuvent être ignorés, car ils n'appellent que onNext une fois et ensuite ils appellent onComplete . Le site Router appelle plutôt onNext à plusieurs reprises et pourrait ne jamais appeler onComplete (je n'en suis pas sûr...). Il en va de même pour Observable s de Event s. Donc je suppose qu'ils devraient être unsubscribed .

1 votes

@gt6707a Le flux se termine (ou ne se termine pas) indépendamment de toute observation de cet achèvement. Les rappels (l'observateur) fournis à la fonction d'abonnement ne déterminent pas si les ressources sont allouées. C'est l'appel à subscribe lui-même qui alloue potentiellement des ressources en amont.

1 votes

Faites-en un muscle memory de vous désabonner explicitement dans votre typescript . Même le http abonnements. Ex : A Http.get() complète la réponse. Si l'api de votre serveur prend 10 seconds pour répondre, et votre composant est détruit dans 5 seconds de l'appel, votre réponse arrivera 5 seconds after la destruction du composant. Cela déclenchera une exécution hors-contexte, ce qui est bien pire que la partie fuite de mémoire, indiquée dans les docs d'Angular.

1373voto

seangwright Points 9294

--- Edit 4 - Ressources supplémentaires (2018/09/01)

Dans un récent épisode de Aventures en Angular Ben Lesh et Ward Bell discutent des questions relatives à la manière et au moment de se désabonner dans un composant. La discussion commence à environ 1:05:30.

Ward mentionne right now there's an awful takeUntil dance that takes a lot of machinery et Shai Reznik mentionne Angular handles some of the subscriptions like http and routing .

En réponse, Ben mentionne qu'il y a actuellement des discussions pour permettre aux Observables de s'accrocher aux événements du cycle de vie des composants Angular et Ward suggère un Observable des événements du cycle de vie auquel un composant pourrait s'abonner comme moyen de savoir quand compléter les Observables maintenus comme état interne du composant.

Cela dit, nous avons surtout besoin de solutions maintenant, alors voici d'autres ressources.

  1. Une recommandation pour le takeUntil() de Nicholas Jamieson, membre de l'équipe centrale de RxJs, et une règle tslint pour aider à la faire respecter. https://ncjamieson.com/avoiding-takeuntil-leaks/

  2. Paquet npm léger qui expose un opérateur Observable qui prend une instance de composant ( this ) en tant que paramètre et se désinscrit automatiquement pendant ngOnDestroy . https://github.com/NetanelBasal/ngx-take-until-destroy

  3. Une autre variation de la précédente avec une ergonomie légèrement meilleure si vous ne faites pas de constructions AOT (mais nous devrions tous faire des AOT maintenant). https://github.com/smnbbrv/ngx-rx-collector

  4. Directive personnalisée *ngSubscribe qui fonctionne comme async pipe mais crée une vue intégrée dans votre modèle afin que vous puissiez vous référer à la valeur "unwrapped" dans votre modèle. https://netbasal.com/diy-subscription-handling-directive-in-angular-c8f6e762697f

Je mentionne dans un commentaire sur le blog de Nicholas que la surutilisation des takeUntil() pourrait être un signe que votre composant essaie d'en faire trop et que la séparation de vos composants existants en Fonctionnalité y Présentation doivent être pris en compte. Vous pouvez alors | async l'Observable du composant Feature dans un Input du composant "Presentational", ce qui signifie qu'aucun abonnement n'est nécessaire nulle part. En savoir plus sur cette approche aquí

--- Edit 3 - La solution "officielle" (2017/04/09)

J'ai discuté de cette question avec Ward Bell à la NGConf (je lui ai même montré cette réponse qui, selon lui, était correcte) mais il m'a dit que l'équipe chargée de la documentation d'Angular avait une solution à cette question qui n'est pas publiée (bien qu'elle travaille à la faire approuver). Il m'a également dit que je pouvais mettre à jour ma réponse SO avec la recommandation officielle à venir.

La solution que nous devrions tous utiliser à l'avenir est d'ajouter une fonction private ngUnsubscribe = new Subject(); à tous les composants qui ont .subscribe() appels à Observable dans leur code de classe.

Nous appelons alors this.ngUnsubscribe.next(); this.ngUnsubscribe.complete(); dans notre ngOnDestroy() méthodes.

La sauce secrète (comme déjà noté par @metamaker ) est d'appeler takeUntil(this.ngUnsubscribe) avant chacun de nos .subscribe() qui garantiront que tous les abonnements seront nettoyés lorsque le composant sera détruit.

Exemple :

import { Component, OnDestroy, OnInit } from '@angular/core';
// RxJs 6.x+ import paths
import { filter, startWith, takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { BookService } from '../books.service';

@Component({
    selector: 'app-books',
    templateUrl: './books.component.html'
})
export class BooksComponent implements OnDestroy, OnInit {
    private ngUnsubscribe = new Subject();

    constructor(private booksService: BookService) { }

    ngOnInit() {
        this.booksService.getBooks()
            .pipe(
               startWith([]),
               filter(books => books.length > 0),
               takeUntil(this.ngUnsubscribe)
            )
            .subscribe(books => console.log(books));

        this.booksService.getArchivedBooks()
            .pipe(takeUntil(this.ngUnsubscribe))
            .subscribe(archivedBooks => console.log(archivedBooks));
    }

    ngOnDestroy() {
        this.ngUnsubscribe.next();
        this.ngUnsubscribe.complete();
    }
}

Nota: Il est important d'ajouter le takeUntil comme le dernier opérateur pour éviter les fuites avec les observables intermédiaires dans la chaîne d'opérateurs.

--- Edit 2 (2016/12/28)

Source 5

Dans le tutoriel Angular, le chapitre sur le routage indique désormais ce qui suit : " Le routeur gère les observables qu'il fournit et localise les abonnements. Les abonnements sont nettoyés lorsque le composant est détruit, ce qui protège contre les fuites de mémoire, de sorte que nous n'avons pas besoin de nous désabonner de l'Observable des paramètres de route." - Mark Rajcok

Voici un discussion sur les questions Github pour les documents Angular concernant Router Observables où Ward Bell mentionne que la clarification de tout cela est en cours.

--- Edit 1

Source 4

Dans ce vidéo de NgEurope Rob Wormald affirme également qu'il n'est pas nécessaire de se désabonner de Router Observables. Il mentionne également le http service et ActivatedRoute.params dans ce vidéo de novembre 2016 .

--- Réponse originale

TLDR :

Pour cette question, il y a (2) sortes de Observables - finie valeur et infini valeur.

http Observables produire finie (1) des valeurs et quelque chose comme un DOM event listener Observables produire infini valeurs.

Si vous appelez manuellement subscribe (sans utiliser de pipe asynchrone), alors unsubscribe de infini Observables .

Ne vous inquiétez pas pour finie les uns et les autres, RxJs s'occupera d'eux.

Fuente 1

J'ai trouvé une réponse de Rob Wormald dans le Gitter d'Angular. aquí .

Il déclare (j'ai réorganisé pour la clarté et l'emphase est la mienne)

si son une séquence à valeur unique (comme une requête http) le site le nettoyage manuel est inutile (en supposant que vous vous inscrivez manuellement dans le contrôleur)

Je devrais dire "si c'est un séquence qui complète " (dont les séquences à valeur unique, à la http, sont une)

si c'est une séquence infinie , vous devez vous désinscrire ce que le tuyau asynchrone fait pour vous

Il mentionne également dans ce vidéo youtube sur les observables qui they clean up after themselves ... dans le contexte des Observables qui complete (comme les promesses, qui sont toujours complètes parce qu'elles produisent toujours une valeur et se terminent - nous ne nous sommes jamais inquiétés de nous désabonner des promesses pour nous assurer qu'elles se nettoient. xhr écouteurs d'événements, n'est-ce pas ?).

Fuente 2

Également dans le Guide de Rangle sur Angular 2 On peut y lire

Dans la plupart des cas, nous n'aurons pas besoin d'appeler explicitement la méthode de désabonnement, à moins que nous ne voulions résilier prématurément ou que notre Observable ait une durée de vie supérieure à celle de notre abonnement. Le comportement par défaut des opérateurs d'Observable est de se débarrasser de l'abonnement dès que les messages .complete() ou .error() sont publiés. Gardez à l'esprit que RxJS a été conçu pour être utilisé la plupart du temps selon le principe du "feu et oublie".

Quand est-ce que la phrase our Observable has a longer lifespan than our subscription appliquer ?

Il s'applique lorsqu'un abonnement est créé à l'intérieur d'un composant qui est détruit avant (ou pas "longtemps" avant) la date limite d'utilisation de l'abonnement. Observable complète.

J'interprète cela comme signifiant que si nous souscrivons à un http ou un observable qui émet 10 valeurs et que notre composant est détruit avant cela. http revient ou les 10 valeurs ont été émises, nous sommes toujours ok !

Lorsque la requête est retournée ou que la 10e valeur est finalement émise, l'option Observable se terminera et toutes les ressources seront nettoyées.

Source 3

Si nous regardons cet exemple à partir du même guide Rangle, nous pouvons voir que le Subscription a route.params nécessite un unsubscribe() parce que nous ne savons pas quand ces params cessera de changer (en émettant de nouvelles valeurs).

Le composant pourrait être détruit en quittant la navigation, auquel cas les paramètres de la route continueront probablement à changer (ils pourraient techniquement changer jusqu'à ce que l'application se termine) et les ressources allouées dans la souscription seront toujours allouées parce qu'il n'y a pas eu de completion .

0 votes

Lorsqu'on s'éloigne d'un itinéraire, le routeur enfant de cet itinéraire est détruit. Je suppose que c'est la raison pour laquelle il n'est pas nécessaire de se désabonner des événements de routeur.

0 votes

Qu'en est-il des sujets locaux, qui sont créés par un composant (par exemple pour la logique/le câblage interne au composant) : Est-ce que complete() être appelé sur ces sujets en ngOnDestroy ? Cela nettoierait les abonnements et chaque abonnement aurait la possibilité de nettoyer ce qu'il a utilisé, dans son gestionnaire complet, n'est-ce pas ?

0 votes

@Lars Je crois que les sujets locaux sont nettoyés automatiquement puisqu'ils sont créés dans la portée du composant parent mais l'équipe Angular va recommander l'approche que j'ai détaillée ci-dessus dans "Edit 3".

153voto

metamaker Points 1157

Vous n'avez pas besoin d'avoir un tas d'abonnements et de vous désabonner manuellement. Utilisez Sujet y prendre jusqu'à combo pour gérer les abonnements comme un patron :

import { Subject } from "rxjs"
import { takeUntil } from "rxjs/operators"

@Component({
  moduleId: __moduleName,
  selector: "my-view",
  templateUrl: "../views/view-route.view.html"
})
export class ViewRouteComponent implements OnInit, OnDestroy {
  componentDestroyed$: Subject<boolean> = new Subject()

  constructor(private titleService: TitleService) {}

  ngOnInit() {
    this.titleService.emitter1$
      .pipe(takeUntil(this.componentDestroyed$))
      .subscribe((data: any) => { /* ... do something 1 */ })

    this.titleService.emitter2$
      .pipe(takeUntil(this.componentDestroyed$))
      .subscribe((data: any) => { /* ... do something 2 */ })

    //...

    this.titleService.emitterN$
      .pipe(takeUntil(this.componentDestroyed$))
      .subscribe((data: any) => { /* ... do something N */ })
  }

  ngOnDestroy() {
    this.componentDestroyed$.next(true)
    this.componentDestroyed$.complete()
  }
}

Approche alternative qui a été proposé par @acumartini dans les commentaires , utilise takeWhile au lieu de prendre jusqu'à . Vous pouvez préférer cette méthode, mais gardez à l'esprit que de cette façon, l'exécution de votre Observable ne sera pas annulée lors du ngDestroy de votre composant (par exemple, lorsque vous effectuez des calculs fastidieux ou attendez des données du serveur). La méthode, qui est basée sur prendre jusqu'à ne présente pas cet inconvénient et entraîne l'annulation immédiate de la demande. Merci à @AlexChe pour l'explication détaillée dans les commentaires .

Voici donc le code :

@Component({
  moduleId: __moduleName,
  selector: "my-view",
  templateUrl: "../views/view-route.view.html"
})
export class ViewRouteComponent implements OnInit, OnDestroy {
  alive: boolean = true

  constructor(private titleService: TitleService) {}

  ngOnInit() {
    this.titleService.emitter1$
      .pipe(takeWhile(() => this.alive))
      .subscribe((data: any) => { /* ... do something 1 */ })

    this.titleService.emitter2$
      .pipe(takeWhile(() => this.alive))
      .subscribe((data: any) => { /* ... do something 2 */ })

    // ...

    this.titleService.emitterN$
      .pipe(takeWhile(() => this.alive))
      .subscribe((data: any) => { /* ... do something N */ })
  }

  ngOnDestroy() {
    this.alive = false
  }
}

0 votes

Pourquoi utiliser un sujet juste pour garder un bool, alors que vous pouvez utiliser un bool ?

3 votes

S'il utilise simplement un bool pour conserver l'état, comment faire pour que "takeUntil" fonctionne comme prévu ?

0 votes

@Val, vous avez tout à fait raison. @Royi, vérifiez takeUntil documentation pour voir l'explication de son fonctionnement xgrommx.github.io/rx-book/contenu/observable/ . Comme paramètre, il n'accepte que Observable | Promise.

138voto

Steven Liekens Points 2327

La classe Abonnement présente une caractéristique intéressante :

Représente une ressource jetable, telle que l'exécution d'un Observable. Un abonnement possède une méthode importante, unsubscribe, qui ne prend aucun argument et se contente de disposer de la ressource détenue par l'abonnement.
En outre, les abonnements peuvent être regroupés grâce à la méthode add(), qui permet de rattacher un abonnement enfant à l'abonnement actuel. Lorsqu'un abonnement est désabonné, tous ses enfants (et ses petits-enfants) sont également désabonnés.

Vous pouvez créer un objet Abonnement agrégé qui regroupe tous vos abonnements. Pour ce faire, vous devez créer un abonnement vide et y ajouter des abonnements à l'aide de son objet add() méthode. Lorsque votre composant est détruit, il vous suffit de désabonner l'abonnement à l'agrégat.

@Component({ ... })
export class SmartComponent implements OnInit, OnDestroy {
  private subscriptions = new Subscription();

  constructor(private heroService: HeroService) {
  }

  ngOnInit() {
    this.subscriptions.add(this.heroService.getHeroes().subscribe(heroes => this.heroes = heroes));
    this.subscriptions.add(/* another subscription */);
    this.subscriptions.add(/* and another subscription */);
    this.subscriptions.add(/* and so on */);
  }

  ngOnDestroy() {
    this.subscriptions.unsubscribe();
  }
}

3 votes

J'utilise cette approche. Je me demande si c'est mieux que d'utiliser l'approche avec takeUntil(), comme dans la réponse acceptée des inconvénients ?

1 votes

Aucun inconvénient à ma connaissance. Je ne pense pas que ce soit mieux, juste différent.

6 votes

Voir medium.com/@benlesh/rxjs-dont-unsubscribe-6753ed4fda87 pour une discussion plus approfondie sur le site officiel takeUntil par rapport à cette approche qui consiste à collecter les inscriptions et à appeler unsubscribe . (Cette approche me semble beaucoup plus propre).

52voto

Mouneer Points 4273

Quelques-unes des meilleures pratiques concernant la désinscription des observables dans les composants Angular :

Une citation de Routing & Navigation

Lorsque vous vous abonnez à un observable dans un composant, vous vous arrangez presque toujours pour vous désabonner lorsque le composant est détruit.

Il y a quelques observables exceptionnels où cela n'est pas nécessaire. Les observables ActivatedRoute font partie de ces exceptions.

L'ActivatedRoute et ses observables sont isolés du Router lui-même. Le routeur détruit un composant routé lorsqu'il n'est plus nécessaire et l'ActivatedRoute injectée meurt avec lui.

N'hésitez pas à vous désabonner de toute façon. C'est inoffensif et ce n'est jamais une mauvaise pratique.

Et en répondant aux liens suivants :

J'ai rassemblé certaines des meilleures pratiques concernant la désinscription des observables dans les composants Angular pour les partager avec vous :

  • http La désinscription observable est conditionnelle et nous devons examiner au cas par cas les effets de l'exécution de la "callback de souscription" après la destruction du composant. Nous savons qu'Angular désabonne et nettoie le composant http observable en soi (1) , (2) . Bien que cela soit vrai du point de vue des ressources, cela ne représente que la moitié de l'histoire. Disons que nous parlons d'appeler directement http à partir d'un composant, et le http La réponse a pris plus de temps que nécessaire et l'utilisateur a donc fermé le composant. Le site subscribe() sera toujours appelé même si le composant est fermé et détruit. Cela peut avoir des effets secondaires indésirables et, dans le pire des cas, briser l'état de l'application. Cela peut également provoquer des exceptions si le code du callback tente d'appeler quelque chose qui vient d'être détruit. Cependant, de temps en temps, ils sont souhaités. Par exemple, disons que vous créez un client de messagerie et que vous déclenchez un son lorsque l'envoi de l'e-mail est terminé - vous voudriez que cela se produise même si le composant est fermé ( 8 ).
  • Il n'est pas nécessaire de se désabonner des observables qui se terminent ou se trompent. Cependant, il n'y a pas de mal à le faire (7) .
  • Utilice AsyncPipe autant que possible, car il se désinscrit automatiquement de l'observable lors de la destruction du composant.
  • Se désabonner de la ActivatedRoute des observables comme route.params s'ils sont inscrits à l'intérieur d'un composant imbriqué (ajouté à l'intérieur d'un tpl avec le sélecteur de composant) ou dynamique, car ils peuvent être inscrits plusieurs fois tant que le composant parent/hôte existe. Il n'est pas nécessaire de les désabonner dans d'autres scénarios, comme mentionné dans la citation ci-dessus de Routing & Navigation docs.
  • Désabonnez-vous des observables globaux partagés entre les composants qui sont exposés par le biais d'un service Angular par exemple, car ils peuvent être souscrits plusieurs fois tant que le composant est initialisé.
  • Il n'est pas nécessaire de se désabonner des observables internes d'un service d'application puisque ce service n'est jamais détruit, à moins que toute votre application ne soit détruite, il n'y a aucune raison réelle de se désabonner et il n'y a aucun risque de fuite de mémoire. (6) .

    Nota: En ce qui concerne les services à portée étendue, c'est-à-dire les fournisseurs de composants, ils sont détruits lorsque le composant est détruit. Dans ce cas, si nous nous abonnons à un observable à l'intérieur de ce fournisseur, nous devrions envisager de nous en désabonner à l'aide de la commande OnDestroy qui sera appelé lorsque le service sera détruit, selon la documentation.

  • Utilisez une technique abstraite pour éviter tout désordre dans le code qui pourrait résulter des désinscriptions. Vous pouvez gérer vos abonnements avec takeUntil (3) ou vous pouvez utiliser ceci npm paquet mentionné à (4) Le moyen le plus simple de se désabonner des Observables dans Angular .
  • Toujours se désabonner de FormGroup des observables comme form.valueChanges y form.statusChanges
  • Toujours se désabonner des observables de Renderer2 service comme renderer2.listen
  • Se désabonner de chaque observable else comme mesure de protection contre les fuites de mémoire jusqu'à ce que les documents d'Angular nous indiquent explicitement les observables qu'il n'est pas nécessaire de désabonner (vérifier le problème) : (5) Documentation pour le désabonnement à RxJS (Ouvert) ).
  • Bonus : Utilisez toujours les méthodes d'Angular pour lier des événements comme HostListener car angular prend soin de supprimer les écouteurs d'événements si nécessaire et empêche toute fuite de mémoire potentielle due aux liaisons d'événements.

Un bon conseil final : Si vous ne savez pas si une observable est automatiquement désabonnée/complétée ou non, ajoutez une balise complete à l'appel subscribe(...) et vérifier si elle est appelée lorsque le composant est détruit.

0 votes

La réponse au numéro 6 n'est pas tout à fait juste. Les services sont détruits et leurs ngOnDestroy est appelé lorsque le service est fourni à un niveau autre que le niveau racine, par exemple lorsqu'il est fourni explicitement dans un composant qui est ensuite supprimé. Dans ce cas, vous devez vous désabonner des observables internes du service.

0 votes

@Drenai, merci pour votre commentaire et poliment je ne suis pas d'accord. Si un composant est détruit, le composant, le service et l'observable seront tous GCed et la désinscription sera inutile dans ce cas à moins que vous gardiez une référence pour l'observable n'importe où loin du composant (ce qui n'est pas logique pour faire fuir les états du composant globalement malgré le scoping du service au composant).

0 votes

Si le service en cours de destruction a un abonnement à un observable appartenant à un autre service situé plus haut dans la hiérarchie de l'ID, le GC ne se produira pas. Évitez ce scénario en vous désabonnant dans ngOnDestroy qui est toujours appelé lorsque les services sont détruits github.com/angular/angular/commit/

19voto

evenstar Points 572

Cela dépend. Si en appelant someObservable.subscribe() , vous commencez à retenir une ressource qui doit être libérée manuellement lorsque le cycle de vie de votre composant est terminé, alors vous devez appeler theSubscription.unsubscribe() pour éviter les fuites de mémoire.

Examinons de plus près vos exemples :

getHero() renvoie le résultat de http.get() . Si vous regardez dans l'angulaire 2 code source , http.get() crée deux écouteurs d'événements :

_xhr.addEventListener('load', onLoad);
_xhr.addEventListener('error', onError);

et en appelant unsubscribe() vous pouvez annuler la demande ainsi que les récepteurs :

_xhr.removeEventListener('load', onLoad);
_xhr.removeEventListener('error', onError);
_xhr.abort();

Notez que _xhr est spécifique à la plate-forme, mais je pense qu'on peut supposer qu'il s'agit d'une XMLHttpRequest() dans votre cas.

Normalement, c'est une preuve suffisante pour justifier une enquête manuelle. unsubscribe() appel. Mais selon cette Spécification du WHATWG El XMLHttpRequest() est soumis à la collecte des déchets une fois qu'il est "terminé", même si des écouteurs d'événements lui sont attachés. Je suppose donc que c'est la raison pour laquelle le guide officiel d'Angular 2 ne mentionne pas les éléments suivants unsubscribe() et laisse le GC nettoyer les auditeurs.

Quant à votre deuxième exemple, il dépend de l'implémentation de la fonction params . A partir d'aujourd'hui, le guide officiel angular ne montre plus la désinscription de params . J'ai regardé dans src encore une fois et a trouvé que params est un juste un ComportementSujet . Puisqu'aucun écouteur d'événements ou temporisateur n'a été utilisé et qu'aucune variable globale n'a été créée, il devrait être possible d'omettre l'option unsubscribe() .

La réponse à votre question est qu'il faut toujours appeler unsubscribe() comme protection contre les fuites de mémoire, sauf si vous êtes certain que l'exécution de l'observable ne crée pas de variables globales, n'ajoute pas d'écouteurs d'événements, ne définit pas de temporisateurs ou ne fait rien d'autre qui entraîne des fuites de mémoire.

En cas de doute, examinez l'implémentation de cet observable. Si l'observable a écrit une logique de nettoyage dans sa fonction unsubscribe() qui est généralement la fonction renvoyée par le constructeur, vous avez de bonnes raisons d'envisager sérieusement d'appeler la fonction unsubscribe() .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X