How to get the size of a filtered (piped) set in angular2 - filter

I wrote my own filter pipe as it disappeared in angular2:
import {Pipe, PipeTransform} from 'angular2/core';
#Pipe({
name: 'myFilter'
})
export class MyFilter implements PipeTransform {
transform(customerData: Array<Object>, args: any[]) {
if (customerData == undefined) {
return;
}
var re = new RegExp(args[0]);
return customerData.filter((item) => re.test(item.customerId));
}
}
And use it in my template:
<tr *ngFor="#singleCustomerData of customerData | myFilter:searchTerm">
...
</tr>
Now I'd like to see how many matches the pipe returns. So essentially the size of the returned array.
In angular 1.x we were able so assign the returned set to a variable in a template like so:
<div ng-repeat="person in filtered = (data | filter: query)">
</div>
But we can no longer assign variables in templates in angular2.
So how do I get the size of the filtered set without calling the filter twice?

You still must call the filter a second time but you can use it directly like this :
{{ (customerData | myFilter:searchTerm)?.length }}

original
AFAIK there is currently no way to do this directly. A hack would be to add a template variable to the content and use a ViewChildren(...) query to get the created items and count them.
<tr *ngFor="let singleCustomerData of customerData | myFilter:searchTerm" #someVar>
...
</tr>
<div>count: {{filteredItems?.length}}</div>
#ViewChildren('someVar') filteredItems;
An alternative approach would be to pass a reference to a counter variable to the pipe like shown in https://plnkr.co/edit/Eqjyt4qdnXezyFvCcGAL?p=preview
update (Angular >=4.0.0)
Since Angular 4 *ngFor supports as
<tr *ngFor="let singleCustomerData of customerData | myFilter:searchTerm as result">
which you can use in the template (inside the element that *ngFor is added to) like
<div>{{result?.length}}</div>

Gunter answer is in the right direction, it lacks only the info on how to use the result out of the *ngFor loop. One possible solution is to enclose the *ngFor in a wider directive, like the following:
<ng-directive *ngIf='customerData | myFilter:searchTerm as filteredItems'>
<tr *ngFor="let singleCustomerData of filteredItems">
...
</tr>
<div>count: {{filteredItems.length}}</div>
</ng-directive>
Credits for this hint go to the following post:
https://netbasal.com/using-pipe-results-in-angular-templates-430683fa2213

I don't know what you exactly want to do with the size and the Günter's solution can fit your needs.
That said, you can inject the component instance into your pipe and set directly the length into a property of this component.
#Pipe({
name: 'dump'
})
export class DumpPipe {
constructor(#Inject(forwardRef(() => AppComponent)) app:AppComponent) {
this.app = app;
}
transform(array: Array<string>, args: string): Array<string> {
(...)
this.app.filteredItemLength = array.length;
return array;
}
}
#Component({
(...)
})
export class AppComponent {
(...)
}
See this answer:
How to save model manipulation of *ngFor with pipes? - "#item of (result = (items | orderAsc))" doesn't work in A2.
Hope it helps you,
Thierry

You can simply pass an object from the class component to HTML-pipe as the second argument. And in the class pipe pass the resulting array.

Related

VueJS add class depending item v-for [duplicate]

I have some data that is accessible via:
{{ content['term_goes_here'] }}
... and this evaluated to either true or false. I'd like to add a class depending on the truthiness of the expression like so:
<i class="fa" v-bind:class="[{{content['cravings']}} ? 'fa-checkbox-marked' : 'fa-checkbox-blank-outline']"></i>
where true gives me the class fa-checkbox-marked and false would give me fa-checkbox-blank-outline. The way I wrote it above gives me an error:
- invalid expression: v-bind:class="[{{content['cravings']}} ? 'fa-checkbox-marked' : 'fa-checkbox-blank-outline']"
How should I write it to be able to conditionally determine the class?
Use the object syntax.
v-bind:class="{'fa-checkbox-marked': content['cravings'], 'fa-checkbox-blank-outline': !content['cravings']}"
When the object gets more complicated, extract it into a method.
v-bind:class="getClass()"
methods:{
getClass(){
return {
'fa-checkbox-marked': this.content['cravings'],
'fa-checkbox-blank-outline': !this.content['cravings']}
}
}
Finally, you could make this work for any content property like this.
v-bind:class="getClass('cravings')"
methods:{
getClass(property){
return {
'fa-checkbox-marked': this.content[property],
'fa-checkbox-blank-outline': !this.content[property]
}
}
}
<i class="fa" v-bind:class="cravings"></i>
and add in computed :
computed: {
cravings: function() {
return this.content['cravings'] ? 'fa-checkbox-marked' : 'fa-checkbox-blank-outline';
}
}
Why not pass an object to v-bind:class to dynamically toggle the class:
<div v-bind:class="{ disabled: order.cancelled_at }"></div>
This is what is recommended by the Vue docs.
the problem is blade, try this
<i class="fa" v-bind:class="['{{content['cravings']}}' ? 'fa-checkbox-marked' : 'fa-checkbox-blank-outline']"></i>
You could use string template like with backticks `` :
:class="`${content['cravings'] ? 'fa-checkbox-marked' : 'fa-checkbox-blank-outline'}`"
if you want to apply separate css classes for same element with conditions in Vue.js
you can use the below given method.it worked in my scenario.
html
<div class="Main" v-bind:class="{ Sub: page}" >
in here, Main and Sub are two different class names for same div element.
v-bind:class directive is used to bind the sub class in here.
page is the property we use to update the classes when it's value changed.
js
data:{
page : true;
}
here we can apply a condition if we needed.
so, if the page property becomes true element will go with Main and Sub claases css styles. but if false only Main class css styles will be applied.

How to ignore html if data is empty in Laravel?

I am trying to retrieve data from database and check if the data is empty or not. What problem I am facing is that html is showing even if the data is empty. I want to ignore the html tag like example ul li. Here how i tried is like
#if(!empty($jobseekers->skill_1))
<li> My Skill is : {{ \App\skill::where('id',$jobseekers->skill_1)->pluck('name')->first() }}</li><br/>
#endif
I want to ignore "My Skill is " if the data is empty. I don't want to show anything.
When using ->get() you can't simply use any of the below:
if (empty($jobseekers->skill_1)) { }
if (!$jobseekers->skill_1) { }
if ($jobseekers->skill_1) { }
But, When you are getting data with first() method, You can simply use all above methods.
Because if you dd($jobseekers->skill_1); you'll notice an instance of Illuminate\Support\Collection is always returned, even when there are no results.
I think you are using !empty() on data with ->get() method which will always return true even data is empty. You need to use other way.
To determine if there are any results you can do any of the following:
if (!$jobseekers->skill_1->isEmpty()) { }
if ($jobseekers->skill_1->count()) { }
if (count($jobseekers->skill_1)) { }
If you get $jobseekers with get() method you can not use empty($jobseekers )
instead of empty you can use other conditions :
#if($jobseekers->skill_1 != '')
in this condition you check skill_1 as empty string
also
#if($jobseekers->skill_1)
and etc
replace your code with below code and check it:
#if($jobseekers->skill_1 != '')
<li> My Skill is : {{ \App\skill::where('id',$jobseekers->skill_1)-pluck('name')->first() }}</li><br/>
#endif
you should use isset()
#if(isset($jobseekers->skill_1))
<li> My Skill is : {{ \App\skill::where('id',$jobseekers->skill_1)->pluck('name')->first() }}</li><br/>
#endif
you can use count method
#if(count($jobseekers->skill_1)>0)
<li> My Skill is : {{ \App\skill::where('id',$jobseekers->skill_1)-pluck('name')->first() }}</li><br/>
#endif
#if(count($data)>0)
//write your code which you want to show if data is available
#endif

Dynamic pattern validation in Angular 2 and 4

Input validation works fine with a fixed pattern, e.g.
<input type="number"
[(ngModel)]="info.sortOrder"
pattern="[0-9][0-9]"
id="sortOrder" name="sortOrder" #sortOrder="ngModel"/>
When changing the pattern to be evaluated through a function, validation always fails. The function "customPattern()" is called, though.
<input type="number"
[(ngModel)]="info.sortOrder"
[pattern]="customPattern()"
id="sortOrder" name="sortOrder" #sortOrder="ngModel"/>
With
customPattern() { return "[1-9][0-9]"; }
Is this a bug or is this not supposed to work this way?
I guess you are dong wrong, #black
I would recommend you to use Reactive Forms approach to achieve the desire result.
create a reactive form.
Add the control name for eg('number_validation').
(optional) Register the HTML input element with the formControlName same as above ('number_validation').
create a field 'regex' = '[0-9][0-9]' in the component.ts file.
Bind the [pattern] = regex in the HTML HTML input element tag.
Listen the change and change the regex pattern according to the requirement.
In ts file.
someForm: FormGroup;
this.someForm= new FormGroup({
'some_name': new FormControl('', [
Validators.required])});
regex = /[0-9][0-9]/;
In HTML:
<input type="number"
[pattern]=regex
id="sortOrder"
formControlName=some_name
name="sortOrder"/>
Logic :
It depends on the requirement how you are going to change the regex value, dynamically.
eg.
ngAfterViewInit() {
this.someForm.get('some_name').valueChanges.subscribe(val => {
if (val === 'anything') {
this.regex = /change the regex/;
} else if (val === 'anything_other') {
this.regex = /change the regex/;
}
});
}
Hope it help you, or other devs! :)
In you component, simply define a member variable like this:
export class AppComponent {
customPattern = '[1-9][0-9]';
In your html, use interpolation like this:
pattern = "{{customPattern}}"
That should work.

Angular2 how to call a method only after subscribed data is completely bounded to a table using ng-for? [duplicate]

In Angular 1 I have written a custom directive ("repeater-ready") to use with ng-repeat to invoke a callback method when the iteration has been completed:
if ($scope.$last === true)
{
$timeout(() =>
{
$scope.$parent.$parent.$eval(someCallbackMethod);
});
}
Usage in markup:
<li ng-repeat="item in vm.Items track by item.Identifier"
repeater-ready="vm.CallThisWhenNgRepeatHasFinished()">
How can I achieve a similar functionality with ngFor in Angular 2?
You can use #ViewChildren for that purpose
#Component({
selector: 'my-app',
template: `
<ul *ngIf="!isHidden">
<li #allTheseThings *ngFor="let i of items; let last = last">{{i}}</li>
</ul>
<br>
<button (click)="items.push('another')">Add Another</button>
<button (click)="isHidden = !isHidden">{{isHidden ? 'Show' : 'Hide'}}</button>
`,
})
export class App {
items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
#ViewChildren('allTheseThings') things: QueryList<any>;
ngAfterViewInit() {
this.things.changes.subscribe(t => {
this.ngForRendred();
})
}
ngForRendred() {
console.log('NgFor is Rendered');
}
}
origional Answer is here
https://stackoverflow.com/a/37088348/5700401
You can use something like this (ngFor local variables):
<li *ngFor="#item in Items; #last = last" [ready]="last ? false : true">
Then you can Intercept input property changes with a setter
#Input()
set ready(isReady: boolean) {
if (isReady) someCallbackMethod();
}
For me works in Angular2 using Typescript.
<li *ngFor="let item in Items; let last = last">
...
<span *ngIf="last">{{ngForCallback()}}</span>
</li>
Then you can handle using this function
public ngForCallback() {
...
}
The solution is quite trivial. If you need to know when ngFor completes printing all the DOM elements to the browser window, do the following:
1. Add a placeholder
Add a placeholder for the content being printed:
<div *ngIf="!contentPrinted">Rendering content...</div>
2. Add a container
Create a container with display: none for the content. When all items are printed, do display: block. contentPrinted is a component flag property, which defaults to false:
<ul [class.visible]="contentPrinted">
...items
</ul>
3. Create a callback method
Add onContentPrinted() to the component, which disables itself after ngFor completes:
onContentPrinted() {
this.contentPrinted = true;
this.changeDetector.detectChanges();
}
And don't forget to use ChangeDetectorRef to avoid ExpressionChangedAfterItHasBeenCheckedError.
4. Use ngFor last value
Declare last variable on ngFor. Use it inside li to run a method when this item is the last one:
<li *ngFor="let item of items; let last = last">
...
<ng-container *ngIf="last && !contentPrinted">
{{ onContentPrinted() }}
</ng-container>
<li>
Use contentPrinted component flag property to run onContentPrinted() only once.
Use ng-container to make no impact on the layout.
Instead of [ready], use [attr.ready] like below
<li *ngFor="#item in Items; #last = last" [attr.ready]="last ? false : true">
I found in RC3 the accepted answer doesn't work. However, I have found a way to deal with this. For me, I need to know when ngFor has finished to run the MDL componentHandler to upgrade the components.
First you will need a directive.
upgradeComponents.directive.ts
import { Directive, ElementRef, Input } from '#angular/core';
declare var componentHandler : any;
#Directive({ selector: '[upgrade-components]' })
export class UpgradeComponentsDirective{
#Input('upgrade-components')
set upgradeComponents(upgrade : boolean){
if(upgrade) componentHandler.upgradeAllRegistered();
}
}
Next import this into your component and add it to the directives
import {UpgradeComponentsDirective} from './upgradeComponents.directive';
#Component({
templateUrl: 'templates/mytemplate.html',
directives: [UpgradeComponentsDirective]
})
Now in the HTML set the "upgrade-components" attribute to true.
<div *ngFor='let item of items;let last=last' [upgrade-components]="last ? true : false">
When this attribute is set to true, it will run the method under the #Input() declaration. In my case it runs componentHandler.upgradeAllRegistered(). However, it could be used for anything of your choosing. By binding to the 'last' property of the ngFor statement, this will run when it is finished.
You will not need to use [attr.upgrade-components] even though this is not a native attribute due to it now being a bonafide directive.
I write a demo for this issue. The theory is based on the accepted answer but this answer is not complete because the li should be a custom component which can accept a ready input.
I write a complete demo for this issue.
Define a new component:
import {Component, Input, OnInit} from '#angular/core';
#Component({
selector: 'app-li-ready',
templateUrl: './li-ready.component.html',
styleUrls: ['./li-ready.component.css']
})
export class LiReadyComponent implements OnInit {
items: string[] = [];
#Input() item;
constructor() { }
ngOnInit(): void {
console.log('LiReadyComponent');
}
#Input()
set ready(isReady: boolean) {
if (isReady) {
console.log('===isReady!');
}
}
}
template
{{item}}
usage in the app component
<app-li-ready *ngFor="let item of items; let last1 = last;" [ready]="last1" [item]="item"></app-li-ready>
You will see the log in the console will print all the item string and then print the isReady.
I haven't yet looked in depth of how ngFor renders elements under the hood. But from observation, I've noticed it often tends to evaluate expressions more than once per each item it's iterating.
This causes any typescript method call made when checking ngFor 'last' variable to get, sometimes, triggered more than once.
To guarantee a one call to your typescript method by ngFor when it properly finishes iterating through items, you need to add a small protection against the multiple expression re-evaluation that ngFor does under the hood.
Here is one way to do it (via a directive), hope it helps:
The directive code
import { Directive, OnDestroy, Input, AfterViewInit } from '#angular/core';
#Directive({
selector: '[callback]'
})
export class CallbackDirective implements AfterViewInit, OnDestroy {
is_init:boolean = false;
called:boolean = false;
#Input('callback') callback:()=>any;
constructor() { }
ngAfterViewInit():void{
this.is_init = true;
}
ngOnDestroy():void {
this.is_init = false;
this.called = false;
}
#Input('callback-condition')
set condition(value: any) {
if (value==false || this.called) return;
// in case callback-condition is set prior ngAfterViewInit is called
if (!this.is_init) {
setTimeout(()=>this.condition = value, 50);
return;
}
if (this.callback) {
this.callback();
this.called = true;
}
else console.error("callback is null");
}
}
After declaring the above directive in your module (assuming you know how to do so, if not, ask and I'll hopefully update this with a code snippet), here is how to use the directive with ngFor:
<li *ngFor="let item of some_list;let last = last;" [callback]="doSomething" [callback-condition]="last">{{item}}</li>
'doSomething' is the method name in your TypeScript file that you want to call when ngFor finishes iterating through items.
Note: 'doSomething' doesn't have brackets '()' here as we're just passing a reference to the typescript method and not actually calling it here.
And finally here is how 'doSomething' method looks like in your typescript file:
public doSomething=()=> {
console.log("triggered from the directive's parent component when ngFor finishes iterating");
}

How do I insert a localStorage variable into ng-repeat so the user can change the filter

Right now I have a couple of buttons that will insert a value into localStorage. I thought I could get the value and insert it into the ng-repeat line to change the filtering.
If I use plain filtering, this works:
<div class='segment' ng-repeat="x in names | filter:x.bodyType='Buggy'">
But this does not work:
<div class='segment' ng-repeat="x in names | filter:x.bodyType='<script>var show = localStorage.getItem('show'); show</script>'">
I'm not using Jquery or any other JS or CSS framework except AngularJS. Not using PHP. This is simple HTML5 for a web app.
Build the object dynamically and pass it to filter.
$scope.show = 'A';
$scope.base = function() {
return $scope.show;
};
$scope.bodyType = function() {
return {bodyType: $scope.base()};
};
$scope.objs = [{bodyType: 'A'}, {bodyType: 'B'}];
<button ng-click="show='B'">Button</button>
<div ng-repeat="x in objs | filter: bodyType()">{{x}}</div>
I don't think Angular is capable of interpolating the expression <script>...</script>. I suggest you have a function in your controller that gets the value using localStorage service like:
$scope.bodyType = function() {
localStorage.getItem('show');
};
<div class='segment' ng-repeat="x in names | filter:x.bodyType='{{bodyType()}}'">

Resources