angular 2 equivalent of react renderToString - javascript

I am looking for the angular 2 equivalent of react-dom/server.renderToString
import { renderToString } from 'react-dom/server';
// Render the component to a string
const html = renderToString(
<App />
);
What is the simplest code example to transform a directive/component into HTML using NodeJs?
I guess it should be possible with one of these packages:
#angular/compiler - v2.0.0-rc.2
#angular/platform-server -
v2.0.0-rc.2

Angular Components use HTML templates as part of the development process, but we actually compile those templates to Javascript, rather than HTML in order to render them to the browser.
The closest thing that might help you would be to look at https://github.com/angular/universal. This project represents the best way to run your Angular components on the server in a way that renders javascript and HTML and delivers it to the browser via the network.
We can't supply a simple method to "render" to HTML, because any sort of "rendering" involves the entire Angular framework (core, common, template compilation) and processing your entire component tree.
Hope this helps!

import 'angular2-universal/polyfills';
import {
provide,
REQUEST_URL,
ORIGIN_URL,
NODE_ROUTER_PROVIDERS,
NODE_HTTP_PROVIDERS,
Bootloader
} from 'angular2-universal';
import { APP_BASE_HREF } from '#angular/common';
const bootloader = new Bootloader({
platformProviders: [
{provide: ORIGIN_URL, useValue: 'http://localhost:3000'},
{provide: APP_BASE_HREF, useValue: '/'}
],
async: true,
preboot: false // { appRoot: 'app-root' } // your top level app component selector
});
export function ngApp(req, res) {
let url = req.originalUrl || '/';
const config = {
template: `<!doctype html><html><head>...</head><body>...</body></html>`,
directives: [AppComponent],
providers: [
{provide: REQUEST_URL, useValue: url},
...NODE_ROUTER_PROVIDERS,
...NODE_HTTP_PROVIDERS
]
};
bootloader.serializeApplication(config)
.then(html => res.send(html));
}

Related

Dynamically inject HTML and CSS on request

So I have been looking around on how to load CSS and HTML from the server.
What I want to achieve is to request a certain template to be displayed which sends the HTML and CSS to the website and loads it in together with some user-defined styles like colour
So far I was able to inject HTML using:
<div [innerHTML]="template | sanitizeHtml"></div>
and
import { Pipe, PipeTransform, SecurityContext } from '#angular/core';
import { DomSanitizer } from '#angular/platform-browser';
#Pipe({
name: 'sanitizeHtml'
})
export class SanitizeHtmlPipe implements PipeTransform {
constructor(private sanitizer: DomSanitizer) { }
transform(value: any): any {
return this.sanitizer.bypassSecurityTrustHtml(value);
}
}
Which I have seen from different posts and blogs (thank you for that).
The HTML I have been building works like a charm:
this.template = "<div class='template' style='width: 1080px; height: 1920px; background-color: #212121;'><div class='clr-row' style='padding:45px 0px 10px 25px; position: relative; width: inherit;'><div class='clr-col-5'><div style='width: 230px; height: 60px; background-image: url(*LINK_TO_IMAGE*); background-repeat: no-repeat; float: left;'></div></div></div></div>"
This HTML is a part of the complete template.
So what I would like to do is to use styles on this by using variables.
So what I have tried is to make a style object:
public style: {};
public template: string;
ngOnInit(){
this.style = {
template: {
"color": "#D8B088",
}
}
this.template = "<div [ngStyle]='style.template' class='template' style='width: 1080px; height: 1920px; background-color: #212121;'><div class='clr-row' style='padding:45px 0px 10px 25px; position: relative; width: inherit;'><div class='clr-col-5'><div style='width: 230px; height: 60px; background-image: url(*LINK_TO_IMAGE*); background-repeat: no-repeat; float: left;'></div></div></div></div>"
}
I have added the style object to the template by using [ngStyle]='style.template', for some reason the style didn't get loaded, so I tried to use camelCasing instead but still no success.
So does someone know how to get the CSS to work in this case, and eventually use user-defined styles?
Thanks in advance.
Edit 1:
I have also included the Sanitize pipe in the app.module.ts:
#NgModule({
declarations: [
...,
SanitizeHtmlPipe
],
...
});
(for those who were wondering)
Edit 2:
So I have been working out what I kinda want to have with these templates:
A user can register multiple devices of where they want to display the bookings from office 365. A user can setup templates in 2 ways, but this does not matter. When a user wants to display the template for a certain device they go to /device/:deviceid/template/:templateid.
This way the component will load in the template of that device.
So first we load in the device settings which contains the user styles for the template. Afterwards, we load in the data from office365 that has to be displayed in the template and finally load in the template with the template styles.
So there will be 3 requests to the server.
DeviceSettings -- Data Office365 -- Template
So far I have been able to load in the data and place this in the template, but the template was available locally and not from the server.
The reason why I want to have the templates to be requested from the server is that there will be an admin portal where those templates will be made and managed.
These templates will have a name, the HTML and the CSS.
For big template differences you can use Angular CDK Portal: https://material.angular.io/cdk/portal/overview
Example here: https://stackblitz.com/angular/mkvvyvgqxox?file=src%2Fapp%2Fcdk-portal-overview-example.ts
Instead of using [ngStyle] in sanitized HTML, I would instead just change class for dom element, into which sanitized HTML is inserted:
<div [ngClass]="templateClass" [innerHTML]="templateHtml"></div>
In this way code is more readable and styling code is separated from HTML.
Css for templates would look like this:
.template-class-1 {
background-color: #f44336;
}
.template-class-2 {
background-color: #4caf50;
}
Update 14/10/2020:
The previous solution required the compiler to be included that way you couldn't build the project in production mode. Thanks to Owen Kelvins answer it is now possible to add dynamic html and css while still being to build to production since it doesn't require the compiler:
Angular multiple templates in one component based on id (with template store)
For adding custom CSS you can either use Owen Kelvins method or append the "" tag at the end of the html and add in your custom CSS together with the end tag.
Original Answer:
I have found the solution to this subject. Thanks to someone in the discord server "The Coding Den", he messaged me about this and give me a link to Dynamically load template for a component on Github. After scrolling through this long post I found the answer of Alarm9k. This is how I used it to create a component that could display different templates based on a given id through a server request, I have also added some comments to explain it.
import { Component, AfterViewInit, Compiler, NgModule, ViewChild, ViewContainerRef, OnInit } from '#angular/core';
import { CommonModule } from '#angular/common';
import { BookingService } from 'src/app/services/booking.service';
import { ApplicationModel } from 'src/app/models/application.model';
import { Booking } from 'src/app/models/vo/booking';
import { Subscription } from 'rxjs';
import { SplitStringPipe } from '../../utils/split-string.pipe';
import { HttpClientModule } from '#angular/common/http';
import { BrowserAnimationsModule } from '#angular/platform-browser/animations';
import { BrowserModule } from '#angular/platform-browser';
#Component({
selector: 'app-bookings-template',
templateUrl: './bookings-template.component.html',
styleUrls: ['./bookings-template.component.css']
})
export class BookingsTemplateComponent implements AfterViewInit {
public template: string;
public date: Date;
public locale: string;
public id: string;
#ViewChild('container', { read: ViewContainerRef, static: false }) container: ViewContainerRef;
constructor(private compiler: Compiler, private bs: BookingService, private apm: ApplicationModel) { }
ngAfterViewInit() {
// Must clear cache.
this.compiler.clearCache();
// fill in template from server request
this.template = "<div class="test">{{test}}</div>;
var styles = ".test{color:red}";
// Define the component using Component decorator.
const component = Component({
template: this.template + "<div>Hard Coded html for error checks and loading spinner</div>",
styles: [styles]
})(class implements OnInit {
//example properties
public date: Date;
public bookings: Array<Booking>;
public isLoading: boolean = true;
public hasError: boolean = false;
public errorMessage: string;
public errorMessageSub: Subscription;
public bs: BookingService;
public apm: ApplicationModel;
// Do not pass any parameters in the constructor or it will break!
// Instead pass it within the factory method down below as a property!
constructor() {
// refresh template every minute
setInterval(() => {
this.ngOnInit();
}, 60000);
// refresh date every second
setInterval(() => {
this.date = new Date();
}, 1000);
}
ngOnInit() {
// get data to fill in template
}
ngOnDestroy() {
//remove error subscription
this.errorMessageSub.unsubscribe();
}
});
// Define the module using NgModule decorator.
//Modules can be changed based on your needs
const module = NgModule({
imports: [
CommonModule,
BrowserAnimationsModule,
BrowserModule,
HttpClientModule],
declarations: [component, SplitStringPipe],
providers: [BookingService]
})(class { });
// Asynchronously (recommended) compile the module and the component.
this.compiler.compileModuleAndAllComponentsAsync(module)
.then(factories => {
// Get the component factory.
const componentFactory = factories.componentFactories[0];
// Create the component and add to the view.
const componentRef = this.container.createComponent(componentFactory);
// pass parameters that would go in the constructor as properties
// subscriptions should also work.
componentRef.instance.bs = this.bs;
componentRef.instance.apm = this.apm;
componentRef.instance.errorMessageSub = this.apm.getMessageError().subscribe(me => componentRef.instance.errorMessage = me);
});
}
}
The BookingsTemplateComponent acts as the parent of the anonymous component class which acts as the child. This way the child can be added to the parent thanks to #ViewChild where the container name is specified and matches with the parent html id:
<div #container></div> (in this case).
You will also need to add some things to the app module:
import { NgModule, CompilerFactory, Compiler, COMPILER_OPTIONS } from '#angular/core';
import { JitCompilerFactory } from '#angular/platform-browser-dynamic';
import { CommonModule } from '#angular/common';
export function createCompiler(compilerFactory: CompilerFactory) {
return compilerFactory.createCompiler();
}
#NgModule({
declarations: [
// components and pipes
...
],
imports: [
CommonModule, // required
... //other modules
],
providers: [
// different services
...,
// these are need to add the compiler manually to the project
{ provide: COMPILER_OPTIONS, useValue: {}, multi: true },
{ provide: CompilerFactory, useClass: JitCompilerFactory, deps: [COMPILER_OPTIONS] },
{ provide: Compiler, useFactory: createCompiler, deps: [CompilerFactory] }
],
bootstrap: [AppComponent]
})
export class AppModule { }
WARNING:
The most important factor of this is that you cannot build the project in production mode. The reason for this is because JIT compilation doesn't work and you will get the following error:
This is because the angular compiler is not included in the production environment, even when you try to add it manually.

Angular 4 unit test, but getting error No provider for Http

I'm trying to learn how to run a unit test component on angular 4, but I'm not getting success, when I run the test with the code below I get this error:
Error: No provider for http! and Failed: :
could not find an object to spy upon for filexGeneralData()
I don't know if I'm on the right way...
Take a look at my code
my spec file
import { TestBed, async, inject } from '#angular/core/testing';
import { HttpModule } from '#angular/http';
import { of } from 'rxjs/observable/of';
import { filex } from '../../../models/filex';
import { filexService } from '../../../services/filex.service';
import { fileyfilexComponent } from './filey-filex.component';
import { dataService } from '../../../services/data.service';
describe('fileyfilexComponent', () => {
let filexService;
let myComponent;
let fixture;
let element;
beforeEach(
async(() => {
TestBed.configureTestingModule({
declarations: [fileyfilexComponent],
providers: [filexService, dataService],
imports: [HttpModule]
}).compileComponents();
})
);
beforeEach(inject([filexService], s => {
filexService = s;
fixture = TestBed.createComponent(fileyfilexComponent);
myComponent = fixture.componentInstance;
element = fixture.nativeElement;
}));
it(
'should call getUsers and return list of users',
async(() => {
const response: filex[] = [];
spyOn(filexService, 'filexGeneralData').and.returnValue(of(response));
myComponent.method1();
fixture.detectChanges();
expect(myComponent.datafilex).toEqual(response);
})
);
});
You just need to include HubWrapperComponent in your TestBed. In the providers array, you need to include all of the services provided to your component being tested (better yet, you should provide "mocked" versions of those service). So, you could get the error to "go away" by simply adding HubWrapperComponent to the providers array in your spec file's TestBed.configureTestingModule method. It will end up looking like this:
spec.ts:
TestBed.configureTestingModule({
declarations: [IndicatorsDashboardComponent],
providers: [DashboardService, DadosService, HubWrapperComponent],
imports: [HttpModule]
}).compileComponents();
An additional piece of advice: I would advise using jasmine to mock your HubWrapperComponent (which seems to be a wrapper over the HttpClient?).
mockWrapper = jasmine.createSpyObj('http', ['get']);
Then in your providers array:
{provide: HubWrapperComponent, useValue: mockWrapper}
That approach would look something like this:
let mockHub: SpyObj<HubWrapperComponent>;
beforeEach(
async(() => {
mockHub = jasmine.createSpyObj('http', ['get']);
TestBed.configureTestingModule({
declarations: [IndicatorsDashboardComponent],
providers: [
DashboardService,
DadosService,
{ provide: HubWrapperComponent, useValue: mockHub }
],
imports: [HttpModule]
}).compileComponents();
})
);
Mocking a service / anything that makes Http calls is preferred because you don't want to make real requests in your tests.

Unable to read Route Parameter in Angular 2

I have successfully implemented route parameter in Angular JS for my other components in same project and for the new component also I am following the same way but It's not working and I am unable to understand the problem in the code.
Below is the code of my routes file
import { Routes, RouterModule } from '#angular/router';
import { CustomerBillComponent } from '../components/customer_bill.component';
const routes: Routes = [
{ path: 'add-bill', component: CustomerBillComponent },
{ path: 'add-bill/:customer_reference', component: CustomerBillComponent },
];
export const routing = RouterModule.forRoot(routes);
(I tried using different routes also, but it didn't work)
Below is the code in My CustomerBillComponent.ts file
ngOnInit() {
//When I visit /#/add-bill/CR452152
let route_location = location['hash'].split('/')[1].toLowerCase();
console.log(route_location); //prints add-bill in Console
var customer_reference = this.route.snapshot.params['customer_reference'];
console.log(customer_reference); //prints undefined
}
Did I miss something
Thanks in advance!!!
I face almost the same problems. But the reason was different.
In my scenario, the lending page(Component Specified on routing) was different and I was trying to access the route params on the parent Route Component.
Below is the code to demonstrate my scenario:
home.module.routing.ts
import { NgModule } from '#angular/core';
import { Routes, RouterModule } from '#angular/router';
//other import statements...
const routes: Routes = [
{
path: 'home', component: HomeComponent,
children: [
{ path: 'buy/:mainType/:subType', component: BuyComponent, data: {} },
{ path: 'buy/:mainType', component: BuyComponent, data: { } },
{ path: '', redirectTo: 'buy', pathMatch: 'full' },
]
},
{ path: 'checkout', component: CheckoutCartComponent },
];
#NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class HomeRoutingModule { }
So, I had to access the RouteParam values in HomeComponent(Parent) and the Lending page was BuyComponent(Child Route of HomeComponent).
So, I was not getting values of route params using below code:
//Inside HomeComponent.ts
ngOnInit() {
this.sub = this.route.params.subscribe(params => {
this.routeText = params['category'];
this.title = this.formatTitle(this.routeText);
});
}
As the lending page is BuyComponent, the above code will not work. Angular allows to access the route params in the above way only on the landing Component.
I tried a lot and finally got a solution to access the Route Params in parent route Component.
Below is the code to access the same:
//Inside HomeComponent.ts
const routeChildren = this.activatedRoute.snapshot.children;
if (routeChildren.length) {
//Your code here to assign route params to the Component's Data Member.
}
So, accessing this.activatedRoute.snapshot.children instead of this.route.params did the trick for me.
Hope this will help if someone lands here in search of the problem like me.
Best Regards.
You need to add ModuleWithProviders. It's a wrapper for module provider.
import {ModuleWithProviders} from '#angular/core';
export const routing: ModuleWithProviders = RouterModule.forRoot(router);
First try:
this.route.params.subscribe(
(params) => {
console.log(params);
}
)
And see what you get. If that doesn't work it's likely that you try to access a parent route's parameter, in which case you have to step up in the router tree in order to gain access to it.
You can do this with the .parent property, like such:
this.route.parent.params.subscribe(
(params) => {
console.log(params);
}
)
As many times as you need.

Angular 2 RC5 providers at component level get new instance every time

Until RC5 we could declare providers at component level like this:
#Component({
providers: [SomeService]
})
And every component would get a new instance of SomeService, but now with RC5 the component providers as been deprecated so how we achieve this effect?
One way would be to add a factory method to your service that returns a new instance.
export class SomeService {
constructor(private http: Http) {}
create() {
return new SomeService(this.http);
}
}
That is very much a hack and requires you to call create in your components.
It seems the subscribed solution is to use a Factory Provider.
Essentially you create factory method and provider object:
import { Http } from '#angular2/core';
import { SomeService } from './SomeService';
let someServiceFactory = (http: Http) => {
return new SomeService(http);
};
export let SomeServiceProvider =
{ provide: SomeService,
useFactory: someServiceFactory,
deps: [Http]
};
So now you inject SomeService as you did before, and you will always get a new transient instance.
Import the provider into your module or component and register it as providers: [someServiceProvider]
Or inline it as:
providers: [{
provide: SomeService,
useFactory: (http: Http) => { return new SomeService(http)},
deps: [Http]
}]
According to the quick start here services have not really changed. Still need the 4 core things
A service is created via injectable
2.import that service into component
3.provide that service
4 and construct it in the export

Angular ui-router's nested routes not working in ES6 with bable?

I have multiple modules in Angular and I also use nested routes. There is an auth module which also has a sub route auth.login the code goes as follows:-
Login.route.js
routes.$inject = ['$stateProvider'];
export default function routes($stateProvider) {
$stateProvider
.state('auth.login', {
url: '/login',
template: require('./login.tpl.html')
});
}
auth.route.js
routes.$inject = ['$stateProvider'];
export default function routes($stateProvider) {
$stateProvider
.state('auth', {
url: '/auth',
template: require('./auth.tpl.html')
})
}
Then inject these into the main module like this according to the folder structure.
import auth from './features/auth';
import auth from './features/auth/login';
I am not getting any error but apart from the / & /auth path nothing reflects. If I use /login it redirects me to the / path.
Kinda weird but UI-Router is not working. Please suggest.
NOTE: I use BableJS and Webpack for the Development
I noticed that the code you've shown never actually invokes the exported functions. That might be the root of your problem.
A better way to approach this might be to export the state objects themselves. In the outermost file, you can then import those state objects, and register them with the $stateProvider. Here's an example:
Login.route.js
let loginState = {
// UI-Router allows state definitions to contain the name
name: 'auth.login',
url: '/login',
template: require('./login.tpl.html')
}
// Just export the state definition; you don't have to register it yet
export default loginState;
auth.route.js
let authState = {
name: 'auth',
url: '/auth',
template: require('./auth.tpl.html')
}
export default authState;
app.js
This file shows bootstrapping the application. It imports the state definitions from the child modules, and registers them with the $stateProvider.
// Now import the state definitions from the other modules
import loginState from './Login.route.js';
import authState from './auth.route.js';
let app = angular.module('app', ['ui.router']);
// create a single config block which registers
// all the state definitions that were imported
app.config(registerAllStates);
registerAllStates.$inject = ['$stateProvider'];
function registerAllStates($stateProvider) {
// Loop over them and register them with the $stateProvider.
[loginState, authState].forEach(state => $stateProvider.state(state));
}

Categories