I would like to implement a zoom feature on my website which has a SVG image displayed.
I saw this library github.com/ariutta/svg-pan-zoom which provides the exact features i need,
However i can't seem to get it to work with angular2, window is not reachable.
after some research i figured i have to shim window into the webpack export for svg-pan-zoom. Maybe i'm not looking for the right thing but i think it is quite amazing there is so much work that needs to be done just to import a 3rd party javascript.
best clue i could find is this : https://github.com/ariutta/svg-pan-zoom/issues/207
EDIT: See answer.
I used this angular 2 aspnet core starting project: https://damienbod.com/2017/01/01/building-production-ready-angular-apps-with-visual-studio-and-asp-net-core/
EDIT: actually it was this one https://github.com/MarkPieszak/aspnetcore-angular2-universal but the branch got updated at the time i made this post which got me confused
I have this service here,
svg-pan-zoom.service.ts
import { Injectable } from '@angular/core'
import { isBrowser } from 'angular2-universal';
import * as svgPanZoom from 'svg-pan-zoom';
@Injectable()
export class SvgPanZoomService {
getPanZoom(element: any) {
if (isBrowser) {
svgPanZoom(element);
}
}
}
which is called here map.component.ts
import { Component, AfterViewInit } from '@angular/core';
import { SvgPanZoomService } from '../../injectables/svg-pan-zoom.service';
import { isBrowser } from 'angular2-universal';
@Component({
selector: 'map-full',
template: require('./map.component.html'),
styles: [require('./map.component.css')]
})
export class MapComponent implements AfterViewInit {
constructor(private svgZoom: SvgPanZoomService) {
if (isBrowser) {
this.svgZoom.getPanZoom('#evSvgMap');
}
}
}
The SvgPanZoomService service is referenced in my app.module
the Svg-pan-zoom lib is referenced in my package.json,
In the end my build gives me 2 js files, main-client.js and vendor.js
I can browse main-client.js and see it has references to svg-pan-zoom,
it is present in my browser sources when my page loads
But when it comes to loading the part where it should be doing stuff, i get this error.
An unhandled exception occurred while processing the request.
Exception: Call to Node module failed with error:
Prerendering failed because of error: ReferenceError: window is not defined
at D:\[mystuff]\node_modules\svg-pan-zoom\dist\svg-pan-zoom.js:1493:8
Now i read i'm not meant to access window from a component, but i have no say in what the lib calls, i read somewhere here adding (isBrowser) should verify im not calling this server-side (why would i want the server to zoom this is ui right?)
here is my webpack.config.js
var isDevBuild = process.argv.indexOf('--env.prod') < 0;
var path = require('path');
var webpack = require('webpack');
var nodeExternals = require('webpack-node-externals');
var merge = require('webpack-merge');
var allFilenamesExceptJavaScript = /\.(?!js(\?|$))([^.]+(\?|$))/;
// Configuration in common to both client-side and server-side bundles
var sharedConfig = {
resolve: { extensions: [ '', '.js', '.ts' ] },
output: {
filename: '[name].js',
publicPath: '/dist/' // Webpack dev middleware, if enabled, handles requests for this URL prefix
},
module: {
loaders: [
{ test: /\.ts$/, include: /ClientApp/, loader: 'ts', query: { silent: true } },
{ test: /\.html$/, loader: 'raw' },
{ test: /\.css$/, loader: 'to-string!css' },
{ test: /\.(png|jpg|jpeg|gif|svg)$/, loader: 'url', query: { limit: 25000 } }
]
}
};
// Configuration for client-side bundle suitable for running in browsers
var clientBundleConfig = merge(sharedConfig, {
entry: {
'main-client': './ClientApp/boot-client.ts'
},
output: { path: path.join(__dirname, './wwwroot/dist') },
devtool: isDevBuild ? 'inline-source-map' : null,
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./wwwroot/dist/vendor-manifest.json')
})
].concat(isDevBuild ? [] : [
// Plugins that apply in production builds only
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.optimize.UglifyJsPlugin()
])
});
// Configuration for server-side (prerendering) bundle suitable for running in Node
var serverBundleConfig = merge(sharedConfig, {
entry: { 'main-server': './ClientApp/boot-server.ts' },
output: {
libraryTarget: 'commonjs',
path: path.join(__dirname, './ClientApp/dist')
},
target: 'node',
devtool: 'inline-source-map',
externals: [nodeExternals({ whitelist: [allFilenamesExceptJavaScript] })] // Don't bundle .js files from node_modules
});
module.exports = [clientBundleConfig, serverBundleConfig];
the app then loads another webpack.config.vendor.js
var isDevBuild = process.argv.indexOf('--env.prod') < 0;
var path = require('path');
var webpack = require('webpack');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var extractCSS = new ExtractTextPlugin('vendor.css');
module.exports = {
resolve: {
extensions: [ '', '.js' ]
},
module: {
loaders: [
{ test: /\.(png|woff|woff2|eot|ttf|svg)(\?|$)/, loader: 'url-loader?limit=100000' },
{ test: /\.css(\?|$)/, loader: extractCSS.extract(['css']) }
{ test: require("svg-pan-zoom"),
loader: "imports-loader?window=>window./svg-pan-zoom.js"} // wild try
]
},
entry: {
vendor: [
'@angular/common',
'@angular/compiler',
'@angular/core',
'@angular/http',
'@angular/platform-browser',
'@angular/platform-browser-dynamic',
'@angular/router',
'@angular/platform-server',
'angular2-universal',
'angular2-universal-polyfills',
'bootstrap',
'bootstrap/dist/css/bootstrap.css',
'es6-shim',
'es6-promise',
'jquery',
'zone.js',
'svg-pan-zoom' //i added this, no clue if it's relevant.
//EDIT :Turns out it was important, very much so.
]
},
output: {
path: path.join(__dirname, 'wwwroot', 'dist'),
filename: '[name].js',
library: '[name]_[hash]',
},
plugins: [
extractCSS,
new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery'}), // Maps these identifiers to the jQuery package (because Bootstrap expects it to be a global variable)
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.DllPlugin({
path: path.join(__dirname, 'wwwroot', 'dist', '[name]-manifest.json'),
name: '[name]_[hash]'
})
].concat(isDevBuild ? [] : [
new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } })
])
};
i also have 2 files boot-client and boot-server related to the webpack.config.js which are instructions for the packaging of the server and client files.
thanks.
This works
https://github.com/MarkPieszak/aspnetcore-angular2-universal#universal-gotchas
However keep in mind that if you're using webpack you need to separate your client and server side code by making two separate bundles, the server side bundle must not contain references to the client side javascript libraries you are using, in this case svg-pan-zoom calls window which does not exist server side.
this separation can be achieved by adding a section such as below inside your webpack.config
this null-loader requires a
npm install null-loader --savemore info : https://github.com/webpack-contrib/null-loader
Once you've separated the bundles, make sure to make every call to your scripts that may require
window,document,navigator, i encountered issues withlocalStoragetoo, inside the if(isPlatformBrowser(this.platformId)) { //your code }blocksEDIT now it's all good: Now i got it working I'll still keep this under here as it kept my spirits up when i could not figure out how to make anything work. webpack is very different from all the things i had seen before and i just was too lazy to read the docs but they do exist and in the end it is a very powerful tool that can get things done. The difference is that if you do not use server side rendering, features such as adding meta tags and descriptions will not be executed at server time. I believe the angular2 app is considered as client javascript by a google crawler for instance and thus will not be loaded making your SEO efforts worthless.
Another suggestion was to get rid of the server side rendering feature, which i did, and it worked for this and any other client related issue that arised later on. https://github.com/MarkPieszak/aspnetcore-angular2-universal#faq---also-check-out-the-faq-issues-label