Ionic 3 : Why are some functions continuously being called? - performance

I have some utility method calls, that I issue console log messages inside of, whenever the methods are invoked via the template.
I'm attempting to implement a variant of an accordion component.
The screen also has a lot of ion-checkboxes, hierachically listed at various levels (root, cagetories, type within categories), and the components are part of a Reactive Form.
When the details are expanded the methods are getting called repeatedly, despite me performing no subsequent actions whatsoever.
It also seems to degrade the performance of the screen.
From this information, can anyone surmise what the cause might be?
Some sort of change detection gone wild?
There is an *ngIf that displays content when things are expanded, then a *ngFor occurs in the expanded list.
It appears to be the nested *ngFor within the *ngIf that is continuously calling out to utilitiy methods.
The getIndex() method, which appears to be the culprit is being used for two things:
<ion-checkbox [attr.id]="'type' + g + '-' + t"
[formControl]=
"jobTypes.controls[getIndex(jt, g, t)]"
(click)="toggleJobTypeCheck(getIndex(jt, g, t))"
class="col3">
</ion-checkbox>
g and t are for loop indices, jt is a Job Type, which has an interface structure like so:
export interface JobTypeMapping {
type: string,
alias?: string,
num: number,
name: string,
group: JobTypeGroup,
sel:boolean
}

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));
}
}

Second parameter in knockout applyBindings. Performance implications

After reading the documentation about the second parameter in knockout applyBindins, I understand the reason behind it:
Optionally, you can pass a second parameter to define which part of
the document you want to search for data-bind attributes. For example,
ko.applyBindings(myViewModel,
document.getElementById('someElementId')). This restricts the
activation to the element with ID someElementId and its descendants,
which is useful if you want to have multiple view models and associate
each with a different region of the page.
But I have not seen anything about performance. My thoughts (which are based on nothing) are that it makes sense that ko bindings will work faster if to restring the binding not to the whole document, but to a small part of it.
So does it make sense to use ko.applyBindings(myViewModel, $('#someElementId')[0]) without having multiple view models just for the sake of performance. (I heard about Mr. Knuth, so it would be nice to refrain from optimization citation).
applyBindings is a thin wrapper over applyBindingsToNodeAndDescendantsInternal (which is where ALL the magic happens) and if you don't supply a node it uses window.document.body
There is no difference between calling applyBindings(viewModel) and applyBindings(viewMode,rootNode) unless you have a very large DOM and only want to bind a small fraction. In this case use the second parameter and Knockout will have a lot less to scan on the initial setup.
Once the bindings are in place they are reacting locally to observable changes, unlike a framework like AngularJS that can potential scan the entire DOM for changes. The root node is irrelevant at this point.
ko.applyBindings = function (viewModel, rootNode) {
if (rootNode && (rootNode.nodeType !== 1) && (rootNode.nodeType !== 8))
throw new Error("ko.applyBindings: first parameter should be your view model; second parameter should be a DOM node");
rootNode = rootNode || window.document.body; // Make "rootNode" parameter optional
applyBindingsToNodeAndDescendantsInternal(viewModel, rootNode, true);
};

AngularDart custom filter call() method required to be idempotent?

The main running example of the Angular Dart tutorial is a Recipe Book app. The exercise at the end of the Chapter 5 on filters and services suggests trying to "create a [custom] filter that will multiply all the amounts [of each ingredient listed] in the recipes" thus allowing a "user to double, triple, or quadruple the recipe." E.g. an ingredient of "1/2 cup of flour" would become "1 cup of flour" when doubled.
I have written such a custom filter: it takes a list of Ingredients (consisting of a quantity and a description) and returns a new list of new Ingredients (with increased quantities), but I am getting the following error:
5 $digest() iterations reached. Aborting!
My question is: what is the required and/or permitted behavior of an AngularDart custom filter call() method? E.g., clearly it is permitted to remove (i.e. filter) elements from its input list, but can it also add new or replace elements? The Dart angular.core NgFilter documentation simply says that a "filter is a class with a call method". I have not found more details.
Extrapolating from the answer to this AngularJS post, it would seem that repeated invocations of call() should (eventually?) yield "the same result". If so, this would be a reasonable constraint.
Yielding "the same result" could mean that call() needs to be idempotent, but in the case of Dart such idempotence should be relative to == (object equivalence) not identical() (object identity), IMHO. I ran a few tests using the following small example to illustrate the issues:
main.dart
import 'package:angular/angular.dart';
class A { }
#NgFilter(name:'myFilter') class MutatingCustomFilter {
final A _a = new A();
call(List list) => new List.from(list)..add(_a); // runs ok.
// call(List list) => new List.from(list)..add(new A()); // gives error
}
class MyAppModule extends Module {
MyAppModule() { type(MutatingCustomFilter); }
}
main() => ngBootstrap(module: new MyAppModule());
index.html excerpt
<ul>
<li ng-repeat="x in [1,2,3] | myFilter">{{x}}</li>
</ul>
If I change the body of class A to be
#override bool operator==(other) => true;
#override int get hashCode => 1;
which makes all instances of A considered ==, then the second implementation of call() in main.dart (the one with add(new A())) still gives an error (though a different one).
I can see how to solve the tutorial exercise without use of a custom filter, but I am trying to not give up on the challenge of finding a filter that will work as requested. I am new to Angular and decided to jump in with AngularDart, so any help in explaining the effects of the various flavors of call(), or in finding documentation for the expected behavior of call(), (or letting me know if you think such a custom filter simply cannot be written!) would be appreciated.
Too many iterations
When angular detects a change in the model, it executes a reaction function. The reaction function can further change the model. This would leave the model in inconsistent state. For this reason we re-run the change detection, which can further create more changes. For this reason we keep re-running the changes until the model stabilizes. But how many times should we rerun the change detection before giving up? By default it is 5 times. If the model does not stabilize after 5 iteration we give up. This is what is going on in your case.
Change Detection
When has object changed? one can use identical or == (equals). Good arguments can be made for each, but we have chosen to use identical because it is fast and consistent. Using == (equals) is tricky and it would negatively impact the change detection algorithm.
Filters and arrays
When a filter which operates an an array, executes it has no choice but to create a new instance of the array. This breaks identical, but luckily it is fed into ng-repeat which uses its own algorithm for array contents detection rather the array detection. While the array does not have to be identical between runs, its content must be. Otherwise ng-repeat can not tell the difference between insertions and changes, which it needs to do proper animations.
Your code
The issue with your filter is that it creates new instance on each iteration of the digest loop. These new instances prevent the model from stabilizing and hence the error. (There are plans to solve this issue, but it will be few weeks before we get there.)
Solution
Your solutions is attempting to create a filter which consumes the whole array and then attempts to create a new array, for the ng-repeat. A different (prefered) solution would be to leave the ng-repeat iteration as is, and instead place the filter on the binding which is creating the qty and apply it there.
<span>{{recipe.qty | myFilter:multiply}}</span>

Using invariant with Dexterity form and fieldsets

I have a content type derived from plone.directives.form.Schema; it has several dozen fields across four fieldsets. I'm trying to create a zope.interface.invariant that looks at fields from two different fieldsets.
From tracing the behaviour, it looks like the invariant is called once for each fieldset, but not for the entire form.
I'm aware I can provide my own handler and perform all the checks I need there, but that feels chunky compared to distinctly defined invariants. While the obvious solution is to move related fields onto the same fieldset, the current setup reflects a layout that is logical the end user.
Is there an existing hook where I could perform validation on multiple fields across fieldsets?
The answer seems to be no: z3c.form.group.Group.extractData calls z3c.form.form.BaseForm.extractData once for each group/fieldset, and this call already includes invariant validation.
Instead of registering your own handler, you could also overwrite extractData:
from plone.directives import form, dexterity
from z3c.form.interfaces import ActionExecutionError,WidgetActionExecutionError
# ...
class EditForm(dexterity.EditForm):
grok.context(IMyEvent)
def extractData(self, setErrors=True):
data, errors = super(EditForm, self).extractData(setErrors)
if not None in(data['start'], data['end']):
if data['end'] < data['start']:
raise WidgetActionExecutionError('end', Invalid(_(u"End date should not lie before the start date.")))
if data['end'] - data['start'] > datetime.timedelta(days=7):
raise WidgetActionExecutionError('end', Invalid(_(u"Duration of convention should be shorter than seven (7) days.")))
return data, errors
Please note that this class derives from dexterity.EditForm, which includes Dexterity's default handlers, instead of form.SchemaForm.
WidgetActionExecutionError does not work reliably, though. For some fields, it produces a 'KeyError'.

How to create dynamic Callbacks in MATLAB?

I have this line of code:
delete_btn = uicontrol(rr_ops, 'Style', 'pushbutton', 'String', 'Delete Graphic', 'Position', [13 135 98 20], ...
'Callback', 'delete_graphic');
and a little bit upper this function:
function delete_graphic
global rr_list
selected = get(rr_list, 'Value');
selected
return;
why this code is not working? I really dont understand...
What do I need? I create one button and a listbox, clicking on button - deleting selected element from a listbox.
Thx for help.
PS
Always getting this error:
??? Undefined function or variable 'delete_graphic'.
??? Error while evaluating uicontrol Callback
here is all my code: http://paste.ubuntu.com/540094/ (line 185)
The generally-preferred way to define a callback function is to use a function handle instead of a string. When you use a string, the code in the string is evaluated in the base workspace. This means that all the variables and functions used in the string have to exist in the base workspace when the callback is evaluated. This makes for a poor GUI design, since you don't really want the operation of your GUI dependent on the base workspace (which the user can modify easily, thus potentially breaking your GUI).
This also explains the error you are getting. The function delete_graphic is defined as a subfunction in your file rr_intervals.m. Subfunctions can only be called by other functions defined in the same m-file, so delete_graphic is not visible in the base workspace (where your string callback is evaluated). Using a function handle callback is a better alternative. Here's how you would do it:
Change the callback of your button (line 216) from 'delete_graphic' to #delete_graphic.
Change the function definition of delete_graphic (line 185) to:
function delete_graphic(hObject,eventdata)
where hObject is the handle of the object issuing the callback and eventdata is optional data provided when the callback is issued.
EDIT:
If you want to pass other arguments to delete_graphic, you can perform the following steps:
Add the additional input arguments to the end of the function definition. For example:
function delete_graphic(hObject,eventdata,argA,argB)
Use a cell array when you set the callback for your button, where the first cell contains the function handle and the subsequent cells each contain an input argument. For example:
set(delete_btn,'Callback',{#delete_graphic,A,B});
There is one caveat to this, which is that the values A and B stored in the cell array are fixed at what they are when you set the callback. If you change A or B in your code it will not change the values stored in the cell-array callback.
If you aren't able to use the above solution (i.e. if A and B need to change value), there are a few other options for how you can share data among a GUI's callbacks:
You can rework the organization of your code to make use of nested functions. This makes it very easy to share data between callbacks. Some nice examples of using nested functions to create GUIs can be found in the MathWorks File Exchange submission GUI Examples using Nested Functions by Steven Lord.
You can store data in the UserData property of a uicontrol object. To access or update it, you just need the object handle.
You can use the functions SETAPPDATA/GETAPPDATA to attach data to a handle graphics object (i.e. uicontrol).
Since it appears your code was created using GUIDE, you can make use of the handles structure GUIDE creates to store data using the GUIDATA function.

Resources