Using array.prototype.some inside ngIf - arrays

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.

Related

if:true comparison inside the for loop

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}>.

AngularJS anonymous function as argument in interpolation

In my controller I have a function that takes another function as an argument, something like:
$scope.myFun = (values, selector) => {
values.forEach((value) => {
selector(value);
}
}
Can I call this from my HTML using interpolation? I've tried:
{{ myFun(myVar, function(var) {return var/2;}) }}
unfortunatelly to no success, the syntax in the interpolation is not valid.
Your code can be simply reorganized to the following:
$scope.myFun = (values) => {
values.forEach((value) => {
value = value / 2;
}
}
And you should think this way - do not spread application logic through the all layers, better keep it one place - in controller or, better, in service. This way it can be covered with tests and much easier for another developer, or sometimes even you, to understand what's going on in the code.
Use templates just to interact with user and avoid putting additional complexity into html files.
What you are trying to do is not valid in angularJs. Based on your .forEach, you are looping through items and executing a function. This could be ok on the controller if you fixed a few issues, but not directly from template. The calculation function (item/2) itself has to be somewhere in angularjs, like your controller or a service.
Also, you are not returning anything from your calculation, so even if you did fix the above, you still need to return the result.
Refactor as such:
<div ng-repeat="val in [2,4,6]">
{{ $ctrl.myFun2(val, $ctrl.calc ) }}
</div>
And on controller (es6)
myFun2(value, selector) {
return selector(value);
}
calc(val) {
return val / 2;
}

What bind(this) is actually doing in ReactJS?

this.something = this.something.bind(this)
What is the above line actually doing? I am a newbie so please give a elementary level of explanation(for my understanding) also in a technical way
What is the above line actually doing?
Use of the JavaScript 'bind' method
bind creates a new function that will have this set to the first parameter passed to bind().
This is necessary because in the DOM library and many parts of JavaScript the hidden/implicit this function parameter is changed to point to different objects behind-the-scenes.
A good example concerns event-handlers in JavaScript, where the this parameter is not what it seems:
HTML:
<button id="someButton" name="Bar">Hello</button>
JavaScript (run after DOM is loaded):
function Foo() {
this.name = "Foo";
}
Foo.prototype.eventHandler( event ) {
console.log( event.type ); // will always print "click"
console.log( this.name ); // will print either "Foo" or "Bar"
}
var button = document.getElementById("someButton"); // HTMLButton
var fooInstance = new Foo(); // fooInstance.name == "Foo"
button.addEventListener( 'click', fooInstance.eventHandler );
If you run this code and click the button and set a breakpoint in Foo.prototype.eventHandler then you'll see that this.name is "Bar" and not "Foo" - even though you passed in a reference to fooInstance.eventHandler which surely knows about fooInstance when invoked?
It doesn't.
This is because the DOM API changes the this of fooInstance.eventHandler to be the button instance instead. I don't know the exact reason, but I believe it's related to maintaining backwards compatibility with old-school HTML-attribute based JavaScript event-handlers like these:
<button onclick="alert(this.name)" name="Baz">Click me</button>
(Where this refers to the containing HTMLElement)
So using .bind overrides the library's changing of this. You might think that as .bind(this) returns another Function that the this parameter will be changed anyway, but in fact, it doesn't. This is because the Function returned actually cannot have its this member changed at all, unlike most Function objects.
In ReactJS:
The use of foo = foo.bind(this) isn't anything unique to ReactJS (it's part of JavaScript) but it is an idiom in ReactJS:
why do you need to bind a function in a constructor
This is because React didn't want to mess with ES6 specifications (binding this to functions from its class is not in the ES6 class spec), but at the same time, wanted to give its users the convenience of ES6 class syntax. You can read more about this below.

What is the Angular equivalent to an AngularJS $watch?

In AngularJS you were able to specify watchers to observe changes in scope variables using the $watch function of the $scope. What is the equivalent of watching for variable changes (in, for example, component variables) in Angular?
In Angular 2, change detection is automatic... $scope.$watch() and $scope.$digest() R.I.P.
Unfortunately, the Change Detection section of the dev guide is not written yet (there is a placeholder near the bottom of the Architecture Overview page, in section "The Other Stuff").
Here's my understanding of how change detection works:
Zone.js "monkey patches the world" -- it intercepts all of the asynchronous APIs in the browser (when Angular runs). This is why we can use setTimeout() inside our components rather than something like $timeout... because setTimeout() is monkey patched.
Angular builds and maintains a tree of "change detectors". There is one such change detector (class) per component/directive. (You can get access to this object by injecting ChangeDetectorRef.) These change detectors are created when Angular creates components. They keep track of the state of all of your bindings, for dirty checking. These are, in a sense, similar to the automatic $watches() that Angular 1 would set up for {{}} template bindings.Unlike Angular 1, the change detection graph is a directed tree and cannot have cycles (this makes Angular 2 much more performant, as we'll see below).
When an event fires (inside the Angular zone), the code we wrote (the event handler callback) runs. It can update whatever data it wants to -- the shared application model/state and/or the component's view state.
After that, because of the hooks Zone.js added, it then runs Angular's change detection algorithm. By default (i.e., if you are not using the onPush change detection strategy on any of your components), every component in the tree is examined once (TTL=1)... from the top, in depth-first order. (Well, if you're in dev mode, change detection runs twice (TTL=2). See ApplicationRef.tick() for more about this.) It performs dirty checking on all of your bindings, using those change detector objects.
Lifecycle hooks are called as part of change detection. If the component data you want to watch is a primitive input property (String, boolean, number), you can implement ngOnChanges() to be notified of changes. If the input property is a reference type (object, array, etc.), but the reference didn't change (e.g., you added an item to an existing array), you'll need to implement ngDoCheck() (see this SO answer for more on this). You should only change the component's properties and/or properties of descendant components (because of the single tree walk implementation -- i.e., unidirectional data flow). Here's a plunker that violates that. Stateful pipes can also trip you up here.
For any binding changes that are found, the Components are updated, and then the DOM is updated. Change detection is now finished.
The browser notices the DOM changes and updates the screen.
Other references to learn more:
Angular’s $digest is reborn in the newer version of Angular - explains how the ideas from AngularJS are mapped to Angular
Everything you need to know about change detection in Angular - explains in great detail how change detection works under the hood
Change Detection Explained - Thoughtram blog Feb 22, 2016 - probably the best reference out there
Savkin's Change Detection Reinvented video - definitely watch this one
How does Angular 2 Change Detection Really Work?- jhade's blog Feb 24, 2016
Brian's video and Miško's video about Zone.js. Brian's is about Zone.js. Miško's is about how Angular 2 uses Zone.js to implement change detection. He also talks about change detection in general, and a little bit about onPush.
Victor Savkins blog posts: Change Detection in Angular 2, Two phases of Angular 2 applications, Angular, Immutability and Encapsulation. He covers a lot of ground quickly, but he can be terse at times, and you're left scratching your head, wondering about the missing pieces.
Ultra Fast Change Detection (Google doc) - very technical, very terse, but it describes/sketches the ChangeDetection classes that get built as part of the tree
This behaviour is now part of the component lifecycle.
A component can implement the ngOnChanges method in the OnChanges interface to get access to input changes.
Example:
import {Component, Input, OnChanges} from 'angular2/core';
#Component({
selector: 'hero-comp',
templateUrl: 'app/components/hero-comp/hero-comp.html',
styleUrls: ['app/components/hero-comp/hero-comp.css'],
providers: [],
directives: [],
pipes: [],
inputs:['hero', 'real']
})
export class HeroComp implements OnChanges{
#Input() hero:Hero;
#Input() real:string;
constructor() {
}
ngOnChanges(changes) {
console.log(changes);
}
}
If, in addition to automatic two-way binding, you want to call a function when a value changes, you can break the two-way binding shortcut syntax to the more verbose version.
<input [(ngModel)]="yourVar"></input>
is shorthand for
<input [ngModel]="yourVar" (ngModelChange)="yourVar=$event"></input>
(see e.g. http://victorsavkin.com/post/119943127151/angular-2-template-syntax)
You could do something like this:
<input [(ngModel)]="yourVar" (ngModelChange)="changedExtraHandler($event)"></input>
You can use getter function or get accessor to act as watch on angular 2.
See demo here.
import {Component} from 'angular2/core';
#Component({
// Declare the tag name in index.html to where the component attaches
selector: 'hello-world',
// Location of the template for this component
template: `
<button (click)="OnPushArray1()">Push 1</button>
<div>
I'm array 1 {{ array1 | json }}
</div>
<button (click)="OnPushArray2()">Push 2</button>
<div>
I'm array 2 {{ array2 | json }}
</div>
I'm concatenated {{ concatenatedArray | json }}
<div>
I'm length of two arrays {{ arrayLength | json }}
</div>`
})
export class HelloWorld {
array1: any[] = [];
array2: any[] = [];
get concatenatedArray(): any[] {
return this.array1.concat(this.array2);
}
get arrayLength(): number {
return this.concatenatedArray.length;
}
OnPushArray1() {
this.array1.push(this.array1.length);
}
OnPushArray2() {
this.array2.push(this.array2.length);
}
}
Here is another approach using getter and setter functions for the model.
#Component({
selector: 'input-language',
template: `
…
<input
type="text"
placeholder="Language"
[(ngModel)]="query"
/>
`,
})
export class InputLanguageComponent {
set query(value) {
this._query = value;
console.log('query set to :', value)
}
get query() {
return this._query;
}
}
If you want to make it 2 way binding, you can use [(yourVar)], but you have to implement yourVarChange event and call it everytime your variable change.
Something like this to track the hero change
#Output() heroChange = new EventEmitter();
and then when your hero get changed, call this.heroChange.emit(this.hero);
the [(hero)] binding will do the rest for you
see example here:
http://plnkr.co/edit/efOGIJ0POh1XQeRZctSx?p=preview
This does not answer the question directly, but I have on different occasions landed on this Stack Overflow question in order to solve something I would use $watch for in angularJs. I ended up using another approach than described in the current answers, and want to share it in case someone finds it useful.
The technique I use to achieve something similar $watch is to use a BehaviorSubject (more on the topic here) in an Angular service, and let my components subscribe to it in order to get (watch) the changes. This is similar to a $watch in angularJs, but require some more setup and understanding.
In my component:
export class HelloComponent {
name: string;
// inject our service, which holds the object we want to watch.
constructor(private helloService: HelloService){
// Here I am "watching" for changes by subscribing
this.helloService.getGreeting().subscribe( greeting => {
this.name = greeting.value;
});
}
}
In my service
export class HelloService {
private helloSubject = new BehaviorSubject<{value: string}>({value: 'hello'});
constructor(){}
// similar to using $watch, in order to get updates of our object
getGreeting(): Observable<{value:string}> {
return this.helloSubject;
}
// Each time this method is called, each subscriber will receive the updated greeting.
setGreeting(greeting: string) {
this.helloSubject.next({value: greeting});
}
}
Here is a demo on Stackblitz

What is the best-practice alternative for $scope.$watch?

I'm looking for a best-practice solution to avoid using $scope.$watch, given the following use-case:
1) directiveA has the following isloated scope:
{ sharedModel : '=' }
2) for its own usage, directiveA needs to change its inner state variable named modelA, based on sharedModel.
3) directiveB uses directiveA, and binds sharedModel to modelB (its internal model).
<directive-a
shared-model="vm.modelB" />
4) Whenever modelB/sharedModel changes, I would like modelA to be updated (remember, modelA data is only derived fromsharedModel).
In order to accomplish #4, I could add $scope.$watch on sharedModel. However, $watch is performance-expensive and not testable. Any best-practice reccomendations?
EDIT:
for live code example see jsfiddle.
notice $scope.$watch on line #10 which I wish to replace.
In this case it's possible to do in without additional pricy watch. You can make use of old-good ES5 property getters, it's going to be very efficient. So instead of $watch try something like this:
Object.defineProperty($scope, 'modelA', {
get() {
return $scope.sharedModel * 10;
}
});
Demo: http://jsfiddle.net/mggc611e/2/

Resources