Correct way to scroll to dynamic angular page - html

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.

Related

Angular infinite scrolling strategy with IntersectionObserver

I've been reading a lot about infinite scrolling strategies as I need to implement this functionality in my component. However, there seems to exist a lot of different approaches and the examples I've seen are not the most straight forward and easy to understand.
To put it briefly, this component consists of 3 mat-tabs (each represent a different order status), and inside each one of these there is a list of order components for that corresponding order status.
The smart parent component looks something like:
<mat-tab-group dynamicHeight animationDuration="400ms">
<mat-tab>
<app-orders-list
[orders]="loadedPendingOrders"
[timelines]="pendingOrderTimelines"
[isDeliverySlotsActive]="isDeliverySlotsActive"
[searchTerm]="searchTerm"
></app-orders-list>
</mat-tab>
...
/* more tabs */
</mat-tab-group>
And the child app-orders-list looks like this (each section contains orders):
<div class="list-container" [ngClass]="{section: isDeliverySlotsActive === false}">
<div [ngClass]="{section: isDeliverySlotsActive === true}" *ngFor="let date of timelines">
<app-orders-list-section
[orders]="orders"
[timeline]="date"
[isDeliverySlotsActive]="isDeliverySlotsActive"
[searchTerm]="searchTerm"
></app-orders-list-section>
</div>
</div>
When I reach the bottom of this list on a scroll down event, I need to load more orders from the API and present them accordingly.
Given this architecture, what strategy do you recommend for infinite scrolling? I'm mainly looking for something like a directive that implements the IntersectionObserver API and triggers an event when the bottom of the list is reached.
I'm not interested in solutions that use ngx-infinite-scrolling.
Thank you in advance.

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

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

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.

Angular sub-navigation based on route

I'm building an Angular application with two levels of navigation in the header.
The top-level navigation are visible on all routes in the application, however the second-level navigation is context-specific and only exists on certain routes.
For example, when viewing a specific course in a university webapp, the second-level navigation might have links to description, prerequisites, the different subjects, etc.
One way of doing it is creating a subnav component that has a switch statement and renders the correct subnav component based on the route:
<div [ngSwitch]="currentRoute">
<course-sub-nav *ngSwitchCase="course"></course-sub-nav>
<students-sub-nav *ngSwitchCase="students">...</students-sub-nav>
<support-sub-nav *ngSwitchCase="support">...</support-sub-nave>
</div>
I don't like that solution because now the sub-nav component knows too much about all the different context-specific sub-navs.
Another approach is to use a service to manage it. i.e. having a SubNavService.setNavigationLinks(links: NavLink[]) that takes an array of links. The sub-nav component can then listen to changes to the SubNavService and dynamically render a list of sublinks.
This solution is a bit cleaner however it does mean that I need to call that service on ever top-level component to ensure that the sub-nav is being updated. I'd also need to clear the links on pages that don't require sub-navs.
A third solution might be doing something like:
<!-- In header.component.html -->
<div class="sub-nav">
<ng-content="subnav"></ng-content>
</div>
<!-- IN course-page.component.html -->
<div>
<subnav>
<!-- my nav links -->
</subnav>
<div class="course-content">
<router-outlet></router-outlet>
</div>
</div>
I like this approach the most given that if a subnav component exists, the subnav will be rendered, and if not, nothing will show. Additionally, the relevant component has full control over rendering the links within the subnav. however given that course-page isn't actually a child of the header, I'm not sure how I can make it work.
I was wondering if there's any way to get the last approach to work. If not, would the second approach be the most pragmatic solution to this problem or is there a cleaner solution that exists?
The cleanest solution would be the second you mentioned.
Having a service that would handle the menu - menu component would subscribe and listen for changes, meanwhile other components would handle what items they need to have in the menu.

shrink html help

I have an array of 2000 items, that I need to display in html - each of the items is placed into a div. Now each of the items can have 6 links to click on for further action. Here is how a single item currently looks:
<div class='b'>
<div class='r'>
<span id='l1' onclick='doSomething(itemId, linkId);'>1</span>
<span id='l2' onclick='doSomething(itemId, linkId);'>2</span>
<span id='l3' onclick='doSomething(itemId, linkId);'>3</span>
<span id='l4' onclick='doSomething(itemId, linkId);'>4</span>
<span id='l5' onclick='doSomething(itemId, linkId);'>5</span>
<span id='l6' onclick='doSomething(itemId, linkId);'>6</span>
</div>
<div class='c'>
some item text
</div>
</div>
Now the problem is with the performance. I am using innerHTML to set the items into a master div on the page. The more html my "single item" contains the longer the DOM takes to add it. I am now trying to reduce the HTML to make it small as possible. Is there a way to render the span's differently without me having to use a single span for each of them? Maybe using jQuery?
First thing you should be doing is attaching the onclick event to the DIV via jQuery or some other framework and let it bubble down so that you can use doSomething to cover all cases and depending on which element you clicked on, you could extract the item ID and link ID. Also do the spans really need IDs? I don't know based on your sample code. Also, maybe instead of loading the link and item IDs on page load, get them via AJAX on a as you need them basis.
My two cents while eating salad for lunch,
nickyt
Update off the top of my head for vikasde . Syntax of this might not be entirely correct. I'm on lunch break.
$(".b").bind( // the class of your div, use an ID , e.g. #someID if you have more than one element with class b
"click",
function(e) { // e is the event object
// do something with $(e.target), like check if it's one of your links and then do something with it.
}
);
If you set the InnerHtml property of a node, the DOM has to interpret your HTML text and convert it into nodes. Essentially, you're running a language interpreter here. More text, more processing time. I suspect (but am not sure) that it would be faster to create actual DOM element nodes, with all requisite nesting of contents, and hook those to the containing node. Your "InnerHTML" solution is doing the same thing under the covers but also the additional work of making sense of your text.
I also second the suggestion of someone else who said it might be more economical to build all this content on the server rather than in the client via JS.
Finally, I think you can eliminate much of the content of your spans. You don't need an ID, you don't need arguments in your onclick(). Call a JS function which will figure out which node it's called from, go up one node to find the containing div and perhaps loop down the contained nodes and/or look at the text to figure out which item within a div it should be responding to. You can make the onclick handler do a whole lot of work - this work only gets done once, at mouse click time, and will not be multiplied by 2000x something. It will not take a perceptible amount of user time.
John Resig wrote a blog on documentDragments http://ejohn.org/blog/dom-documentfragments/
My suggestion is to create a documentDragment for each row and append that to the DOM as you create it. A timeout wrapping each appendChild may help if there is any hanging from the browser
function addRow(row) {
var fragment = document.createDocumentFragment();
var div = document.createElement('div');
div.addAttribute('class', 'b');
fragment.appendChild(div);
div.innerHtml = "<div>what ever you want in each row</div>";
// setting a timeout of zero will allow the browser to intersperse the action of attaching to the dom with other things so that the delay isn't so noticable
window.setTimeout(function() {
document.body.appendChild(div);
}, 0);
};
hope that helps
One other problem is that there's too much stuff on the page for your browser to handle gracefully. I'm not sure if the page's design permits this, but how about putting those 2000 lines into a DIV with a fixed size and overflow: auto so the user gets a scrollable window in the page?
It's not what I'd prefer as a user, but if it fixes the cursor weirdness it might be an acceptable workaround.
Yet Another Solution
...to the "too much stuff on the page" problem:
(please let me know when you get sick and tired of these suggestions!)
If you have the option of using an embedded object, say a Java Applet (my personal preference but most people won't touch it) or JavaFX or Flash or Silverlight or...
then you could display all that funky data in that technology, embedded into your browser page. The contents of the page wouldn't be any of the browser's business and hence it wouldn't choke up on you.
Apart from the load time for Java or whatever, this could be transparent and invisible to the user, i.e. it's (almost) possible to do this so the text appears to be displayed on the page just as if it were directly in the HTML.