I have a Vue component that displays information for a log entry. On my page, I have a for loop that instantiates this component for each log entry. I could have anywhere from 0 - potentially 100+ logs.
<log-entry v-for="(log, index) in logs) :key=index :log="log" />
I have a mixin that contains some methods that can be used all over my application such as a method that looks up a user by their ID.
$_myAppMixin_lookupUserById(id, users) {
// return user with matching id
}
My question is, what kind of performance hit will my app take if I use the mixin in the log-entry component?
I'm thinking that if my log-entry component is instantiated 50 times on a page, my app is loading the mixin 50 times. I'm wondering though, is Vue able to reconcile all the duplicate code?
Related
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));
}
}
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.
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.
I'm building a Durandal SPA that may benefit from a cacheViews setting of true or false, depending on the individuals usage of the app.
This is how I currently have it set in shell.html:
<div class="page-host" data-bind="router: { cacheViews:true }"></div>
And in a child router like in samples/index.html:
<div>
<!--ko router: { cacheViews:false }--><!--/ko-->
</div>
1) Can these be changed at runtime? If so, how?. Does it matter that one is using data-bind and the other is using ko comment style?
2) How granular is this value? Is it per "router" or per "route"? As you can see, I have a parent router and a child router, so there are 2 places in my html code where I can set cacheViews. From my testing, it appears as if they are independent of each other. Can anyone confirm? Can I set this value on individual routes like /#page1, /#page2, /#samples/list, etc?
3) Because the page event life-cycle is different between true/false I need to have some specific logic in my vm depending on this value. How can I retrieve it for the current route?
Thanks
1) Can these be changed at runtime? If so, how?
With any knockout binding, the binding is recomputed if the bound value triggers subscriptions. That means cacheViews can be changed at runtime if it is an observable.
I'm not sure if the right way to do this is ko.observable({ cacheViews: false }) or { cacheViews: ko.observable(false) }. In fact, I believe which one will work is somewhat dependent on the version of knockout you're running, but one of them will work.
2) Does it matter that one is using data-bind and the other is using
ko comment style?
No.
3) How granular is this value? Is it per "router" or per "route"?
Per router. More specifically, per binding.
4) Can I set this value on individual routes like /#page1, /#page2,
/#samples/list, etc?
There's nothing out of the box that lets you set cacheViews per route. cacheViews is processed in the compose binding, which is being called under the hood in the router and so you would need to hook in there. However, Durandal is excellent about exposing hooks at every part of the lifecycle for custom logic. I'm sure with a little digging you can come up with your own customization to handle this.
5) Because the page event life-cycle is different between true/false I
need to have some specific logic in my vm depending on this value. How
can I retrieve it for the current route?
You would want to create the observable from (1) in your viewModel and have your associated view read it, like any other observable in knockout. Then, you will be able to access the current value directly off the viewModel. For example:
viewModel
viewModel = {
settings: ko.observable({
cacheViews: false
})
}
view
<!-- ko router: settings -->
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.