I'm using indexeddb in an Angular 8 service and need window. The code builds without errors and the app creates the db objectstore flawlessly. But at runtime in production mode (with an actual node server instead of ng serve where this error does not occur), I get this error in the terminal running angular:
ERROR ReferenceError: window is not defined
at IndexedDBService.isSupported (D:\MartijnFiles\Documents\Programming\Fenego\fenego-labs-angular\dist\server.js:71199:9)
at IndexedDBService.openDB (D:\MartijnFiles\Documents\Programming\Fenego\fenego-labs-angular\dist\server.js:71203:18)
at Promise (D:\MartijnFiles\Documents\Programming\Fenego\fenego-labs-angular\dist\server.js:72026:46)
Again, it all works and the isSupported() function would stop openDB() from being run if window was actually undefined. There is also no error in the browser console.
Here is the relevant part of my service.
#Injectable()
export class IndexedDBService {
isSupported(): boolean {
return !!window.indexedDB;
}
openDB(dbName: string,
version: number,
onUpgradeNeededCallback: OnUpgradeNeededCallback,
onSuccessCallback: OnOpenSuccessCallback,
onErrorCallback: OnOpenErrorCallback,
onBlockedCallback: OnOpenBlockedCallback): Observable<IDBOpenDBRequest> {
let openDBRequest: IDBOpenDBRequest = null;
if (this.isSupported()) {
openDBRequest = window.indexedDB.open(dbName, version);
openDBRequest.onupgradeneeded = onUpgradeNeededCallback;
openDBRequest.onsuccess = onSuccessCallback;
openDBRequest.onerror = onErrorCallback;
openDBRequest.onblocked = onBlockedCallback;
}
return of(openDBRequest);
}
There are many suggest "solutions" out there that mostly boil down to providing it via a service or plain injection (eg. point 1 in this blog https://willtaylor.blog/angular-universal-gotchas/) but all it does is pass window from some other service via injection to mine. But my code works so it clearly has access to window...
Update:
The following line in a component's ngOnInit() has the same problem with Worker being "not defined" yet the worker is loaded and runs perfectly:
const offlineProductsWorker = new Worker('webworkers/offline-products-worker.js');
Update2:
I have found a solution (posted below) but checking for server side rendering seems more like a workaround than solving the fact that server side rendering is happening (not sure if that is supposed to be the case).
I will include my server.ts script that I use with webpack below. It is a modification of one from another project and I don't understand most of it. If anyone can point out to me what I could change to stop the server side rendering, that would be great. Or, if it is supposed to do that then why?
// tslint:disable:ish-ordered-imports no-console
import 'reflect-metadata';
import 'zone.js/dist/zone-node';
import { enableProdMode } from '#angular/core';
import * as express from 'express';
import { join } from 'path';
import * as https from 'https';
import * as fs from 'fs';
/*
* Load config from .env file
*/
require('dotenv').config({ path: './ng-exp/.env' });
const IS_HTTPS = process.env.IS_HTTPS === 'true';
const SSL_PATH = process.env.SSL_PATH;
const ENVIRONMENT = process.env.ENVIRONMENT;
// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();
const logging = !!process.env.LOGGING;
// Express server
const app = express();
const PORT = process.env.PORT || 4200;
const DIST_FOLDER = process.cwd();
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./dist/server/main');
// Express Engine
import { ngExpressEngine } from '#nguniversal/express-engine';
// Import module map for lazy loading
import { provideModuleMap } from '#nguniversal/module-map-ngfactory-loader';
// Our Universal express-engine (found # https://github.com/angular/universal/tree/master/modules/express-engine)
app.engine(
'html',
ngExpressEngine({
bootstrap: AppServerModuleNgFactory,
providers: [provideModuleMap(LAZY_MODULE_MAP)],
})
);
app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'ng-exp'));
// Server static files from /browser
app.get(
'*.*',
express.static(join(DIST_FOLDER, 'ng-exp'), {
setHeaders: (res, path) => {
if (/\.[0-9a-f]{20,}\./.test(path)) {
// file was output-hashed -> 1y
res.set('Cache-Control', 'public, max-age=31557600');
} else {
// file should be re-checked more frequently -> 5m
res.set('Cache-Control', 'public, max-age=300');
}
},
})
);
// ALl regular routes use the Universal engine
app.get('*', (req: express.Request, res: express.Response) => {
if (logging) {
console.log(`GET ${req.url}`);
}
res.render(
'index',
{
req,
res,
},
(err: Error, html: string) => {
res.status(html ? res.statusCode : 500).send(html || err.message);
if (logging) {
console.log(`RES ${res.statusCode} ${req.url}`);
if (err) {
console.log(err);
}
}
}
);
});
const sslOptions = {
key: fs.readFileSync(`${SSL_PATH}/${ENVIRONMENT}/server.key`),
cert: fs.readFileSync(`${SSL_PATH}/${ENVIRONMENT}/server.crt`),
};
// Start up the Node server
let server;
if (IS_HTTPS) {
server = https.createServer(sslOptions, app);
} else {
server = app;
}
server.listen(PORT, () => {
console.log(`Node Express server listening on http${IS_HTTPS ? 's' : ''}://localhost:${PORT}`);
const icmBaseUrl = process.env.ICM_BASE_URL;
if (icmBaseUrl) {
console.log('ICM_BASE_URL is', icmBaseUrl);
}
});
There is a related issue here:
https://github.com/hellosign/hellosign-embedded/issues/107
Basically, to avoid the error you can declare somewhere globally the window.
if (typeof window === 'undefined') {
global.window = {}
}
I also found React JS Server side issue - window not found which explains the issue better and why it works on the client side.
I found the solution thanks to some input from ChrisY
I deploy my code using webpack and run it using node. It seems that node somehow renders it server side and then the browser renders it too. The server site portion has no effect on the storefront but does cause the (seemingly harmless) error. In isSupported() I added console.log(isPlatformBrowser(this.platformId))and it printed false in the server terminal but true in the browser. Thus, I changed the code as follows:
constructor(#Inject(PLATFORM_ID) private platformId: any) {}
isSupported(): boolean {
return isPlatformBrowser(this.platformId) && !!indexedDB;
}
Now it still works in the browser as it did before but there is no server error.
Update:
I have also found the cause for the server side rendering. The server.ts file in the description has a block with res.render(. This first renders the page on the server and if it does not receive html, it returns status code 500. Otherwise it allows the client to render it. Seeing as this is a realistic scenario, I have decided to keep the extra isPlatformBrowser(this.platformId) check in my code. This should then be done for anything that can only be performed by the client (window, dom, workers, etc.).
Not not have server side rendering, an alternative to the res.render( block is
res.status(200).sendFile(`/`, {root: join(DIST_FOLDER, 'ng-exp')});
Related
i am been struggling for days, on this problem.
I am making an Universal React App with webpack 5.
I have enabled HMR on the client side by webpack-dev-middleware and webpack-hot-middleware
Now i want to do the same things for the backend witch is an express server.
I have tried nodemon but my biggest complain is that does auto restart the server and it doesn't match well with webpack-hot-middleware.
Then I have found webpack-hot-server-middleware witch just what i am looking for but it's not working and it is no longer maintained.
It is stuck in an infinite loop because of webpack-dev-middleware.
I have found this amazing github example linked by webpack-hot-middleware himseld on the troubleshooting section.
But again it is not quite working.
The longer it stay on this problem, I realise how much complex I try to accomplish.
Because I am on a webpack project where there two configuration (development and production) and each one does build theirs bundles.
I launch my node server on the server bundle and I want this to reload when I change the source code witch is in a another directory.
webpack-dev-middleware and webpack-hot-middleware does the job on the frontend because they both use the webpack configurations as a parameter but on backend it is much harder.
Here is what I have done so far:
Here is my main express server file:
server.js
import express from 'express';
import path from 'path';
import cors from 'cors';
import routerPage from './renderpage/routerPage.js';
import {envIsProduction, envIsDevelopment} from './envmode/envUtil.js';
import chokidar from 'chokidar';
const PORT = process.env.PORT || 8080;
let server = express();
if (envIsProduction()) {
console.log(" _______________________________________ ");
console.log("| |");
console.log("| ( PRODUCTION ) |");
console.log("|_______________________________________|");
server.use(express.static(path.join(__dirname,'..'))); // we serve static file like the bundle-app.js to the browser from the current directory where the server is executed and we move to the top root to access the file
}
else if (envIsDevelopment()) {
console.log(" _______________________________________ ");
console.log("| |");
console.log("| ( DEVELOPMENT ) |");
console.log("|_______________________________________|");
// TODO: Régler cohabitation webpack-dev-middleware qui rafraichit en boucle
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');
const webpackHotServerMiddleware = require('webpack-hot-server-middleware');
const config = require('../../webpack.conf.dev.js');
const compiler = webpack(config);
server.use(webpackDevMiddleware(compiler, {
serverSideRender: true,
}));
server.use(webpackHotMiddleware(compiler.compilers.find(compiler => compiler.name === 'client')));
// Do "hot-reloading" of express stuff on the server
// Throw away cached modules and re-require next time
// Ensure there's no important state in there!
const watcher = chokidar.watch('.');
watcher.on('ready', function() {
watcher.on('all', function() {
console.log("OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO Clearing /server/ module cache from server");
Object.keys(require.cache).forEach(function(id) {
console.log(id)
console.log("exist: " + /.[\/\\]/.test(id));
if (/.[\/\\]/.test(id)) delete require.cache[id];
});
});
});
// Do "hot-reloading" of react stuff on the server
// Throw away the cached client modules and let them be re-required next time
compiler.hooks.done.tap('done', (stats) => {
console.log("Clearing /client/ module cache from server");
Object.keys(require.cache).forEach(function(id) {
if (/[\/\\]client[\/\\]/.test(id)) delete require.cache[id];
});
})
}
server.use(cors());
server.use(express.urlencoded({extended:false}));
server.use(express.json());
server.get('/', routerPage); // when the user connect to the root of the server we send the page
server.get('/click', (req, res)=>{
console.log("IUHpzeijgàzhgzieghziêhg");
res.send("I got ur six");
});
server.get("*",function(req,res){
res.status(404).send('page not found');
}); //For all other type of request excluding the one specified here, we send back a 404 page;
server.listen(PORT, () => {
console.log(
`Server listening on \x1b[42m\x1b[1mhttp://localhost:${PORT}\x1b[0m in \x1b[41m${process.env.NODE_ENV}\x1b[0m`,
);
});
I use chokidar to watch all the file on the current directory including the file where the code is and I clear the cache to refresh the server.
Here is the tree structure of my project:
As you can see, I launch node on the bundle-server.js file but I want reload to occur when I change all the file in the serverside directory
But even the console appear, nothing change when I check.
Do you have any advices?
How can I make HMR work on the server side?
Thanks in advance for your responses.
I'm able to dynamically render html for server side rendering using angular 8 and angular universal when I view page source BUT my issue is that I am rendering the dynamic html before the api request has finished loading so I can't see the response in the dynamic html from the view page source - please let me know if you require any further info. I run this command:
i.e. npm run build:ssr && npm run serve:ssr
and when I look at the logs and change the url I get a [NetworkError] in the console but the app still runs as expected, I need to figure out a way to load the api response and then render the html into the page view source after the request has finished but I've run out of ideas.
Hopefully one of you guys can help,
Thanks
Server.ts
import 'zone.js/dist/zone-node';
import 'localstorage-polyfill';
import { join } from 'path';
import * as express from 'express';
const compression = require('compression');
const sessionStorage = require('sessionstorage');
const DIST_FOLDER = join(process.cwd(), 'dist');
const domino = require('domino');
const fs = require('fs');
const template = fs.readFileSync('./dist/browser/index.html').toString();
const win = domino.createWindow(template);
const proxy = require('http-proxy-middleware');
const cors = require('cors');
const helmet = require('helmet');
Object.assign(global, domino.impl);
(global as any)['KeyboardEvent'] = domino.impl.Event;
global['window'] = win;
global['Node'] = win.Node;
global['navigator'] = win.navigator;
global['Event'] = win.Event;
global['KeyboardEvent'] = win.Event;
global['MouseEvent'] = win.Event;
global['Event']['prototype'] = win.Event.prototype;
global['document'] = win.document;
global['sessionStorage'] = sessionStorage;
global['localStorage'] = localStorage;
// Express server
const app = express();
const PORT = process.env.PORT || 4200;
const {AppServerModuleNgFactory, LAZY_MODULE_MAP, ngExpressEngine, provideModuleMap} = require('./dist/server/main');
app.use(cors());
app.use(compression());
// express-engine
app.engine('html', ngExpressEngine({
bootstrap: AppServerModuleNgFactory,
providers: [
provideModuleMap(LAZY_MODULE_MAP)
]
}));
app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'browser'));
// Server static files from /browser
app.get('*.*', express.static(join(DIST_FOLDER, 'browser')));
// Protect website from Clickjacking attack
app.use(helmet.frameguard());
app.use(helmet.xssFilter());
// Proxy API Endpoints
app.use('/api/profileProxy', proxy(
{
target: 'http://xxxxxxx1:9004', // target host
changeOrigin: true, // needed for virtual hosted sites
// ws: true, // proxy websockets
pathRewrite: {
'^/api/profileProxy': ''
}
}
));
app.use('/api/searchProxy', proxy(
{
target: 'http://xxxxxx.160:9005', // target host
changeOrigin: true, // needed for virtual hosted sites
// ws: true, // proxy websockets
pathRewrite: {
'^/api/searchProxy': ''
}
}
));
app.get('/sitemap1.xml', function (req, res, next) {
const file = `${DIST_FOLDER}/sitemap1.xml`;
fs.exists(file, function (exists) {
if (exists) {
res.sendFile(file);
} else {
res.status(404).send('404');
}
});
});
app.get('/robots.txt', function (req, res, next) {
const file = `${DIST_FOLDER}/robots.txt`;
fs.exists(file, function (exists) {
if (exists) {
res.sendFile(file);
} else {
res.status(404).send('404');
}
});
});
// All regular routes use the Universal engine
app.get('*', (req, res) => {
console.time(`GET: ${req.originalUrl}`);
console.log(`req-QQQQQQQQQQ: ${req.originalUrl}`);
res.render(join(DIST_FOLDER, 'browser', 'index.html'), { req });
console.timeEnd(`GET: ${req.originalUrl}`);
console.log(`req-timeEnd: ${req.originalUrl}`);
});
// Start up the Node server
app.listen(PORT, () => {
console.log(`Node Express server listening on http://localhost:${PORT}`);
});
``
What you are describing is normal behaviour. Only your original request (e.h. when you type in the site's adress) in the URL bar will be prerendered using angular universal. This prerendered html is what you can see if you use View Source (or Ctrl +U)
Once the page is displayed, the client side angular app takes over. When you click on the search button, it'll perform an API call and get results, which will update the page. You'll see that html using the Html inspector from your browser's debugging tool, but it will have no impact on the original HTML that has been sent by the server
for people that may come across this issue - I was able to display the data from the api request (in my case the initial search results) in the view page source for SSR by making a GET request in the app.component of my app - thanks
I am developing a react app that uses server-side rendering.
My routes work, except the homepage. Not sure why. I'm assuming I'm not setting up my express server correctly to handle the index route...?
The following is the module which handles express' setup and whatnot. I'm assuming I have the app.get('*) or app.use(express.static) incorrect.
app.js (server)
require('ignore-styles')
const compression = require('compression')
const express = require('express')
const path = require('path')
require('babel-register')({
ignore: /\/(build|node_modules)\//,
presets: ['env', 'react-app']
})
const universalLoader = require('./universal')
const app = express()
// Support Gzip
app.use(compression())
// Serve static assets
app.use(express.static(path.resolve(__dirname, '..', 'build')))
// Always return the main index.html, so react-router render the route in the client
app.get('*', universalLoader)
module.exports = app
universalLoader.js (server)
import path from 'path'
import fs from 'fs'
import React from 'react'
import { Provider } from 'react-redux'
import { renderToString } from 'react-dom/server'
import { StaticRouter, matchPath } from 'react-router-dom'
import configureStore from '../src/store'
import App from '../src/components/App'
import routes from '../src/shared/routes'
import { getSiteInfo } from '../src/store/actions/siteInfo'
import { REACT_APP_SITE_KEY } from '../src/shared/vars'
import Helmet from 'react-helmet'
module.exports = function universalLoader(req, res, next) {
// console.log('Loading....')
const store = configureStore()
const fullUrl = req.protocol + '://' + req.get('host') + req.originalUrl
let routeFound = false
// Try to find a matched route
const promises = routes.reduce((promise, route) => {
var props = matchPath(req.url, route)
// If route was matched, component exists, and has an initialAction
// then call it's initialAction.
// This action will most-likely load some data asyncronously
if (props && route.component && route.component.initialAction) {
promise.push(Promise.resolve(store.dispatch(route.component.initialAction(store, props))))
}
return promise
}, [])
// Load initial site data
promises.push(Promise.resolve(store.dispatch(getSiteInfo(REACT_APP_SITE_KEY))))
// Wait until all async data has been loaded
Promise.all(promises)
.then(() => {
// Load index file path
const filePath = path.resolve(__dirname, '..', 'build', 'index.html')
// Read index file
fs.readFile(filePath, 'utf8', (err, htmlData) => {
if(err){
console.error('read err', err)
return res.status(404).end()
}
const preloadedState = store.getState()
// console.log("PreloadedState:", preloadedState)
const context = preloadedState
// console.log(context)
// Note: Pass in serverRequest prop so the App knows the domain it's on for meta tags
const markup = renderToString(
<Provider store={store}>
<StaticRouter location={req.url} context={context}>
<App serverRequest={req} serverResponse={res} />
</StaticRouter>
</Provider>
)
const helmet = Helmet.renderStatic()
// Somewhere a `<Redirect>` was rendered
if(context.url){
console.log('Redirected:', context.url)
redirect(301, context.url)
// we're good, send the response
}else{
// Page meta data
const meta = helmet.title.toString() + helmet.meta.toString() + helmet.link.toString()
// Prep state to be injected into DOM for client
const pageState = `<script>window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState).replace(/</g, '\\u003c')}</script>`
// Inject state and markup
const RenderedApp = htmlData
.replace('<script></script>', pageState) // Store state to pass to client
.replace('<meta name="helmet">', meta) // Meta data
.replace('{{SSR}}', markup) // Actual markup/component html
console.log("SSR Rendered: ", req.path)
res.send(RenderedApp)
}
})
})
.catch(err => {
console.log("Error:", err)
})
}
I am console.log()-ing when a route is being handled within universalLoader(). All routes show in the console that stuff is happening. Except my homepage. It does not even show the "Loading..." message.
express.static will be serving up any files in your build directory. If it finds the requested file it will serve it up and end the request/response. No middleware registered after express.static will get the chance to run if a suitable file is found.
Based on this line:
const filePath = path.resolve(__dirname, '..', 'build', 'index.html')
it would appear that you have a file called index.html in your build directory. This will get served up by express.static when you hit the URL for index.html but it will also get served up if you just hit the / URL because express.static defaults to serving up index.html. See the index property here:
https://expressjs.com/en/4x/api.html#express.static
The directory you point express.static at needs to contain files that are static, i.e. that require no processing whatsoever. If any of the files need processing they need to live elsewhere. Note the parallels with how an Express app typically has a separate folder for views, which in many ways is similar to what you're trying to do.
I would also suggest commenting out express.static to see what effect that has. It should be a quick way to confirm that express.static is responsible for stopping your index route being reached.
Update
Based on your comments it would seem that you do have a static directory at build/static that contains your JS and CSS files. You can serve this up directly using:
app.use(express.static(path.resolve(__dirname, '..', 'build', 'static')))
However, this will cause all your URLs to change too, so http://localhost/static/js/example.js will now be http://localhost/js/example.js. To retain the original URLs you would need to put the static back in via the route path:
app.use('/static', express.static(path.resolve(__dirname, '..', 'build', 'static')))
I have followed the tutorial found here:
https://blog.frankdejonge.nl/rendering-reactjs-templates-server-side/
In my server.js:
'use strict';
require("babel/register");
var React = require('react');
var express = require('express');
var path = require('path');
var bodyParser = require('body-parser');
var app = express();
app.use(bodyParser.json());
app.use('/', function(req, res) {
try {
var view = path.resolve('public/src/' + req.query.module);
var component = require(view);
var props = req.body || null;
res.status(200).send(
React.renderToString(
React.createElement(component, props)
)
);
} catch (err) {
res.status(500).send(err.message);
}
});
app.listen(3000);
console.log('Listening carefully...')
But when I run it I get Cannot find module 'babel/register'
If I comment that out, it works, but I get the following in the browser:
Unexpected token import
I'm guessing this is due to the error.
How can I fix this?
I changed it to this:
require('babel-register')({
presets: ['es2015', 'react']
});
...
Which got it a bit further, but now in my browser I am getting:
React.renderToString is not a function
My component:
import React from 'react';
class HelloComponent extends React.Component {
render () {
return (
<h1>Hello, {this.props.name}!</h1>
);
}
}
HelloComponent.defaultProps = { name: 'World' };
export default HelloComponent;
Looks like this code is using BabelJS version 5 - so when you will install babel#5 - it should work.
But maybe it would be better if you replace require("babel/register"); with require("babel-register"); and use babel#6. Also, you will need to add .babelrc file with configuration for babeljs (https://babeljs.io/docs/usage/babelrc/).
If you are looking to ready to use configuration for server-side rendering for react components - take a look at this project: https://github.com/zxbodya/reactive-widgets (I am an author).
It is a bit more than just server side rendering - it is a complete solution that will allow you to build isomorphic js components for your PHP application.
The proper syntax for babel register seems different now: use require("babel-register"); after having installed babel.
see require('babel/register') doesn't work : it is a similar issue
I am having trouble figuring out the best way for the serve to render my react components only after the http requests have been resolved.
For example:
component A <- component B <- component C (C triggers an action which makes a call to the API and returns data to render, in the mean time just renders 'loading').
When inspecting my source code, I only see 'loading' and would like, for SEO purposes, the server to wait till component C's call has resolved and rendered.
In theory, that sounds easy because the server should call that action itself and wait till the action is finished then call react.renderToString(), like this:
server.get('/', function (req, res, next) {
showMessages({}, function showMessagesCallback() { //add a callback
var html = React.renderToString(ChatComponent());
res.send(html);
});
});
But what if multiple components make action calls and I need to wait for multiple actions to solve and then call renderToString
Instead, the requests are picked up on the client side. My server file:
/**
* This leverages Express to create and run the http server.
* A Fluxible context is created and executes the navigateAction
* based on the URL. Once completed, the store state is dehydrated
* and the application is rendered via React.
*/
import express from 'express';
import path from 'path';
import serialize from 'serialize-javascript';
import {navigateAction} from 'fluxible-router';
import debugLib from 'debug';
import React from 'react';
import app from './app';
import HtmlComponent from 'components/Html';
const htmlComponent = React.createFactory(HtmlComponent);
const debug = debugLib('quran-com');
const server = express();
server.set('state namespace', 'App');
server.use('/public', express.static(path.join(__dirname, '/build')));
server.use('/images', express.static(path.join(__dirname, '/client/images')));
server.use('/fonts', express.static(path.join(__dirname, '/client/styles/fonts')));
server.use((req, res, next) => {
let context = app.createContext();
debug('Executing navigate action');
context.getActionContext().executeAction(navigateAction, {
url: req.url
}, (err) => {
if (err) {
if (err.status && err.status === 404) {
next();
} else {
next(err);
}
return;
}
debug('Exposing context state');
const exposed = 'window.App=' + serialize(app.dehydrate(context)) + ';';
debug('Rendering Application component into html');
const html = React.renderToStaticMarkup(htmlComponent({
context: context.getComponentContext(),
state: exposed,
markup: React.renderToString(context.createElement())
}));
debug('Sending markup');
res.type('html');
res.write('<!DOCTYPE html>' + html);
res.end();
});
});
const port = process.env.PORT || 8000;
server.listen(port);
console.log('Listening on port ' + port);
export default server;
what's the best way to do this?
You need to rethink architecture of your app. Components should not be getting data themselves, you are better of when something gives data to them.
My suggestion would be to do it in the navigate action, so that it becomes entry point into any view. Then here you can resolve all data needed and feed stores with that data, once resolved call the callback. E.g.:
module.exports = {
navigateAction: function (context, state, done) {
var completeNavigation = function () {
context.dispatch('NAVIGATE_DONE');
done()
}
var route = _.last(state.routes);
debug('navigate to: ' + route.name);
switch (route.name) {
case 'products':
context.executeAction(productActions.loadProducts, null, completeNavigation);
break;
default:
completeNavigation();
break;
}
}
};
In this sample I'm using react-router.