I have an Angular (4) template which injects what I give it using [innerHTML], and this cannot be changed as it's an external package. So originally I had something like this:
public getMessages(): InterfaceName {
return {
emptyMessage: `<span>Some content!</span>`
}
}
I then passed this into the template of the external element:
<external-element [messages]="this.getMessages()"></external-element>
This worked perfectly. However, the product I am working on needs to support internationalisation, so the strings all need to come from a separate JSON file, and Angular's TranslateService is used to retrieve the strings and render them. So in the internationalisation file, I had an array of messages which would each be posted to the emptyMessage object above. So I had this:
public getMessages(): InterfaceName {
const messages = this.translate.instant('PATH.TO.JSON.STRINGS');
return {
emptyMessage: `
<div>
<span *ngFor="let message of messages;">{{ message }}</span>
</div>`
}
}
However, this just rendered {{ message }}, presumably due to introducing the possibility of an XSS attack, Angular sanitised the HTML. I also tried to use [innerHTML]="message" but this just showed up blank.
So how can I get the content from the i18n file and render it, after which the content is injected using innerHTML, of which I have no control?
Related
I am constructing a markup string with an onclick action as shown below.
helpInfoMarkup: computed('levelInfo.name', function() {
return '<a class="help-text" {{action "switchContext"}}' + get(this, 'levelInfo.name') + '</a>';
}
My .hbs file
<div>{{{helpInfoMarkup}}}</div>
However this is not binding to the ember action and just appears as part of the markup and does not work? Any thoughts on this ?
This can not work because the glimmer rendering engine (which is used by ember) does not operate on strings.
Instead your ember templates are compiled to a binary format during the build, and later at the runtime glimmer will directly produce DOM, not an HTML string.
This means you can never pass a glimmer template around as a string. You could bundle the template compiler with your app (but you should not) to compile the template in the browser, but even then you can not use it to produce an HTML string representation because in this step you loose information (for example action binding). This is because these action bindings are never part of the HTML, but glimmer directly attaches event listeners to the DOM nodes during rendering. By using a HTML string and {{{...}}} you render plain HTML, not a glimmer template, and so this wont work.
What you should do is move all your HTML to templates and use a component to embed it.
The only other possibility is to utilize did-insert from ember-render-modifiers to manually attach an event. so you could do this:
<div {{did-insert this.attachSwitchContextAction}}>{{{helpInfoMarkup}}}</div>
and then in your component:
#action
switchContext() {
}
#action
attachSwitchContextAction(elem) {
elem.addEventListener('click', (...args) => this.switchContext(...args));
}
get helpInfoMarkup() {
return '<a class="help-text" ' + get(this, 'levelInfo.name') + '</a>';
}
this way you work around the glimmer template engine altogether and fall back to manual DOM manipulation.
You can remove the helpInfoMarkup computed property and update your template to
<div>
<a class="help-text" {{on "click" this.switchContext}}>{{this.levelInfo.name}}</a>
</div>
I'm working on a React frontend that gets data from a python JSON API. One section of my website has premium content and is reserved for paying users; currently I ensure that other users don't have access to it by fetching the content as a JSON object and converting it to JSX on the frontend according to a certain convention. For example:
{
{ 'type': 'paragraph', 'value': 'some text'},
{ 'type': 'anchor', 'href': 'some url', 'value': 'some description'}
}
would be rendered as :
<p>some text</p>
some description
Not surprisingly, things started to get pretty complicated as the content began to get more structured, simple things like making part of the text bold require a disproportional amount of effort.
As a potential solution, I had this idea: instead of sending the content as an object and parsing it, why not send a string of JSX and evaluate it on the frontend?
I started like this:
import * as babel from "#babel/standalone";
export function renderFromString(code) {
const transformed_code = babel.transform(code, {
plugins: ["transform-react-jsx"]
}).code;
return eval(transformed_code);
}
I imported this function in my premiumContent page and tried passing a complete component as a string (with import statements, etc) but got errors because the modules can't be found. I assumed this happens because the code is being interpreted by the browser so it doesn't have access to node_modules?
As a workaround, I tried passing only the tags to renderFromString and call it in the context of my component where all the modules are already imported :
import * as babel from "#babel/standalone";
export function renderFromString(code, context) {
const _es5_code = babel.transform(code, {
plugins: ["transform-react-jsx"]
}).code;
return function() {
return eval(_es5_code);
}.call(context);
}
This also failed, because it seems that eval will still run from the local context.
Finally, I tried doing the same as above but executing eval directly in my component, instead of from my function .This works as a long as I store "React" in a variable : import ReactModule from "react";const React = ReactModule, otherwise it can't be found.
My questions are:
Is there any way I can make my first two approaches work?
I know eval is considered harmful, but since the content is always completely static and comes from my own server, I don't see how this wouldn't be safe. Am I wrong?
Is there a better solution for my problem? That is, a way to safely deliver structured content to only some users without changing my single page app + JSON api setup?
The best solution for this is React server-side rendering.
Since you need markup that is client-side compatible but at the same time dynamically generated through React, you can offload the markup generation to the server. The server would then send the rendered HTML to the client for immediate display.
Here's a good article about React SSR and how it can benefit performance.
I'm trying to implement server side rendering on an angular 5 app. When the app is build and served everything works fine, however the source of the page isn't being rendered properly. I've discovered that if I use subscriptions and async pipes this solves the problem - what I see on the screen is also in the source. However, if I try to pipe the subscription and perform any actions with the return value the html is rendered correctly, however the source isn't.
This is how its implemented in the typescript when working correctly :
ngOnInit() {
this.httpResponse$ = this._dataService
.send(new BrochureRequest(this._stateService.params.propertyId));
}
This is the html
<div *ngIf="httpResponse$ | async as httpResponse; else loading">
{{content goes here}}
</div>
In this case the info is displayed on the screen, ie the html waits for the async response - and crucially - all the data on the screen is reflected in the source.
However, in many cases I will want to perform some actions on the data returned from the service - SEO, ad targeting, etc... in which case I would like to do something like this
ngOnInit() {
this.httpResponse$ = this._dataService
.send(new BrochureRequest(this._stateService.params.propertyId)).pipe(
tap((httpResponse) => {
this.activeLink =
this._headerService.getActiveLink(this.httpResponse.Id);
this._seoService.setSeoDetails(this.httpResponse.SeoDetails);
// do more stuff
return httpResponse;
}));
}
In this case, the info is displayed on screen as expected and all the other functions are called, however nothing in this component is appearing in the source. Its like the html was initially served without any of the data and not updated when the data was populated.
Any ideas why I'm not getting all my data in the source of the page? Is there a way I can delay any html rendering until the httpResponse is returned?
maybe you can use an event like ngAfterViewChecked? Read more about angular lifecycle hooks
here https://angular.io/guide/lifecycle-hooks
I wonder is there a way to load a react component template from server?
When I worked with Vuejs or any other js-library I actually have to add code to a view and after it rendered by server javascript starts running and do the rest.
For instance, I can type something like this in a Symfony app to get localized string: {{ 'header.article_title'|trans }} and it were translated.
However, since templates are hardcoded into a reactjs-component I can not use php/symfony/twig functions anymore. So, I'm wondering if there a way to fetch template from a server like an AngularJS templateURL-option.
Finally get how to do what I need.
I've realized that it is possible to not have actual file by URL. So, if I need a /react/component/Article.js it could be a URL to an action.
So, I've created a new bundle, add a controller and use one action with view for a single react-component.
The skeleton code looks like this:
/**
* Class ComponentController
* #package ReactjsBundle\Controller
*
* #Route("/react/component")
*/
class ComponentController extends Controller
{
/**
* #Route("/Article.js")
*/
public function articleAction()
{
$resp = new Response();
$resp->headers->set('Content-Type', 'text/javascript');
return $this->render('ReactjsBundle:Component:article.html.twig', [], $resp);
}
}
I'm currently reusing a template to populate a list using the code below:
<li>
{% include "ding/search_result.html" with title=field.title url=field.url description=field.description %}
</li>
When new data comes in, I want to be able to add new elements to my list dynamically from javascript. I can use the code below to add new list items:
$("ul.search-results-list").append('<li>New List Item</li>')
Is there a way for me to append the reusable template code I'm using, and pass in the appropriate parameters?
Yes there is! AND you get to refactor your APIs as well.
Imagine that you have an API that returns this json blob for every new list item.
{
name: "limelights"
}
And let's just use jQuery for this (even though I kinda don't want to.)
So you query the API like this
$.getJSON('api/v1/getName.json', {'someParameter', 123}, function() {
//this is where you would do your rendering then.
});
So how to do the rendering? Well, I'm a huge fan of underscore.js and it's rendering of templates, so I'm gonna go ahead and use that (but you can exchange it for Mustasche, Handlebars, Hogan or whatever you fancy).
First, we'll define a script template like this: (We're using underscore.js own ERB style template language).
<script type="text/template" id="my_fancy_list">
<li><%= name %></li>
</script>
Now this can live pretty much anywhere, either in your template or you can require it in if you're using Require.JS but we're gonna assume that it lives in your template (Hint: it could be rendered by some Django template tag)
Anyhoo,
Fill this template with data now:
var template = _.template($('script#my_fancy_list').html());
//this will give you the template as a javascript function.
//Query the API
$.getJSON('api/v1/getName.json', {'startsWith': 'l'}, function(data) {
$('ul.search-results-list').append(template(data));
});
And presto, you now have an JavaScript driven application that renders new data that comes from the server without fetching the entire template and render it all again.