web-dev-qa-db-fra.com

La vue angulaire2 ne change pas après la mise à jour des données

J'essaie de mettre à jour ma vue après qu'un événement websocket renvoie des données mises à jour.

J'ai injecté un service dans mon application et appelé la méthode getData () sur le service. Cette méthode émet un événement socket.io sur mon serveur NodeJS qui, à son tour, effectue un appel externe à l'API et analyse certaines données. Le serveur NodeJS émet ensuite un événement de réussite avec les nouvelles données que j'écoute dans mon service. Lorsque l'événement de réussite est renvoyé, je mets à jour ma propriété sur le service référencé à mon avis.

Cependant, peu importe ce que j'essaie, je ne peux pas afficher les données une fois la propriété mise à jour.

Je cherche depuis quelques jours et tout ce que je trouve, ce sont des billets de blog qui disent que ce changement doit être transparent, ou que je dois intégrer zone.js d'une manière ou d'une autre, ou essayer la même logique en utilisant des formulaires (même si j'essaie de le faire sans interaction de l'utilisateur). Rien ne semble fonctionner pour moi et je suis un peu frustré.

Par exemple: 

Disons que je reçois un tableau de chaînes avec lequel je veux créer une liste non triée.

app.ts

import {Component, View, bootstrap, NgFor} from 'angular2/angular2';
import {MyService} from 'js/services/MyService';

// Annotation section
@Component({
    selector: 'my-app',
    viewInjector: [MyService]
})
@View({
    templateUrl: 'templates/my-app.tpl.html',
    directives: [NgFor]
})

class MyComponent {
    mySvc:MyService;

    constructor(mySvc:MyService) {
        this.mySvc = mySvc;
        this.mySvc.getData();
    }
}   

bootstrap(MyComponent, [MyService]);

MyService.ts

let socket = io();
export class MyService {
    someList:Array<string>;

    constructor() {
        this.initListeners();
    }

    getData() {
        socket.emit('myevent', {value: 'someValue'});
    }

    initListeners() {
        socket.on('success', (data) => {
            self.someList = data;
        });
    }
 }

my-app.tpl.html

<div>
    <h2>My List</h2>
    <ul>
        <li *ng-for="#item of mySvc.myList">Item: {{item}}</li>
    </ul>
</div>

Assez intéressant, j’ai trouvé que, si j’incorpore dans mon composant un délai qui met à jour une propriété arbitraire que j’ai définie dans la vue après la mise à jour de la propriété someList à partir du rappel de réussite, les deux valeurs de propriété sont mises à jour correctement en même temps.

Par exemple:

nouvelles applications

    import {Component, View, bootstrap, NgFor} from 'angular2/angular2';
    import {MyService} from 'js/services/MyService';

    // Annotation section
    @Component({
        selector: 'my-app',
        viewInjector: [MyService]
    })
    @View({
        templateUrl: 'templates/my-app.tpl.html',
        directives: [NgFor]
    })

    class MyComponent {
        mySvc:MyService;
        num:Number;

        constructor(mySvc:MyService) {
            this.mySvc = mySvc;
            this.mySvc.getData();
            setTimeout(() => this.updateNum(), 5000);
        }

        updateNum() {
            this.num = 123456;
        }
    }   

    bootstrap(MyComponent, [MyService]);

nouveau my-app.tpl.html

<div>
    <h2>My List {{num}}</h2>
    <ul>
        <li *ng-for="#item of mySvc.myList">Item: {{item}}</li>
    </ul>
</div>

Alors, comment devrais-je obtenir angular2 pour reconnaître que les données ont changé après l'événement 'success' sans mettre à jour une autre propriété?

Y a-t-il quelque chose qui me manque avec l'utilisation de la directive NgFor?

29
John Gainfort

J'ai donc finalement trouvé une solution que j'aime bien. Suite à la réponse dans cet article Comment mettre à jour la vue après modification dans angular2 après le lancement de l’écouteur d’événements Google j’ai mis à jour myList dans zone.run () et maintenant mes données sont mises à jour dans ma vue comme prévu.

MyService.ts

/// <reference path="../../../typings/tsd.d.ts" />

// Import
import {NgZone} from 'angular2/angular2';
import {SocketService} from 'js/services/SocketService';

export class MyService {
    zone:NgZone;
    myList:Array<string> = [];
    socketSvc:SocketService;

    constructor() {
        this.zone = new NgZone({enableLongStackTrace: false});
        this.socketSvc = new SocketService();
        this.initListeners();
    }

    getData() {
        this.socketSvc.emit('event');
    }

    initListeners() {
        this.socketSvc.socket.on('success', (data) => {
            this.zone.run(() => {
                this.myList = data;
                console.log('Updated List: ', this.myList);
            });
        });
    }
 }
35
John Gainfort

Il suffit de déplacer votre initialisation socket.io vers le constructeur de service et cela fonctionnera.
Regardez cet exemple:

import {Injectable} from 'angular2/core';  
@Injectable()
export class SocketService {
    socket:SocketIOClient.Socket;

    constructor(){
        this.socket = io.connect("localhost:8000");
    }
    public getSocket():SocketIOClient.Socket{
        return this.socket;
    }
}

Désormais, chaque fois que vous injectez ce service dans un composant et utilisez un socket, votre vue sera automatiquement mise à jour. Mais si vous le laissez dans une portée globale comme vous l'avez fait, vous devrez interagir avec quelque chose afin de forcer l'affichage à se mettre à jour.
Voici un exemple de composant qui utilise ce service: 

export class PostsComponent {
    socket: SocketIOClient.Socket;
    posts: Array<Post> = [];

    constructor(private _socketService:SocketService){
        this.socket.on('new-post', data => {
            this.posts.Push(new Post(data.id, data.text));
        });  
}  
5
Master Kwoth

Un moyen très simple de faire cela consiste simplement à exécuter dans la zone quelle que soit la variable que vous souhaitez mettre à jour.

zone.run(()=>{
    this.variable = this.variable;
});

Cela ne semble pas si simple, mais le simple fait de l'affecter à lui-même le mettra à jour s'il est exécuté dans une zone. Je ne sais pas si cela pose toujours un problème dans angular2, car j'utilise une version un peu plus ancienne.

4

METTRE &AGRAVE; JOUR

Le plunker que j'ai lié fournit un exemple de base, mais il est frustrant de ne pas pouvoir montrer un "exemple de travail" complet. J'ai donc créé un github repo que vous pouvez télécharger pour voir un exemple complet des éléments dont j'ai parlé ci-dessous.


Donc, dans votre question, il y avait deux problèmes. Vous avez eu une faute de frappe dans le code d'origine dans le fichier "MyService.ts"

self.someList = data;//should be this.someList, self is the browser window object

Angular2 ne reconnaît pas non plus la détection des modifications comme vous l’attendiez. Si elle avait été définie sur 'this', je ne pense toujours pas que cela aurait mis à jour votre vue des composants.

Dans votre réponse, cela fonctionne, mais vous contournez le problème dans le mauvais sens. Ce que vous devez implémenter est un Observable dans votre service.

Lorsque vous combinez ces deux fonctionnalités, vous pouvez implémenter les voiles de manière assez simple. J'ai créé un exemple plunker mais gardez à l'esprit qu'il ne se connecte pas réellement à un serveur de voiles car Plunker nécessite https et je ne vais pas acheter un certificat SSL pour ma machine locale. Le code reflète bien la manière dont vous devez implémenter la communication avec les voiles dans Angular2.

L'idée de base se trouve dans le fichier src/io.service.ts

constructor() {
  this._ioMessage$ = <Subject<{}>>new Subject();
  //self is the window object in the browser, the 'io' object is actually on global scope
  self.io.sails.connect('https://localhost:1337');//This fails as no sails server is listening and plunker requires https
  this.listenForIOSubmission();
}

get ioMessage$(){
  return this._ioMessage$.asObservable();
}

private listenForIOSubmission():void{
  if(self.io.socket){//since the connect method failed in the constructor the socket object doesn't exist
    //if there is a need to call emit or any other steps to prep Sails on node.js, do it here.
    self.io.socket.on('success', (data) => {//guessing 'success' would be the eventIdentity
      //note - you data object coming back from node.js, won't look like what I am using in this example, you should adjust your code to reflect that.
      this._ioMessage$.next(data);//now IO is setup to submit data to the subscribbables of the observer
    });
  }
}
1
Dan Simon

J'avais le même problème, et le problème était:

J'utilisais "angular2": "2.0.0-beta.1" Il semble y avoir un bogue, car après la mise à jour en. "angular2": "2.0.0-beta.15" Cela fonctionne bien.

J'espère que ça aide, je l'ai appris de manière pénible

0
Alan Wagner

Si cela vous intéresse, puis-je vous suggérer d'utiliser ngrx/store avec la détection de changement OnPush. J'ai rencontré des problèmes similaires où quelque chose s'est passé en dehors de Angular (quoi que cela signifie exactement) et mon point de vue ne reflétait pas le changement.

L'utilisation du modèle Redux avec des répartiteurs d'événements et un seul état qui contient mes données conjointement avec la détection de changement OnPush a résolu ce problème pour moi. Je ne sais pas pourquoi ni comment cela résout ce problème. Cum grano salis.

Voir ce commentaire spécifiquement pour plus de détails.

0
weltschmerz