angular universal: dynamic imports for browser only
So there is a way to render PrimeNG components in browser and omit them while server rendering. Those questions helped me start digging the right direction:
angular-cli: Conditional Imports using an environment variable
How can I conditionally import an ES6 module?
While server rendering I used mock component that renders a simple input field and uses the same selector 'p-calendar'. The final code I ended up with in my app.module.
...//other imports
import { isBrowser } from 'angular2-universal';
let imports = [
... //your modules here
];
let declarations = [
... //your declarations here
];
if (isBrowser) {
let CalendarModule = require('primeng/components/calendar/calendar').CalendarModule;
imports.push(CalendarModule);
}
else {
let CalendarMockComponent = require('./components/primeng/calendarmock.component').CalendarMockComponent;
declarations.push(CalendarMockComponent);
}
@NgModule({
bootstrap: [AppComponent],
declarations: declarations,
providers: [
... //your providers here
],
imports: imports
})
To make your mock component support [(ngModel)] binding use this tutorial. http://almerosteyn.com/2016/04/linkup-custom-control-to-ngcontrol-ngmodel
import { Component, forwardRef } from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
export const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CalendarMockComponent),
multi: true
};
@Component({
selector: 'p-calendar',
template: '<input type="text" class="form-control"/>',
providers: [CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR]
})
export class CalendarMockComponent implements ControlValueAccessor {
private innerValue: any = '';
private onTouchedCallback: () => void = () => {};
private onChangeCallback: (_: any) => void = () => {};
//From ControlValueAccessor interface
writeValue(value: any) {
if (value !== this.innerValue) {
this.innerValue = value;
}
}
registerOnChange(fn: any) {
this.onChangeCallback = fn;
}
registerOnTouched(fn: any) {
this.onTouchedCallback = fn;
}
}
An alternative solution for module imports that does not require dynamic script loading: you can use the compilerOptions.paths
option in your server app's tsconfig.json
file to redirect the imported module to a server-only version:
{
...
"compilerOptions": {
...
"paths": {
"path/to/browser/module": "path/to/server/module"
}
}
}
When the server app builds, the compiler will import the server module instead of the browser module.