if:true comparison inside the for loop - salesforce

I want to do comparison in the if:true part inside for:each loop.
The answer I got from google is to create a new sub-component. But I really don't wanna go this route... Any other good options?
<template for:each={listData} for:item="obj">
<template if:true={obj.Id == CurrentId}>
</template>

Documentation
if:true|false={expression}
Use this directive to conditionally render DOM elements in a template.
The expression can be a JavaScript identifier (for example, person) or dot notation that accesses a property from an object (person.firstName). The engine doesn’t allow computed expressions (person[2].name['John']). To compute the value of expression, use a getter in the JavaScript class.
You can't use these kind of expression, but you can add to your object a property or a getter that evaluates to a boolean. Something like this:
this.listData = this.listData.map((elem) => {
const self = this;
return {
...elem,
get isCurrent() {
return this.Id == self.CurrentId;
}
};
});
Then you can use <template if:true={obj.isCurrent}>.

Related

Using array.prototype.some inside ngIf

I am developing an angular app using the 8th version. Inside the ngIf expression, I want to check something existanse inside array. So I have written the expression below:
*ngIf="questionniare.factors.some(item => item.intensities.length > 0)"
But now I get this error inside the console window:
Parser Error: Bindings cannot contain assignments at column 34 in
[questionniare.factors.some(item => item.intensities.length > 0)]
But as see, I don't have any assignment in my condition. So what's the problem and how can I fix it?
(I know that I can define a method and do this job inside that method, but I want to know if this is a limitation on ngIf that I should consider next times?)
The error message mentions an "assignment" but the problem is that you are creating an arrow function inside of the component template, which is not allowed. A feature request has been posted on GitHub, asking to support the creation of these functions in Angular templates.
In order to use Array.prototype.some in the template, you would have to define the predicate in the component code:
// You can define the predicate as a method
public itemHasIntensities(item): boolean {
return item => item.intensities.length > 0;
}
// You can also define the predicate as an arrow function
public itemHasIntensities = (item): boolean => item.intensities.length > 0;
and pass that function as an argument to Array.prototype.some in the template:
*ngIf="questionniare.factors.some(itemHasIntensities)"
This stackblitz is similar to your original code and gives the same error. This other stackblitz shows the same example with the callback function defined in the component code.
That being said, and as mentioned in the question, the simplest solution is to evaluate the whole condition in a component method:
public showElement(): boolean {
return this.questionniare.factors.some(item => item.intensities.length > 0);
}
*ngIf="showElement()"
Note: memoizing the function would be recommended to avoid performance issues.
The problem is that your binding is iterating over the list and tracking a value that is being assigned in the background.
There are a couple solutions to this, first is putting your logic inside a public method on the component class that does this, which is the slower of the two solutions because every time change detection runs it will check your array value.
A better solution is to update a value on the component whenever the array changes
You can do that like this:
#Component({
selector: 'my-component',
templateUrl: 'my-component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MyComponent {
#Input()
set questionaire(value: Questionaire) {
this.questionaire$.next(value);
}
readonly questionaire$ = new ReplaySubject<Questionaire>(1);
readonly hasIntensities$ = this.questionaire$.pipe(
map(questionaire => questionniare.factors.some(item => item.intensities.length > 0))
);
};
Then in the template you can do something like this:
*ngIf="hasIntensities$ | async"
You could also accomplish it via change detector ref and ngOnChanges, but this should be the most efficient method
This is not written in Angular Documentation but looks like you can't use Array.some in structural directives and you should move this logic into specific function inside the compnent.

angular design a compare directive

I'm planning to write a angular directive that will compare two object (primitive type and object type).
my plan is:
write a primitiveCompare directive that compares primitive type and return something like this:
<primitiveCompare left="" right="" settings=""></primitiveCompare>
write a objectCompare directive that compares object type and return something similar:
<objectCompare left="" right="" settings=""></objectCompare>
inside objectCompare directive, I'm thinking to loop the object's keys and create a template dynamically, it would be something like:
Object.keys(left).forEach(function(key){
if(typeOf key !== 'object'){
html = html + '<primitiveCompare left='left[key]' right='right[key] settings=''> </primitiveCompare>
else html = html + '<primitiveObject left='left[key]' right='right[key] settings=''> </primitiveObject>
Would above design work well, is no, can you point out better solutions?
Thanks in advanced!

Detect array in angular template

I have an array that I am iterating through and displaying. Sometimes, however, the item it finds is an array. I'd like to do something different with this, using an ng-if. I am unsure how to do this easily. It seems like a common issue but there doesn't appear to be an easy solution.
You can use Angular.isArray() (see doc) but it does not exist as a already defined filter so you might need to define your own.
Something like that :
angular.module('...', []).filter('isArray', function() {
return function (input) {
return angular.isArray(input);
};
});
And then in your template usimply use {{ myVar | isArray }}.
My only concerne is ... is it really clean to do so ? I don't know but this will solve your problem.
By the way, was already asked on StakcOverFlow : Angular expression to check if model is array or object

What does the "type" attribute of a directive do?

What does the "type" attribute of a directive do? I can't seem to find it documented.
Here's an example of its usage:
https://github.com/crudbetter/angular-charts/blob/master/src/piechart.js#L81
Does that usage just create an unused attribute, or does it actually have some sort of meaning?
The type attribute governs how the template is wrapped. Specifically, if it is svg or math it is wrapped in a div and a sub element of the type. Here is the function that leverages it inside the $compile service where the directive API lives from the source:
function wrapTemplate(type, template) {
type = lowercase(type || 'html');
switch (type) {
case 'svg':
case 'math':
var wrapper = document.createElement('div');
wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>';
return wrapper.childNodes[0].childNodes;
default:
return template;
}
}
It's now known as templateNamespace, and described as such in the docs.
As far as the AngularJS Directive Definition Object documentation goes, there is no mention to a "type" attribute whatsoever.
In the source you provided, they use the value 'SVG'. The only related attribute would be templateNamespace which corresponds to the document type used by the markup in the template.
AngularJS needs this information as those elements need to be created and cloned in a special way when they are defined outside their usual containers like <svg> and <math>.
Edit:
Well, I tried finding out if type works like templateNamespace but couldn't make any of them work (tried with Angular v1.3.2 and v1.2.1).
here's the fiddle

Reference to HTML elements in view, a convention?

I'm currently in the progress of learning Backbone.js and I'm using the book Developping Backbone Applications.
I have a questions about the reference to HTML elements and how they are stored. For example:
initialize: function() {
this.$input = this.$('#new-todo');
Here the HTML element with ID to-do is stored in the this.$input, why do we use the $ in front of input, is this merely a convention? If I change this.$input to this.input my code works fine. I find this confusing because the book states:
The view.$el property is equivalent to $(view.el) and view.$(selector) is equivalent to $(view.el).find(selector).
I would think that $(view.el) does something completely different than (view.el).
How is this.$input saved in Backbone.js? If I console.log it, it produces:
Object[input#new-todo property value = "" attribute value = "null"]
Could someone give me some insight? :)
Using $ infront of a variable name is just a naming convention. It helps developer in distinguishing variable holding jQuery objects from others.
view.$el is a helper variable provided by Backbone, so that we can use it directly, instead of explicitly forming the jQuery object. Hence view.$el is equivalent to $(view.el).
view.$el is assigned in setElement method:
setElement: function(element, delegate) {
// Some code
this.$el = element instanceof Backbone.$ ? element : Backbone.$(element);
// Some code
}
Backbone.$ is reference to $ global variable exported by jQuery.
view.$(selector) is a method defined in View. It's definition does exactly same as $(view.el).find(selector)
$: function(selector) {
return this.$el.find(selector);
}

Resources