Vue: Chrome tab crashes when trying to display a long string - google-chrome

I have a vue component whose purpose is to display a string.
The string can be very long - the one I tested had about 3 megabytes.
When trying to display string of such size the chrome tab crashes with its CPU usage going up to 100%. The console is clear.
Here's the simplified code of the component:
<template>
<div>
{{ output }}
</div>
</template>
<script>
export default {
name: "OutputField",
props: ['output']
}
</script>
The problem does not occur on Firefox.
It also disappears once the {{ output }} is commented out - which leads me to believe it has nothing to do with the logic of parent component.
Last but not least, when directly inserting the string into the innerHTML of the div, it is shown correctly even on Chrome.
I would really appreciate an explanation of this behavior and suggestions on how to display the string in a way that won't lead to it. Thanks in advance!

After looking into the problem more I managed to narrow down the possible cause to specific string messing up Vuetify's behavior in Chrome.
Created a separate question, as adding the details here would make the initial one hard to read.
It is available here: ­ inserted into string in Vuetify crashing Chrome tab

Since the only purpose of your component is to display a value passed as a prop, you should use a functional component. It basically is a component that gets rid of the overhead that vue needs to have a state (data, methods, etc.). Instead, it will deal only with props passed to it.
You can set it up like this:
ChildComponent.vue
<template functional>
<div>{{ props.outputVal }}</div>
</template>
ParentComponent.vue
<ChildComponent outputVal="stringToDisplay">
It probably won't make the page respond instantly still, as that's a lot of data to display, but it should increase the performance by a lot and at least allow the string to display.
Here is an example of it which displays a huge string:
https://codesandbox.io/s/vue-functional-components-xbpci

Related

How to get a I18n variable value I can return to my Angular parent component?

I'm new to Angular and I just put in place an i18n (2 languages) system for a website I am creating. Everything works properly but in order to switch from one language to another in my header, I feel stuck.
I followed the Angular documentation to transfer my variables from child to parent component and I ended with this:
<input type="text" id="item-input" #lang>
<button type="button" (click)="changeChosenLang(lang.value)">
{{ 'global.lang' | translate }}
</button>
As you can see, I write my language in the input form and I send it to the proper component with a button. What I wanted was to click on my 'global.lang' text and to be able to send its value to the parent component, since the value is the language which is not actually used.
I don't know how to put my 'global.lang' text in a variable, neither what kind of balise I can use. Also I didn't know how to summarize my problem to search for it on StackOverflow so if you know a similar post, don't hesitate to post the link.
Thank you for your reading!
I found a less tortured way (poor brain) to have the result I wanted:
<span (click)="changeChosenLang()">
{{ 'global.lang' | translate }}
</span>
First I temporary changed my button to a span balise and I deleted the parameter from my changeChosenLang() function. Then, I transferred a variable 'lang' from my parent component to this one, witch contains the value of the language chosen in my app constructor. At each click, I change its value in my changeChosenLang() function and everything works great!
I hope it can help someone someday. The moral of this post is: the simpler, the better! Have a good day.

Correct way to scroll to dynamic angular page

I am searching for the correct way to correct to scroll in a page that will load dynamic information. These informations are asynchronous so to avoid my user seeing the whole page constructing itself I have a boolean flag like so :
<div *ngIf="loaded"> ... </div>
My problem is that I want to scroll to an anchor using the angular router, but that anchor doesn't exist yet at this moment because the load isn't finished.
The anchor :
<hr id="my_anchor">
the code I use to load the page and get to that anchor :
<a [routerLink]="['/some/route/', idParameter]" fragment="my_anchor">...</a>
You probably want to use virtual scrolling, available through the Angular CDK:
https://material.angular.io/cdk/scrolling/examples
https://medium.com/codetobe/learn-how-to-us-virtual-scrolling-in-angular-7-51158dcacbd4
This approach dynamically loads only those components that are on screen, allowing you to load extremely large datasets.
It is also possible to code this manually using:
const el: any = document.elementFromPoint(x, y);
This Javascript function determines which HTMLElement is located at a specific x, y, coordinate and thereby determining which elements are on screen. Using this information you can wrap all items in a list like so:
<ng-container *ngFor="let data of datas">
<ng-container *ngIf="data.isOnScreen">
<app-my-component></app-my-component>
</ng-container>
<ng-container *ngIf="!data.isOnScreen">
<div class="empty-div"> </div>
</ng-container>
</ng-container>
and style the empty-div to be the same size as a non empty div. This ensures that scrolling works. I got this working well. However, no doubt the CDK makes this a whole world smoother and easier.
The only benefit with my custom approach is it gives you total control. You can easily use:
list.scrollTop = 999;
to scroll to any position in the list, thus supporting anchors (where list is the HTML list element that will scroll). Not for the feint hearted though, would only recommend this for confident coders.

Issues with text rendering in react component

So I am working with an API, and found this in the documentation:
let message = {
"text" : "<i>or</i> HN: the Next Iteration<p>I get the impression that with Arc being released a lot of people who never had time for HN before are suddenly dropping in more often. (PG: what are the numbers on this? I'm envisioning a spike.)<p>Not to say that isn't great, but I'm wary of Diggification. Between links comparing programming to sex and a flurry of gratuitous, ostentatious adjectives in the headlines it's a bit concerning.<p>80% of the stuff that makes the front page is still pretty awesome, but what's in place to keep the signal/noise ratio high? Does the HN model still work as the community scales? What's in store for (++ HN)?",
"time" : 1203647620
}
So in react (using function component), what is the proper way of rendering message.text? I am asking because my rendering output still has the tag <i></i> and <p></p> in the text when I return {message.text}. So I guess there is something wrong.
If you want to render a string containing html you can use dangerouslySetInnerHTML
In your case will be:
<div dangerouslySetInnerHTML={{ __html: message.text }} />
Watch out using this attribute because it could expose your app to XSS attacks. (check https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml)

How do I set AG-Grid's height to automatically match the data content height?

I'm trying to get ag-grid's height to match the content.
I have several sets of data which I'm trying to load into different grids, each with the appropriate height for the data.
There is an AutoHeight option but the Angular version doesn't seem to work (I'm assuming this is for a previous version).
Here is my alternative attempt, which doesn't work:
<ng-container *ngFor="let reportItem of reportData">
<br />
<ag-grid-angular style="width: 100%; height: {{ 560 + (reportItem.data.length * 40) }}px;"
class="ag-theme-material bold-headers"
[rowData]="reportItem.data"
[columnDefs]="columnDefs">
</ag-grid-angular>
</ng-container>
Does anyone have a better suggestion?
Rather than just linking to the Auto-Grid Height method I described at the top - which I tried and failed with, could you please explain how I should implement it with my code above? (as I accept I may have missed something)
Thanks
There is the setGridAutoHeight method available on the api object. You need to get a reference to that object from the gridReady event and then you can call it, passing true to it. You have to take care with it if your data has many rows because using this method all the rows will be rendered to the DOM, normally only the visible ones are rendered.
gridApi.setGridAutoHeight(true)
It's at the bottom of the page: https://www.ag-grid.com/javascript-grid-api/
according to this link you can use domLayout='autoHeight' as a property to your grid.
it worked for me.

Angular4 ng-content gets built when ngIf is false

I have a problem with the new ng-content transclusion.
Let's say I have a component my-component that, in its ngOnInit() function does some heavy operation on load (for now, just a console.log()).
I have a wrapper, that displays the content via transclusion (my-wrapper.component.html).
<ng-content></ng-content>
If I set the surroundings up like this, the log statement doesn't show:
<my-wrapper *ngIf="false">
<my-component></my-component>
</my-wrapper>
I assume, the my-wrapper component does not get built, so the content is ignored.
But if I try to move the logic into the my-wrapper component like this (my-wrapper.component.html):
<ng-container *ngIf="false">
<ng-content></ng-content>
</ng-container>
I always see the console.log() output. I guess, the my-component gets built and then stored away until the *ngIf becomes true inside my-wrapper.
The intention was to build a generic "list-item + detail" component. Say I have a list of N overview-elements (my-wrapper), that get rendered in a *ngFor loop. Every of those elements has its own detail component (my-component) that is supposed to load its own data, once I decide to show more infos to a specific item.
overview.html:
<ng-container *ngFor="let item of items">
<my-wrapper>
<my-component id="item.id"></my-component>
</my-wrapper>
</ng-container>
my-wrapper.component.html:
<div (click)="toggleDetail()">Click for more</div>
<div *ngIf="showDetail">
<ng-content></ng-content>
</div>
Is there a way to tell Angular, to ignore the transcluded content until it is necessary to be added to the page? Analogously to how it was in AngularJS.
Based on the comment of #nsinreal I found an answer. I find it to be a bit abstruse, so I'm trying to post it here:
The answer is to work with ng-template and *ngTemplateOutlet.
In the my-wrapper component, set up the template like this (my-wrapper.component.html):
<div (click)="toggleDetail()">Click for more</div>
<div *ngIf="showDetail" [hidden]="!isInitialized">
<ng-container *ngTemplateOutlet="detailRef"></ng-container>
</div>
Note, that the [hidden] there is not really necessary, it hides the "raw" template of the child until it decides it is done loading. Just make sure, not to put it in a *ngIf, otherwise the *ngTemplateOutlet will never get triggered, leading to nothing happening at all.
To set the detailRef, put this in the component code (my-wrapper.component.ts):
import { ContentChild, TemplateRef } from '#angular/core';
#Component({ ... })
export class MyWrapperComponent {
#ContentChild(TemplateRef) detailRef;
...
}
Now, you can use the wrapper like this:
<my-wrapper>
<ng-template>
<my-component></my-component>
</ng-template>
</my-wrapper>
I am not sure, why it needs such complicated "workarounds", when it used to be so easy to do this in AngularJS.
By doing this:
<my-wrapper *ngIf="false">
<my-component></my-component>
</my-wrapper>
You are not calling MyComponent component, because the *ngIf is false. that means, that not calling it you are not instancing it and, therefore, not passing through its ngOnInit. And that's why you are not getting the console log.
By doing this:
<ng-container *ngIf="false">
<ng-content></ng-content>
</ng-container>
You are inside the component, you are just limiting what to render in your template, but you already instanced your component and, therefore, you passed through your ngOnInit and you get your console log done.
If, you want to limit something (component call with selector or a ng-content or even a div) until you have some data available, you can do the following:
datasLoaded: Promise<boolean>;
this.getData().subscribe(
(data) => {
this.datasLoaded = Promise.resolve(true); // Setting the Promise as resolved after I have the needed data
}
);
And in your template:
<ng-container *ngIf="datasLoaded | async">
// stuff here
</ng-container>
Or:
<my-component *ngIf="datasLoaded | async">
// Didn't test this one, but should follow the same logic. If it doesn't, wrap it and add the ngIf to the wrapper
</my-component>
It’s because Ng content happens at the build time and when you pass the content it is actually not removed or recreated with the ngIf directive. It is only moved and the component is instantiated .
I encountered this problem recently as well but settled on a different solution than the currently accepted one.
Solution (TL;DR)
(Solution is for AngularDart; I figure it's similar in Angular though)
Use a structural directive; tutorials linked below.
Instead of:
<my-wrapper>
<my-contents></my-contents>
</my-wrapper>
your usage becomes:
<div *myWrapper>
<my-contents></my-contents>
</div>
which is shorthand for the following (in AngularDart; I think Angular uses <ng-template>)
<template myWrapper>
<div>
<my-contents></my-contents>
</div>
</template>
The MyWrapper directive logic is similar to NgIf except it has its own logic to compute the condition. Both of the following tutorials explain how to create an NgIf-like directive and how to pass it your own inputs using the special microsyntax (e.g. *myWrapper="myInput: expression"). Note that the microsyntax doesn't support outputs (#Output), but you can mimic an output by using an input that is a function.
Tutorial for Angular
Tutorial for AngularDart
Caveat: Since this is just a directive, it shouldn't do anything more complicated than instantiating a template ref at the appropriate time and maybe specifying some DI providers. For example, I would avoid trying to apply styles or instantiating a complex tree of components in the directive. If I wanted to create a list component, I would probably take the #ContentChild(TemplateRef) approach described in another answer; you would lose the asterisk shorthand for creating <template> but you would gain the full power of components.
My problem
My team owns an app that's part of a larger web application with other apps owned by other teams. Our components assume they can inject a MyAppConfiguration object, but this object can only be injected after it is loaded with an asynchronous request. In our app this is not a problem: we have a "shell" component that hides everything behind an ngIf until the configuration is loaded.
The problem is when other teams want to reference our components. We don't want them to duplicate the "wait until configuration is loaded" logic every time, so I tried creating a wrapper component that can be used like so:
<my-app-wrapper>
<my-app-component></my-app-component>
</my-app-wrapper>
The wrapper injects a service object and hides its contents behind an ngIf until the service says that the configuration is loaded.
Like the question poster, I discovered that the ng-content approach doesn't work as intended: while the contents are correctly hidden from the DOM, Angular still instantiates the components causing dependency injection to fail.
The solution that I settled on was to rewrite the wrapper component as a structural directive.