angular-redux / store

Angular 2+ bindings for Redux
MIT License
1.34k stars 202 forks source link

When manually triggering dispatch, how do you get the view to update? #7

Closed meenie closed 9 years ago

meenie commented 9 years ago

I'm using Socket.io to get data pushed in from the server and I'm manually running store.dispatch(action(socketIODate)). I can see that it is going into the reducer and adding the data to the state. It's even calling the attached mapState method in my container. It just won't update the DOM. I'm guessing it's something I'm missing with Angular2's change detection, but I'm not quite sure what it is.

wbuchwalter commented 9 years ago

Hey Cody, Is the code visible somewhere? It's hard to tell like that, i would need to reproduce. Thanks!

meenie commented 9 years ago

Sure thing. If you take your example code, and add in Socket.io to trigger manual dispatches, it should be the same thing:

// index.ts
import {bootstrap} from 'angular2/angular2';
import {bind} from 'angular2/di';
import App from './containers/App';
import {increment} from './actions/CounterActions';
import configureStore from './store/configureStore';
import io from 'socket.io-client';

const provider = require('ng2-redux').provider;
const socket = io('http://localhost:3000');

const store = configureStore();
socket.on('increment-counter', function() {
    console.log('counter incremented');
    store.dispatch(increment());
});

bootstrap(
  App,
  [provider(store)]
  );

And then on your server side, do: (I'm not 100% on how to use webpack, but I think it already sets up a socket.io connection? If not, you'll get the gist of what I mean :).

// server.js
var webpack = require('webpack');
var WebpackDevServer = require('webpack-dev-server');
var config = require('./webpack.config');

new WebpackDevServer(webpack(config), {
  publicPath: config.output.publicPath,
  hot: true,
  historyApiFallback: true,
  stats: {
    colors: true
  }
})
.listen(3000, 'localhost', function (err) {
  if (err) { console.log(err); }
  this.io.on('connection', function(socket) {
    // Should increment the counter by 1 every second
    setInterval(function() {
      socket.emit('increment-counter');
    }, 1000);
  })
  console.log('Listening at localhost:3000');
});

Hopefully the above works for you :). I'm assuming by just manually running store.dispatch() it should propagate throughout the whole app. It seems to do 3/4 of that and leaves out updating the DOM.

meenie commented 9 years ago

Or if you want to skip Socket.io all together, just try something like this:

// index.ts
import {bootstrap} from 'angular2/angular2';
import {bind} from 'angular2/di';
import App from './containers/App';
import {increment} from './actions/CounterActions';
import configureStore from './store/configureStore';

const provider = require('ng2-redux').provider;

const store = configureStore();
// Increment every 1 second
setInterval(() => {
  store.dispatch(increment());
}, 1000);

bootstrap(
  App,
  [provider(store)]
  );
meenie commented 9 years ago

BTW, I'm using v2.0.0-alpha.2

wbuchwalter commented 9 years ago

:+1: Will try this later today! Thanks.

wbuchwalter commented 9 years ago

Hey, So after digging a bit, It seems it is an issue related to zone.js.

in App.ts, If you do:

setInterval(() => {
  ngRedux.dispatch(increment());
}, 1000);

you will see that the bug goes away.

I know next to nothing about zone currently, but my limited understanding is that your ng2 components run into an execution context of zone.js, which allows it to know when a setTimeout (or related) call is finished, and thus check for changes in your bindings.

The problem with your code is that your setInterval is done outside the context of angular, so angular has no way to know when something might have changed.

in ng-redux for angular1, this issue is fixed by a digest middleware, but I haven't yet dug deep enough into angular2 to know how to handle that here (and if it should be handled at all).

I will leave this issue open while I do some research on the subject.

In the mean time you could subscribe to socket events in the App container. While it's not ideal code practice, it should unblock you at least.

meenie commented 9 years ago

Oh wow, you are so right! That's where I'm bootstrapping angular. Not actually in the context of angular. Thanks for figuring that out! I'll just make it a service and inject it in and initialise it in the root component. Should have done it like that in the first place. Better coding practice ☺.

meenie commented 9 years ago

I actually didn't know that ngRedux was the store and you can dispatch from it. Makes sense now. I'm just new to Redux.

wbuchwalter commented 9 years ago

Yea I was also going to suggest doing it in a bind.toFactory, and handle the lifecyle of socket.io in the App container. The more I think about it, the more it seems logical to do it this way, so if you are comfortable with that I will just close this issue, since there is not a lot I can do about it in ngRedux without being hacky.

meenie commented 9 years ago

Ya, totally! I'll close it now :). Thanks for the ideas!

wbuchwalter commented 9 years ago

:+1: