AngularJS browser memory keeps climbing when adding a removing controller - memory-management

I've created a webapp using angular js and the routeProvider. I don't load partials inside ng-view but I highjack the routParam changes that it causes to create my own object indicating what part of the app is active see below:
lrApp.config(function ($routeProvider) {
$routeProvider
.when('/',{
redirectTo:'/share'
})
.when('/:action',{
controller: "viewController",
template: ' '
})
.when('/:action/:section',{
controller: "viewController",
template: ' '
})
.when('/:action/:section/:subsection',{
controller: "viewController",
template: ' '
})
.otherwise({
redirectTo:'/'
});
});
The empty spaces for template and including an empty in your body trigger the routeParams to change I then call viewController which is included on the body and includes a globally accessible object i.e. ng-show="url.section == 'contacts'"
So I use this global object to ng-show or ng-switch or ui-if certain parts of the app into place when applicable. This also allows me to create 3 levels of deep linking.
I'm creating a factory for each major section of the app that contains an empty object I then store the growing data for each controller inside of a factory so that when the controllers are reloaded via ng-switch the data is still there and I don't have to get it again...
MY ISSUE / QUESTIONS
Is this the appropriate way to be storing my data? or should I be coupling this with cacheFactory and storing that object in the cacheFactory and then returning the cacheFactory in the controller?
What's the difference between storing the data in cacheFactory and just storing the data in the factory and having it outside of the controller ... I'm confused?
When I do this and I switch to a different section using ng-switch the info is taken out of the DOM and when I switch back the data is put back in because it still stored in the factory BUT the memory it shows for my tab in chrome just keeps climbing higher and higher every time I switch back even though I'm not loading any new data... what am I doing wrong?
This is a rough sample of how I build my app... test app I built

I didn't understand enough about angularjs at the time but my first issue was that I was de-rendering and rendering large chunks of data out of the DOM and then back in and what I thought was a memory leak was really just the DOM memory climbing because the angular template engine was having to re-render templates.

Related

How To Query Through <slot> Using Cypress While Testing Web Components

After years of testing one global DOM for end-to-end testing, I'm finding it very difficult, if not impossible, to test web components that use slots. Before I explain the problem, I want to say that I cannot change the generated markup to improve things as they are.
<wc-1 attributes-etc="">
<wc-2 attributes-etc="">
<slot>
<wc-3 attributes-etc="">
<slot>
...eventually get to an input...
<input type="text" name="firstName" />
There are a buttload of nested web components from some kind of form builder, and there are also plenty of slots used. The web components have attributes but the slots never do, so I use the web component name for querying.
document.querSelector('wc-1')
.shadowRoot.querySelector('wc-2')
.shadowRoot.querySelector('slot')
// Yields <slot>...</slot>
All fine to this point and Cypress has a .shadow() command I used, but I'm testing with just devtools here to see all the properties the slot has.
document.querSelector('wc-1')
.shadowRoot.querySelector('wc-2')
.shadowRoot.querySelector('slot')
.shadowRoot
// Yields "null".
// I don't know how to get to the .lightDOM? of wc-2?
Any property I try ends up being null or having 0 elements in the returned value. Using other front-end tools and the global DOM, I can always cy.get('div[data-testid="the-nested-element-i-want"]').type('important words') in one command.
So my main question is: How do people test these things once web components start piling up? Or don't do this and just test the web components in isolation/unit tests since it's so hard to query the nested shadow DOMs?
The main goal is to eventually get to a form input to cy.get('input[name"firstName"]').type('John'). Can someone give me the chained docuement.querySelector() command to get to <wc-3> in my example?
The answer involves assignedNodes(): https://developer.mozilla.org/en-US/docs/Web/API/HTMLSlotElement/assignedNodes
The assignedNodes() property of the HTMLSlotElement interface returns a sequence of the nodes assigned to this slot...
It made no difference for me to use that vs. assignedElements(). So, all you have to do is use that method once you've queried down to the slot you need. For my example, the answer is:
const wc-3 = document.querySelector('wc-1').shadowRoot
.querySelector('wc-2').shadowRoot
.querySelector('slot').assignedNodes()
.map((el) => el.shadowRoot)[0]
And then you can keep going down the chain...I know I only have one un-named slot, so that's why I grab it from the returned .map().
Props to this Q&A for pointing me on the right direction: Web components: How to work with children?
There will be no DOM content in your <slot>, as there is no DOM content moved to slots.
lightDOM content is reflected in slots, but remains invisible! in lightDOM.
(that is why you also style slotted content in lightDOM)
From the docs:
๐˜พ๐™ค๐™ฃ๐™˜๐™š๐™ฅ๐™ฉ๐™ช๐™–๐™ก๐™ก๐™ฎ, ๐™™๐™ž๐™จ๐™ฉ๐™ง๐™ž๐™—๐™ช๐™ฉ๐™š๐™™ ๐™ฃ๐™ค๐™™๐™š๐™จ ๐™˜๐™–๐™ฃ ๐™จ๐™š๐™š๐™ข ๐™– ๐™—๐™ž๐™ฉ ๐™—๐™ž๐™ฏ๐™–๐™ง๐™ง๐™š.
๐™Ž๐™ก๐™ค๐™ฉ๐™จ ๐™™๐™ค๐™ฃ'๐™ฉ ๐™ฅ๐™๐™ฎ๐™จ๐™ž๐™˜๐™–๐™ก๐™ก๐™ฎ ๐™ข๐™ค๐™ซ๐™š ๐˜ฟ๐™Š๐™ˆ; ๐™ฉ๐™๐™š๐™ฎ ๐™ง๐™š๐™ฃ๐™™๐™š๐™ง ๐™ž๐™ฉ ๐™–๐™ฉ ๐™–๐™ฃ๐™ค๐™ฉ๐™๐™š๐™ง ๐™ก๐™ค๐™˜๐™–๐™ฉ๐™ž๐™ค๐™ฃ ๐™ž๐™ฃ๐™จ๐™ž๐™™๐™š ๐™ฉ๐™๐™š ๐™จ๐™๐™–๐™™๐™ค๐™ฌ ๐˜ฟ๐™Š๐™ˆ.
So to test if something is "in" a slot
you need to check for slot=? attributes on lightDOM elements
and double check if that <slot name=? > actually exists in shadowDOM
Or vice versa
Or hook into the slotchange Event, but that is not Testing
pseudo code:
for the vice-versa approach; can contain errors.. its pseudo code..
function processDOMnode( node ){
if (node.shadowRoot){
// query shadowDOM
let slotnames = [...node.shadowRoot.querySelectorAll("slot")].map(s=>s.name);
// query lightDOM
slotnames.forEach( name =>{
let content = node.querySelectorAll(`[slot="${name}"]`);
console.log( "slot:" , name , "content:" , content );
});
// maybe do something with slotnames in lightDOM that do NOT exist in shadowDOM
// dive deeper
this.shadowRooot.children.forEach(shadownode => processDOMnode(shadownode));
}
}

How to overwrite global event handler in nested Vue component, and later use the previous one?

I am working in Vue and also i use VueRouter, VueX and VueWebsocket. My App has component called App which holds all other components inside itself. Also I have websocket event which is set globally like this:
this.$options.sockets.onmessage = (websocket) => { /* sth1 */ }
When it gets any data from websocket, sth1 is called. it works like charm. However deep inside App component is another component, let's call it InputComponent. It may be included in App or not becaue it is single page aplication and some parts do include InputComponent, and some do not. Inside InputComponent there is also:
this.$options.sockets.onmessage = (websocket) => { /* sth2 */ }
And of course it overwrites function on message so sth1 will never be executed if InputComponent is nested by App component. It is quite obvious. However if i remove (in next SPA page), and InputComponent disappears i still have my onmessage event overwritten which i would like to have in original version.
I could ofcourse make some kind of merging functionalities of sth1 and sth2 in App component or InputComponent but it is repeating myself.
Here comes the question - is there a way to return original version of onmessage event without reloading whole App Component? In other words: can i have temporary overwritten function and then come back to its functionalities? Something like extending an eent with new functionalities of sth2.
I hope you get the idea!
K.
The general way to do that would be to use addEventListener and removeEventListener. So in the input component
created() {
this.$options.sockets.addEventListener('message', handleMessage);
},
destroyed() {
this.$options.sockets.removeEventListener('message', handleMessage);
}
Note that this approach doesn't prevent the original handler from also receiving the events. Without knowing more about the app architecture, it's hard to suggest the best way to avoid this behavior, but perhaps you can set a messageHandled flag on the event in the component's handler; then check that flag in the parent.

Coldfusion Coldbox - create and cache drop down options from queries

I am looking for opinions and alternate ideas, as what I am doing is working, but wanted to ask if it was optimal
I have a site that when the index handler is called, it populates the request collection with specific queries from database tables so that I can build drop downs for the user to select.
i am querying two models and putting their results in their respective variables, then loop thru them in the view to create the drop down
index Handler
function index(event, rc, prc){
event.paramValue("debug",0);
rc.stages = getmodel("tms_proposal_stage").select();
rc.plannedGiftTypes = getmodel("tms_funding_type").select();
event.setLayout('layout.bootstrap');
}
index view
<div class="form-group">
<label for="proposal_stage" class="control-label">Proposal Stage Code</label>
<select id="proposal_stage" name="proposal_stage" class="form-control">
<cfloop query="rc.stages">
<option value="#stage_code#">#short_desc#</option>
</cfloop>
</select>
</div>
I understand that two queries isnt that costly, but if I needed to run 100 of these that would have scalability issues. These query result sets do not change much, so I was thinking, shouldnt these be cached or stored and accessed a different way?
I thought about html5 local storage, which I have used but not in this regard.
I also considered making a new handler function that makes all of these database calls and is cached, then referenced by other functions
anyways, all thoughts are appreciated
You have several options available to you. Since you're using ColdBox, you have CacheBox readily available to you.
https://github.com/ColdBox/cbox-refcards/raw/master/CacheBox/CacheBox-Refcard.pdf
A very simple way to do inline caching of the data would be to inject a CacheBox provider at the top of your component:
component {
property name="cache" inject="cachebox:default";
}
Then use the cache API in your event to store and retrieve data. My favorite method is getOrSet() because it wraps it up in a single call.
rc.stages = cache.getOrSet(
objectKey="stages",
produce=function(){
return getmodel("tms_proposal_stage").select();
}
);
The closure is only executed if the key is not already in the cache.
http://wiki.coldbox.org/wiki/WhatsNew:CacheBox-1.6.cfm#Get_Or_Set_Baby
Another approach is to cache the full HTML. To do this, create a viewlet that only outputs the HTML for your form control. Create an event that returns the output of just that view like so:
function stagesFormInput(event, rc, prc) cache=true {
var stagesData = getmodel("tms_proposal_stage").select();
return renderView(view="viewlets/stages", args={ stagesData : stagesData } );
}
Note, I'm passing stageData directly into the view so it doesn't pollute the rc or prc. This data will be available in your viewlet as "args.stagesData".
Also note the "cache=true" in the method declaration. That's the magic that will tell ColdBox to cache this event (inside CacheBox's "template" provider"). You can specify a timeout, but this will use the default. Now, enabled eventCaching in your /config/ColdBox.cfc file.
coldbox={
eventCaching = true
}
http://wiki.coldbox.org/wiki/ConfigurationCFC.cfm#Application_Aspects
And finally, in your main view or layout, just run your new viewlet everywhere you want the cached HTML to be output.
#runEvent("viewlets.stagesFormInput")#
This is a little bit more setup, but is more powerful since it caches the full HTML snippet which is really ideal. I also have an entire sample app that demos this inside a working app. You can check it out here:
https://github.com/bdw429s/ColdBox-Viewlet-Sample

What's the scope of definition of "persistent state" in EmberJS?

It may sound like a pedantic question. Sorry :)
I have a case like this... here's my router definition:
Router.map(function() {
this.resource('gobernadores', { path: '/gobernadores' }, function() {
this.resource('gobernador', { path: '/:id_estado' }, function() {
this.route('simulacion', { path: '/simulacion' }),
this.route('info', { path: '/info' })
})
});
this.route("login");
this.route("bienvenido");
});
In the "gobernadores" route, I have list of provinces. You can see it's a nested layout. In that same page, we're showing the currently-selected province (that's the gobernador route). Inside the template for that gobernador route, I have a tab, with two elements..., one showing the route "simulacion", and the other one showing the template of route "info" (of that province).
Now, the problem: as user jumps from one province to another province (by clicking the navigation menu on the left side of the screen), I want to keep in memory, the tab that was currently selected, for each province.
So, if the user is currently seeing the result of simulacion of province X, and then he clicks on the link to go to province Y (where he will be presented with "info" of province Y), and then he goes back to province X, I want the app to take the user back to the screen he was seeing (the simulacion of province X).
You can't have that information stored in the controller (GobernadorController), because I can see that controllers can't keep state, it's stateless.
So..., I have to move that info into the model of the route (GobernadorRouteModel)...
My doubt: is it okay? Why my doubt? Because of this: http://emberjs.com/guides/concepts/core-concepts/
It says:
MODELS
A model is an object that stores persistent state. Templates are
responsible for displaying the model to the user by turning it into
HTML. In many applications, models are loaded via an HTTP JSON API,
although Ember is agnostic to the backend that you choose.
ROUTE
A route is an object that tells the template which model it should
display.
This GobernadorRouteModel is not something I persists in the backend. I have no intention to do that. So, am I violating the general advice for a good EmberJS app?
Or in other words: "persistent" here doesn't have to mean "something you save into DB", right? It's just "something you want to keep around..., eventhough only during the session of the app, in the memory".
Thanks in advance,
Raka
You can't have that information stored in the controller (GobernadorController), because I can see that controllers can't keep state, it's stateless.
This might be where your problem arises. Controllers are not stateless. Controllers in Ember are singletons and keep their state throughout the lifecycle of the app. However, this is going to change in Ember 2.0. To quote from that RFC:
Persistent state should be stored in route objects and passed as initial properties to routable components.
So if you're trying to be forward-compatible, that is the approach I would take. In my opinion, models should really only be used for persistent state (persistent meaning it's persisted between page loads). To keep session state, I would do as the RFC says and keep that state in the routes and inject it into the controllers during the resetController hook.
Or if you don't want to be that fancy and you don't care about forward-compatibility, just have a global Session object that you store state in. That's how I currently do it and it works quite well. (Although we will probably move away from it.)
TL;DR: No, I don't think you're using models for their intended purpose.

Routing Conventions in Can.js

So Iโ€™m looking to make some routes within my super cool can.js application. Aiming for something like thisโ€ฆ
#!claims ClaimsController - lists claims
#!claims/:id ClaimController - views a single claim
#!claims/new ClaimController - creates a new claim
#!claims/:id/pdf - do nothing, the ClaimController will handle it
#!admin AdminController - loads my Administrative panel with menu
#!admin/users - do nothing, the AdminController will handle it
#!admin/settings - do nothing, the AdminController will handle it
So how might we do this?
โ€œclaims routeโ€: function() { load('ClaimsController'); },
โ€œclaims/:id routeโ€: function() { load('ClaimController'); },
โ€œadminโ€: function() { load(โ€˜AdminControllerโ€™); },
Cool beans, weโ€™re off. So what if someone sends a link to someone like...
http://myapp#!claims/1/pdf
Nothing happens! Ok, well letโ€™s add the route.
โ€œclaims/:id/pdf routeโ€: function() { load('ClaimController'); },
Great. Now that link works. Here, the routerโ€™s job is only to load the controller. The controller will recognize that the pdf action is wanted, and show the correct view.
So pretend Iโ€™ve loaded up a claim claims/:id and I edit one or two things. Then I click the Print Preview button to view the PDF and change my route to claims/:id/pdf.
What should happenโ€ฆ the Claim Controller is watching the route and shows the pdf view.
What actually happensโ€ฆ the router sees the change, matches the claims/:id/pdf route we added, and reloads the Claim Controller, displaying a fresh version of the claim pulled from the server/cache, losing my changes.
To try and define the problem, I need the router to identify when the route changes, what controller the route belongs to, and if the controller is already loaded, ignore it. But this is hard!
claims //
claims/:id // different controllers!
claims/:id //
claims/:id/pdf // same controller!
We could just bind on the "controller" change. So defining routes like can.route(':controller') and binding on :controller.
{can.route} controller
// or
can.route.bind('controller', function() {...})
But clicking on a claim (changing from ClaimsController to ClaimController) won't trigger, as the first token claim is the same in both cases.
Is there a convention I can lean on? Should I be specifying every single route in the app and checking if the controller is loaded? Are my preferred route urls just not working?
The following is how I setup routing in complex CanJS applications. You can see an example of this here.
First, do not use can.Control routes. It's an anti-pattern and will be removed in 3.0 for something like the ideas in this issue.
Instead you setup a routing app module that imports and sets up modules by convention similar to this which is used here.
I will explain how to setup a routing app module in a moment. But first, it's important to understand how can.route is different from how you are probably used to thinking of routing. Its difference makes it difficult to understand at first, but once you get it; you'll hopefully see how powerful and perfect it is for client-side routing.
Instead of thinking of urls, think of can.route's data. What is in can.route.attr(). For example, your URLs seem to have data like:
page - the primary area someone is dealing with
subpage - an optional secondary area within the page
id - the id of a type
For example, admin/users might want can.route.attr() to return:
{page: "admin", subpage: "users"}
And, claims/5 might translate into:
{page: "claims", id: "5"}
When I start building an application, I only use urls that look like #!page=admin&subpage=users and ignore the pretty routing until later. I build an application around state first and foremost.
Once I have the mental picture of the can.route.attr() data that encapsulates my application's state, I build a routing app module that listens to changes in can.route and sets up the right controls or components. Yours might look like:
can.route.bind("change", throttle(function(){
if( can.route.attr("page") == "admin" ) {
load("AdminController")
} else if(can.route.attr("page") === "claims" && can.route.attr("id") {
load("ClaimController")
} else if ( ... ) {
...
} else {
// by convention, load a controller for whatever page is
load(can.capitalize(can.route.attr("page")+"Controller")
}
}) );
Finally, after setting all of that up, I make my pretty routes map to my expected can.route.attr() values:
can.route(":page"); // for #!claims, #!admin
can.route("claims/new", {page: "claims", subpage: "new"});
can.route("claims/:id", {page: "claims"});
can.route("admin/:subpage",{page: "admin"});
By doing it this way, you keep your routes independent of rest of the application. Everything simply listens to changes in can.route's attributes. All your routing rules are maintained in one place.

Resources