Data from backend not visible in html page - javascript

I have angular 8 application. And I have a service for retrieving courses from a api call.
So the service method looks like this:
loadCourseById(courseId: number) {
return this.http.get<Course>(`/api/courses/${courseId}`)
.pipe(
shareReplay()
);
}
and the component looks like this:
#Component({
selector: 'course',
templateUrl: './course.component.html',
styleUrls: ['./course.component.css']
})
export class CourseComponent implements OnInit {
course$: Observable<Course>;
lessons$: Observable<Lesson[]>;
constructor(private route: ActivatedRoute, private courseService: CoursesService) {
}
ngOnInit() {
const courseId = parseInt(this.route.snapshot.paramMap.get('courseId'));
this.course$ = this.courseService.loadCourseById(courseId);
console.log(courseId);
}
}
and template looks like this:
<ng-container *ngIf = "(course$ | async) as course">
<div class="course">
<h2>{{course?.description}}</h2>
<img class="course-thumbnail" [src]="course?.iconUrl">
<table class="lessons-table mat-elevation-z7">
<thead>
<th>#</th>
<th>Description</th>
<th>Duration</th>
</thead>
</table>
</div>
</ng-container>
So I have checked the api call. And I see in the console.log that I get the correct data back.
But in the html page it is empty so no content is visible.
So what I have to change?
Thank you

You're miss a small change in your code
remove this pipe from your service
.pipe(shareReplay());
and for your template there are 2 possible change to proceed:
1 - remove () inside your ngIf statement => *ngIf="course$ | async as course"
<h2>{{course.description}}</h2>
<img class="course-thumbnail" [src]="course.iconUrl">
....
2 - your ngIf statement will look like this => *ngIf="course$ | async"
<h2>{{(course$ | async)?.description}}</h2>
<img class="course-thumbnail" [src]="(course$ | async)?.iconUrl">
....
I hope that is helpfull

Related

Using dynamic component within Angular structural directive produces extra HTML tag. How to remove or replace it?

I have quite complex infrastructure in my project which contains of
host component
structural directive used in host component's template (MyDir)
another component used in structural directive (MyComp)
Simplified version looks like the following.
host component
#Component({
selector: 'my-app',
template: `
<table>
<tr *myDir="let item of data">
<td>{{item.text}}</td>
</tr>
</table>`
})
export class AppComponent {
data = [ { text: 'item 1' }, { text: 'item 2' } ];
}
structural directive
import { MyComp } from './myComp';
#Directive({ selector: '[myDir][myDirOf]' })
export class MyDir implements OnInit {
private data: any;
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef,
private resolver: ComponentFactoryResolver
) {
}
#Input() set myDirOf(data: any) {
this.data = data;
}
ngOnInit() {
const templateView = this.templateRef.createEmbeddedView({});
const compFactory = this.resolver.resolveComponentFactory(MyComp);
const componentRef = this.viewContainer.createComponent(
compFactory, undefined, this.viewContainer.injector, [templateView.rootNodes]
);
componentRef.instance.data = this.data;
componentRef.instance.template = this.templateRef;
}
}
structural directive's component
#Component({
selector: '[my-comp]',
template: `
<tr><td>custom td</td></tr>
<ng-template *ngFor="let item of data"
[ngTemplateOutlet]="template"
[ngTemplateOutletContext]="{ $implicit: item }"
></ng-template>`
})
export class MyComp {
public template: TemplateRef<any>;
public data: any;
}
The output is
custom td
item 1
item 2
which is fine except the markup which is
<table>
<div my-comp>
<tr><td>custom td</td></tr>
<tr><td>item 1</td></tr>
<tr><td>item 2</td></tr>
</div>
</table>
Problem
I want to remove intermediate <div my-comp> from the result view or at least replace it with <tbody>. To see the whole picture I prepared Stackblitz DEMO, hope it will help... Also, it might be obvious the example is artificial, but this is what I came with trying to reproduce the issue with minimal code. So the problem should have a solution in the given infrastructure.
Update
#AlexG found simple way to replace intermediate div with tbody and stackblitz demo showed a good result at first. But when I tried to apply it to my project locally I faced new issue: browser arranges its own tbody before the dynamic contents of the table are ready to render, which results in two nested tbody in the end view, which seems inconsistent per html specs
<table>
<tbody>
<tbody my-comp>
<tr><td>custom td</td></tr>
<tr><td>item 1</td></tr>
<tr><td>item 2</td></tr>
</tbody>
</tbody>
</table>
Stackblitz demo has no such problem, only tbody my-comp is present. But exactly the same project in my local dev environment does. So I'm still trying to find a way how to remove intermediate my-comp container.
Update 2
The demo had been updated in accordance with the solution suggested by #markdBC.
My answer is inspired by Slim's answer to a similar question found here: https://stackoverflow.com/a/56887630/12962012.
You can remove the intermediate <div my-comp> by
Creating a TemplateRef object representing the template of the MyComp component inside the MyComp component.
Accessing this TemplateRef object from the structural directive.
Creating an embedded view inside the view container with the TemplateRef object from the structural directive.
The resulting code looks something like:
MyComp component
#Component({
selector: "[my-comp]",
template: `
<ng-template #mytemplate>
<tr>
<td>custom td</td>
</tr>
<ng-template
*ngFor="let item of data"
[ngTemplateOutlet]="template"
[ngTemplateOutletContext]="{ $implicit: item }"
></ng-template>
</ng-template>
`
})
export class MyComp {
public template: TemplateRef<any>;
public data: any;
#ViewChild("mytemplate", { static: true }) mytemplate: TemplateRef<any>;
}
MyDir directive
export class MyDir implements OnInit {
private version: string;
private data: any;
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef,
private resolver: ComponentFactoryResolver
) {}
#Input() set myDirOf(data: any) {
this.data = data;
}
ngOnInit() {
const compFactory = this.resolver.resolveComponentFactory(MyComp);
const componentRef = compFactory.create(this.viewContainer.injector);
componentRef.instance.data = this.data;
componentRef.instance.template = this.templateRef;
this.viewContainer.createEmbeddedView(componentRef.instance.mytemplate);
}
}
The resulting HTML looks something like:
<table>
<tr><td>custom td</td></tr>
<tr><td>item 1</td></tr>
<tr><td>item 2</td></tr>
</table>
I've prepared a StackBlitz demo at https://stackblitz.com/edit/table-no-div-wrapper.
Change the selector of your component from [my-comp] to tbody [my-comp] and your will have a <tbody my-comp> instead of a <div my-comp> which would be sufficient if I understood you correctly.

Angular 2 - Manipulating rxjs Observable in a view

I'm getting started with Observable in Angular 2 and I can't figure out how to use them properly in my views.
I'm using Angular 2 with angular-redux, and using the #select() decorator to retrieve my selectedMovie$ from the redux store. This part works fine, the component basically dispatch a redux event to set the default selectedMovie$ upon init. The redux store is correctly updated, but when I try to consumme it in the view, I face some issues.
import { Component, OnInit } from '#angular/core';
import { NgRedux, select } from '#angular-redux/store';
import { MovieActions} from '../store/app.actions';
import { IAppState} from '../store/reducers';
import { MovieService } from '../movie.service';
import { Observable } from 'rxjs/Observable';
import { IMovie } from '../movie.model';
#Component({
selector: 'app-movies-container',
templateUrl: './movies-container.component.html',
styleUrls: ['./movies-container.component.css'],
providers: [MovieService]
})
export class MoviesContainerComponent implements OnInit {
movies: Array<IMovie>;
#select() readonly selectedMovie$: Observable<IMovie>; // HERE - Get data from the redux store
constructor(
private movieService: MovieService,
private movieActions: MovieActions,
private ngRedux: NgRedux<IAppState>
) { }
ngOnInit() {
// Fetch movies and use the first one as default displayed movie.
this.getMovies() // HERE - Takes the first movie and make it the default selectedMovie by dispatching a redux action
.then(movies =>
this.ngRedux.dispatch(this.movieActions.changeSelectedMovie(this.movies[0]))
);
}
getMovies() { // HERE: Call an API, returns an array of movies in data.results
return this.movieService.getMostPopular()
.then(data => this.movies = data.results);
}
onSelect(movie: IMovie) {
this.ngRedux.dispatch(this.movieActions.changeSelectedMovie(movie));
}
}
Here comes the view:
<div *ngIf="movies">
<md-list>
<h3 md-subheader>Most popular movies NOW!</h3>
<pre>
{{(selectedMovie$ | async | json).id}} // This fails and displays nothing. I'd expect it to display the movie id
</pre>
<md-list-item
*ngFor="let movie of movies"
[class.selected]="movie.id === (selectedMovie$ | async | json).id" // This is a real deal, I don't know what's the syntax to use. I wanted to compare ids
(click)="onSelect(movie)"
>
<img src="https://image.tmdb.org/t/p/w92{{movie.poster_path}}" />
{{movie.title}}
</md-list-item>
</md-list>
<app-movie-card [movie]="selectedMovie$ | async | json"></app-movie-card> // This component gets the object correctly formatted
</div>
Maybe I'm just not using the right syntax. Or maybe I shouldn't use an Observer in the view in the first place?
Edit: Solution
<div *ngIf="movies && (selectedMovie$ | async); let selectedMovie">
<md-list>
<h3 md-subheader>Most popular movies NOW!</h3>
<md-list-item
*ngFor="let movie of movies"
[class.selected]="movie.id === selectedMovie.id"
(click)="onSelect(movie)"
>
<img src="https://image.tmdb.org/t/p/w92{{movie.poster_path}}" />
{{movie.title}}
</md-list-item>
</md-list>
<app-movie-card [movie]="selectedMovie"></app-movie-card>
</div>
The problem results from a common misconception that JSON is a synonym for plain object. It isn't.
json pipe converts input into actual JSON string. So (selectedMovie$ | async | json) expression evaluates to a string and doesn't have id property.
It is helpful to use AoT compilation, because it allows to detect type problems in template and would likely result in type error in this case.
It should be (selectedMovie$ | async).id instead.
If (selectedMovie$ | async) is used more than once (like in this case), it will result in several subscriptions. It can be optimized by assigning it to local variable, as explained here:
<div *ngIf="movies">
<ng-container *ngIf="(selectedMovie$ | async); let selectedMovie">
...
{{selectedMovie.id}}
...
</ng-container>
</div>

Is it possible to create HTML items from JS in Angular 2?

I have simple GET request, which brings some data to show.
this.apiRequest.get(url)
.map(response => response.json())
.subscribe(response => {
this.notes = response.notes
loader.dismiss()
}, err => {
loader.dismiss()
})
Then I show it like this:
<ion-item *ngFor="let note of notes" text-wrap>
<!-- data -->
</ion-item>
The main problem is that when there is a lot of notes the loader dismisses before all items are shown.
I need loop ngFor in js and then hide loader or somehow disable loader from HTML...
this is not what you are looking for but hope will help
Base idea is to provide ngFor loop complete event
Create component
import { Component, Input, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'islast',
template: '<span></span>'
})
export class LastDirective {
#Input() isLast: boolean;
#Output() onLastDone: EventEmitter<boolean> = new EventEmitter<boolean>();
ngOnInit() {
if (this.isLast)
this.onLastDone.emit(true); //you can hide loader from here this is last element in ngfor
}
}
in html
<tr *ngFor="let sm of filteredMembers; let last = last; let i=index;" data-id="{{sm.Id}}">
<td>
<islast [isLast]="last" (onLastDone)="modalMembersDone()">

Angular2 : render a component without its wrapping tag

I am struggling to find a way to do this. In a parent component, the template describes a table and its thead element, but delegates rendering the tbody to another component, like this:
<table>
<thead>
<tr>
<th>Name</th>
<th>Time</th>
</tr>
</thead>
<tbody *ngFor="let entry of getEntries()">
<my-result [entry]="entry"></my-result>
</tbody>
</table>
Each myResult component renders its own tr tag, basically like so:
<tr>
<td>{{ entry.name }}</td>
<td>{{ entry.time }}</td>
</tr>
The reason I'm not putting this directly in the parent component (avoiding the need for a myResult component) is that the myResult component is actually more complicated than shown here, so I want to put its behaviour in a separate component and file.
The resulting DOM looks bad. I believe this is because it is invalid, as tbody can only contain tr elements (see MDN), but my generated (simplified) DOM is :
<table>
<thead>
<tr>
<th>Name</th>
<th>Time</th>
</tr>
</thead>
<tbody>
<my-result>
<tr>
<td>Bob</td>
<td>128</td>
</tr>
</my-result>
</tbody>
<tbody>
<my-result>
<tr>
<td>Lisa</td>
<td>333</td>
</tr>
</my-result>
</tbody>
</table>
Is there any way we can get the same thing rendered, but without the wrapping <my-result> tag, and while still using a component to be sole responsible for rendering a table row ?
I have looked at ng-content, DynamicComponentLoader, the ViewContainerRef, but they don't seem to provide a solution to this as far as I can see.
You can use attribute selectors
#Component({
selector: '[myTd]'
...
})
and then use it like
<td myTd></td>
You need "ViewContainerRef" and inside my-result component do something like this:
.html:
<ng-template #template>
<tr>
<td>Lisa</td>
<td>333</td>
</tr>
</ng-template>
.ts:
#ViewChild('template', { static: true }) template;
constructor(
private viewContainerRef: ViewContainerRef
) { }
ngOnInit() {
this.viewContainerRef.createEmbeddedView(this.template);
}
you can try use the new css display: contents
here's my toolbar scss:
:host {
display: contents;
}
:host-context(.is-mobile) .toolbar {
position: fixed;
/* Make sure the toolbar will stay on top of the content as it scrolls past. */
z-index: 2;
}
h1.app-name {
margin-left: 8px;
}
and the html:
<mat-toolbar color="primary" class="toolbar">
<button mat-icon-button (click)="toggle.emit()">
<mat-icon>menu</mat-icon>
</button>
<img src="/assets/icons/favicon.png">
<h1 class="app-name">#robertking Dashboard</h1>
</mat-toolbar>
and in use:
<navigation-toolbar (toggle)="snav.toggle()"></navigation-toolbar>
Attribute selectors are the best way to solve this issue.
So in your case:
<table>
<thead>
<tr>
<th>Name</th>
<th>Time</th>
</tr>
</thead>
<tbody my-results>
</tbody>
</table>
my-results ts
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'my-results, [my-results]',
templateUrl: './my-results.component.html',
styleUrls: ['./my-results.component.css']
})
export class MyResultsComponent implements OnInit {
entries: Array<any> = [
{ name: 'Entry One', time: '10:00'},
{ name: 'Entry Two', time: '10:05 '},
{ name: 'Entry Three', time: '10:10'},
];
constructor() { }
ngOnInit() {
}
}
my-results html
<tr my-result [entry]="entry" *ngFor="let entry of entries"><tr>
my-result ts
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: '[my-result]',
templateUrl: './my-result.component.html',
styleUrls: ['./my-result.component.css']
})
export class MyResultComponent implements OnInit {
#Input() entry: any;
constructor() { }
ngOnInit() {
}
}
my-result html
<td>{{ entry.name }}</td>
<td>{{ entry.time }}</td>
See working stackblitz: https://stackblitz.com/edit/angular-xbbegx
Use this directive on your element
#Directive({
selector: '[remove-wrapper]'
})
export class RemoveWrapperDirective {
constructor(private el: ElementRef) {
const parentElement = el.nativeElement.parentElement;
const element = el.nativeElement;
parentElement.removeChild(element);
parentElement.parentNode.insertBefore(element, parentElement.nextSibling);
parentElement.parentNode.removeChild(parentElement);
}
}
Example usage:
<div class="card" remove-wrapper>
This is my card component
</div>
and in the parent html you call card element as usual, for example:
<div class="cards-container">
<card></card>
</div>
The output will be:
<div class="cards-container">
<div class="card" remove-wrapper>
This is my card component
</div>
</div>
Another option nowadays is the ContribNgHostModule made available from the #angular-contrib/common package.
After importing the module you can add host: { ngNoHost: '' } to your #Component decorator and no wrapping element will be rendered.
Improvement on #Shlomi Aharoni answer. It is generally good practice to use Renderer2 to manipulate the DOM to keep Angular in the loop and because for other reasons including security (e.g. XSS Attacks) and server-side rendering.
Directive example
import { AfterViewInit, Directive, ElementRef, Renderer2 } from '#angular/core';
#Directive({
selector: '[remove-wrapper]'
})
export class RemoveWrapperDirective implements AfterViewInit {
constructor(private elRef: ElementRef, private renderer: Renderer2) {}
ngAfterViewInit(): void {
// access the DOM. get the element to unwrap
const el = this.elRef.nativeElement;
const parent = this.renderer.parentNode(this.elRef.nativeElement);
// move all children out of the element
while (el.firstChild) { // this line doesn't work with server-rendering
this.renderer.appendChild(parent, el.firstChild);
}
// remove the empty element from parent
this.renderer.removeChild(parent, el);
}
}
Component example
#Component({
selector: 'app-page',
templateUrl: './page.component.html',
styleUrls: ['./page.component.scss'],
})
export class PageComponent implements AfterViewInit {
constructor(
private renderer: Renderer2,
private elRef: ElementRef) {
}
ngAfterViewInit(): void {
// access the DOM. get the element to unwrap
const el = this.elRef.nativeElement; // app-page
const parent = this.renderer.parentNode(this.elRef.nativeElement); // parent
// move children to parent (everything is moved including comments which angular depends on)
while (el.firstChild){ // this line doesn't work with server-rendering
this.renderer.appendChild(parent, el.firstChild);
}
// remove empty element from parent - true to signal that this removed element is a host element
this.renderer.removeChild(parent, el, true);
}
}
This works for me and it can avoid ExpressionChangedAfterItHasBeenCheckedError error.
child-component:
#Component({
selector: 'child-component'
templateUrl: './child.template.html'
})
export class ChildComponent implements OnInit {
#ViewChild('childTemplate', {static: true}) childTemplate: TemplateRef<any>;
constructor(
private view: ViewContainerRef
) {}
ngOnInit(): void {
this.view.createEmbeddedView(this.currentUserTemplate);
}
}
parent-component:
<child-component></child-component>

Angular2 Pipe - How can I do a basic string filter on a list of items? [duplicate]

Apparently, Angular 2 will use pipes instead of filters as in Angular1 in conjunction with ng-for to filter results, although the implementation still seems to be vague, with no clear documentation.
Namely what I'm trying to achieve could be viewed from the following perspective
<div *ng-for="#item of itemsList" *ng-if="conditon(item)"></div>
How to implement so using pipes?
Basically, you write a pipe which you can then use in the *ngFor directive.
In your component:
filterargs = {title: 'hello'};
items = [{title: 'hello world'}, {title: 'hello kitty'}, {title: 'foo bar'}];
In your template, you can pass string, number or object to your pipe to use to filter on:
<li *ngFor="let item of items | myfilter:filterargs">
In your pipe:
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'myfilter',
pure: false
})
export class MyFilterPipe implements PipeTransform {
transform(items: any[], filter: Object): any {
if (!items || !filter) {
return items;
}
// filter items array, items which match and return true will be
// kept, false will be filtered out
return items.filter(item => item.title.indexOf(filter.title) !== -1);
}
}
Remember to register your pipe in app.module.ts; you no longer need to register the pipes in your #Component
import { MyFilterPipe } from './shared/pipes/my-filter.pipe';
#NgModule({
imports: [
..
],
declarations: [
MyFilterPipe,
],
providers: [
..
],
bootstrap: [AppComponent]
})
export class AppModule { }
Here's a Plunker which demos the use of a custom filter pipe and the built-in slice pipe to limit results.
Please note (as several commentators have pointed out) that there is a reason why there are no built-in filter pipes in Angular.
A lot of you have great approaches, but the goal here is to be generic and defined a array pipe that is extremely reusable across all cases in relationship to *ngFor.
callback.pipe.ts (don't forget to add this to your module's declaration array)
import { PipeTransform, Pipe } from '#angular/core';
#Pipe({
name: 'callback',
pure: false
})
export class CallbackPipe implements PipeTransform {
transform(items: any[], callback: (item: any) => boolean): any {
if (!items || !callback) {
return items;
}
return items.filter(item => callback(item));
}
}
Then in your component, you need to implement a method with the following signuature (item: any) => boolean, in my case for example, I called it filterUser, that filters users' age that are greater than 18 years.
Your Component
#Component({
....
})
export class UsersComponent {
filterUser(user: IUser) {
return !user.age >= 18
}
}
And last but not least, your html code will look like this:
Your HTML
<li *ngFor="let user of users | callback: filterUser">{{user.name}}</li>
As you can see, this Pipe is fairly generic across all array like items that need to be filter via a callback. In my case, I found it to be very useful for *ngFor like scenarios.
Hope this helps!!!
codematrix
Simplified way (Used only on small arrays because of performance issues. In large arrays you have to make the filter manually via code):
See: https://angular.io/guide/pipes#appendix-no-filterpipe-or-orderbypipe
#Pipe({
name: 'filter'
})
#Injectable()
export class FilterPipe implements PipeTransform {
transform(items: any[], field : string, value : string): any[] {
if (!items) return [];
if (!value || value.length == 0) return items;
return items.filter(it =>
it[field].toLowerCase().indexOf(value.toLowerCase()) !=-1);
}
}
Usage:
<li *ngFor="let it of its | filter : 'name' : 'value or variable'">{{it}}</li>
If you use a variable as a second argument, don't use quotes.
This is what I implemented without using pipe.
component.html
<div *ngFor="let item of filter(itemsList)">
component.ts
#Component({
....
})
export class YourComponent {
filter(itemList: yourItemType[]): yourItemType[] {
let result: yourItemType[] = [];
//your filter logic here
...
...
return result;
}
}
I'm not sure when it came in but they already made slice pipe that will do that. It's well documented too.
https://angular.io/docs/ts/latest/api/common/index/SlicePipe-pipe.html
<p *ngFor="let feature of content?.keyFeatures | slice:1:5">
{{ feature.description }}
</p>
A simple solution that works with Angular 6 for filtering a ngFor, it's the following:
<span *ngFor="item of itemsList" >
<div *ngIf="yourCondition(item)">
your code
</div>
</span>
Spans are useful because does not inherently represent anything.
You could also use the following:
<template ngFor let-item [ngForOf]="itemsList">
<div *ng-if="conditon(item)"></div>
</template>
This will only show the div if your items matches the condition
See the angular documentation for more information
If you would also need the index, use the following:
<template ngFor let-item [ngForOf]="itemsList" let-i="index">
<div *ng-if="conditon(item, i)"></div>
</template>
pipes in Angular2 are similar to pipes on the command line. The output of each preceding value is fed into the filter after the pipe which makes it easy to chain filters as well like this:
<template *ngFor="#item of itemsList">
<div *ngIf="conditon(item)">{item | filter1 | filter2}</div>
</template>
I know its an old question, however, I thought it might be helpful to offer another solution.
equivalent of AngularJS of this
<div *ng-for="#item of itemsList" *ng-if="conditon(item)"></div>
in Angular 2+ you cant use *ngFor and *ngIf on a same element, so it will be following:
<div *ngFor="let item of itemsList">
<div *ngIf="conditon(item)">
</div>
</div>
and if you can not use as internal container use ng-container instead.
ng-container is useful when you want to conditionally append a group of elements (ie using *ngIf="foo") in your application but don't want to wrap them with another element.
There is a dynamic filter pipe that I use
Source data:
items = [{foo: 'hello world'}, {foo: 'lorem ipsum'}, {foo: 'foo bar'}];
In the template you can dinamically set the filter in any object attr:
<li *ngFor="let item of items | filter:{foo:'bar'}">
The pipe:
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'filter',
})
export class FilterPipe implements PipeTransform {
transform(items: any[], filter: Record<string, any>): any {
if (!items || !filter) {
return items;
}
const key = Object.keys(filter)[0];
const value = filter[key];
return items.filter((e) => e[key].indexOf(value) !== -1);
}
}
Don't forget to register the pipe in your app.module.ts declarations
Pipe would be best approach. but below one would also work.
<div *ng-for="#item of itemsList">
<ng-container *ng-if="conditon(item)">
// my code
</ng-container>
</div>
For this requirement, I implement and publish a generic component. See
https://www.npmjs.com/package/w-ng5
For use this components, before, install this package with npm:
npm install w-ng5 --save
After, import module in app.module
...
import { PipesModule } from 'w-ng5';
In the next step, add in declare section of app.module:
imports: [
PipesModule,
...
]
Sample use
Filtering simple string
<input type="text" [(ngModel)]="filtroString">
<ul>
<li *ngFor="let s of getStrings() | filter:filtroString">
{{s}}
</li>
</ul>
Filtering complex string - field 'Value' in level 2
<input type="text" [(ngModel)]="search">
<ul>
<li *ngFor="let s of getComplexTypesExtends() | filter:[{field:'n1.n2.valor2', value: search}]">
{{s.nome}} - {{s.idade}} - {{s.n1.valor1}} - {{s.n1.n2.valor2}}
</li>
</ul>
Filtering complex string - middle field - 'Value' in level 1
<input type="text" [(ngModel)]="search3">
<ul>
<li *ngFor="let s of getComplexTypesExtends() | filter:[{field:'n1.valor1', value: search3}]">
{{s.nome}} - {{s.idade}} - {{s.n1.valor1}} - {{s.n1.n2.valor2}}
</li>
</ul>
Filtering complex array simple - field 'Nome' level 0
<input type="text" [(ngModel)]="search2">
<ul>
<li *ngFor="let s of getComplexTypesExtends() | filter:[{field:'nome', value: search2}]">
{{s.nome}} - {{s.idade}} - {{s.n1.valor1}} - {{s.n1.n2.valor2}}
</li>
</ul>
Filtering in tree fields - field 'Valor' in level 2 or 'Valor' in level 1 or 'Nome' in level 0
<input type="text" [(ngModel)]="search5">
<ul>
<li *ngFor="let s of getComplexTypesExtends() | filter:[{field:'n1.n2.valor2', value: search5}, {field:'n1.valor1', value: search5}, {field:'nome', value: search5}]">
{{s.nome}} - {{s.idade}} - {{s.n1.valor1}} - {{s.n1.n2.valor2}}
</li>
</ul>
Filtering nonexistent field - 'Valor' in nonexistent level 3
<input type="text" [(ngModel)]="search4">
<ul>
<li *ngFor="let s of getComplexTypesExtends() | filter:[{field:'n1.n2.n3.valor3', value: search4}]">
{{s.nome}} - {{s.idade}} - {{s.n1.valor1}} - {{s.n1.n2.valor2}}
</li>
</ul>
This component work with infinite attribute level...
I've created a plunker based off of the answers here and elsewhere.
Additionally I had to add an #Input, #ViewChild, and ElementRef of the <input> and create and subscribe() to an observable of it.
Angular2 Search Filter: PLUNKR (UPDATE: plunker no longer works)
Based on the very elegant callback pipe solution proposed above, it is possible to generalize it a bit further by allowing additional filter parameters to be passed along. We then have :
callback.pipe.ts
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'callback',
pure: false
})
export class CallbackPipe implements PipeTransform {
transform(items: any[], callback: (item: any, callbackArgs?: any[]) => boolean, callbackArgs?: any[]): any {
if (!items || !callback) {
return items;
}
return items.filter(item => callback(item, callbackArgs));
}
}
component
filterSomething(something: Something, filterArgs: any[]) {
const firstArg = filterArgs[0];
const secondArg = filterArgs[1];
...
return <some condition based on something, firstArg, secondArg, etc.>;
}
html
<li *ngFor="let s of somethings | callback : filterSomething : [<whatWillBecomeFirstArg>, <whatWillBecomeSecondArg>, ...]">
{{s.aProperty}}
</li>
This is my code:
import {Pipe, PipeTransform, Injectable} from '#angular/core';
#Pipe({
name: 'filter'
})
#Injectable()
export class FilterPipe implements PipeTransform {
transform(items: any[], field : string, value): any[] {
if (!items) return [];
if (!value || value.length === 0) return items;
return items.filter(it =>
it[field] === value);
}
}
Sample:
LIST = [{id:1,name:'abc'},{id:2,name:'cba'}];
FilterValue = 1;
<span *ngFor="let listItem of LIST | filter : 'id' : FilterValue">
{{listItem .name}}
</span>
Another approach I like to use for application specific filters, is to use a custom read-only property on your component which allows you to encapsulate the filtering logic more cleanly than using a custom pipe (IMHO).
For example, if I want to bind to albumList and filter on searchText:
searchText: "";
albumList: Album[] = [];
get filteredAlbumList() {
if (this.config.searchText && this.config.searchText.length > 1) {
var lsearchText = this.config.searchText.toLowerCase();
return this.albumList.filter((a) =>
a.Title.toLowerCase().includes(lsearchText) ||
a.Artist.ArtistName.toLowerCase().includes(lsearchText)
);
}
return this.albumList;
}
To bind in the HTML you can then bind to the read-only property:
<a class="list-group-item"
*ngFor="let album of filteredAlbumList">
</a>
I find for specialized filters that are application specific this works better than a pipe as it keeps the logic related to the filter with the component.
Pipes work better for globally reusable filters.
I created the following pipe for getting desired items from a list.
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'filter'
})
export class FilterPipe implements PipeTransform {
transform(items: any[], filter: string): any {
if(!items || !filter) {
return items;
}
// To search values only of "name" variable of your object(item)
//return items.filter(item => item.name.toLowerCase().indexOf(filter.toLowerCase()) !== -1);
// To search in values of every variable of your object(item)
return items.filter(item => JSON.stringify(item).toLowerCase().indexOf(filter.toLowerCase()) !== -1);
}
}
Lowercase conversion is just to match in case insensitive way.
You can use it in your view like this:-
<div>
<input type="text" placeholder="Search reward" [(ngModel)]="searchTerm">
</div>
<div>
<ul>
<li *ngFor="let reward of rewardList | filter:searchTerm">
<div>
<img [src]="reward.imageUrl"/>
<p>{{reward.name}}</p>
</div>
</li>
</ul>
</div>
Ideally you should create angualr 2 pipe for that. But you can do this trick.
<ng-container *ngFor="item in itemsList">
<div*ngIf="conditon(item)">{{item}}</div>
</ng-container>
This is your array
products: any = [
{
"name": "John-Cena",
},
{
"name": "Brock-Lensar",
}
];
This is your ngFor loop
Filter By :
<input type="text" [(ngModel)]='filterText' />
<ul *ngFor='let product of filterProduct'>
<li>{{product.name }}</li>
</ul>
There I'm using filterProduct instant of products, because i want to preserve my original data.
Here model _filterText is used as a input box.When ever there is any change setter function will call.
In setFilterText performProduct is called it will return the result only those who match with the input. I'm using lower case for case insensitive.
filterProduct = this.products;
_filterText : string;
get filterText() : string {
return this._filterText;
}
set filterText(value : string) {
this._filterText = value;
this.filterProduct = this._filterText ? this.performProduct(this._filterText) : this.products;
}
performProduct(value : string ) : any {
value = value.toLocaleLowerCase();
return this.products.filter(( products : any ) =>
products.name.toLocaleLowerCase().indexOf(value) !== -1);
}
You can do this trick:
<ng-container *ngFor="item in items">
<div *ngIf="conditon(item)">{{ item.value }}</div>
</ng-container>
or
<div *ngFor="item in items">
<ng-container *ngIf="conditon(item)">{{ item.value }}</ng-container>
</div>
Here's an example that I created a while back, and blogged about, that includes a working plunk. It provides a filter pipe that can filter any list of objects. You basically just specify the property and value {key:value} within your ngFor specification.
It's not a lot different from #NateMay's response, except that I explain it in relatively verbose detail.
In my case, I filtered an unordered list on some text (filterText) the user entered against the "label" property of the objects in my array with this sort of mark-up:
<ul>
<li *ngFor="let item of _items | filter:{label: filterText}">{{ item.label }}</li>
</ul>
https://long2know.com/2016/11/angular2-filter-pipes/
The first step you create Filter using #Pipe in your component.ts file:
your.component.ts
import { Component, Pipe, PipeTransform, Injectable } from '#angular/core';
import { Person} from "yourPath";
#Pipe({
name: 'searchfilter'
})
#Injectable()
export class SearchFilterPipe implements PipeTransform {
transform(items: Person[], value: string): any[] {
if (!items || !value) {
return items;
}
console.log("your search token = "+value);
return items.filter(e => e.firstName.toLowerCase().includes(value.toLocaleLowerCase()));
}
}
#Component({
....
persons;
ngOnInit() {
//inicial persons arrays
}
})
And data structure of Person object:
person.ts
export class Person{
constructor(
public firstName: string,
public lastName: string
) { }
}
In your view in html file:
your.component.html
<input class="form-control" placeholder="Search" id="search" type="text" [(ngModel)]="searchText"/>
<table class="table table-striped table-hover">
<colgroup>
<col span="1" style="width: 50%;">
<col span="1" style="width: 50%;">
</colgroup>
<thead>
<tr>
<th>First name</th>
<th>Last name</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let person of persons | searchfilter:searchText">
<td>{{person.firstName}}</td>
<td>{{person.lastName}}</td>
</tr>
</tbody>
</table>
After some googling, I came across ng2-search-filter. In will take your object and apply the search term against all object properties looking for a match.
I was finding somethig for make a filter passing an Object, then i can use it like multi-filter:
i did this Beauty Solution:
filter.pipe.ts
import { PipeTransform, Pipe } from '#angular/core';
#Pipe({
name: 'filterx',
pure: false
})
export class FilterPipe implements PipeTransform {
transform(items: any, filter: any, isAnd: boolean): any {
let filterx=JSON.parse(JSON.stringify(filter));
for (var prop in filterx) {
if (Object.prototype.hasOwnProperty.call(filterx, prop)) {
if(filterx[prop]=='')
{
delete filterx[prop];
}
}
}
if (!items || !filterx) {
return items;
}
return items.filter(function(obj) {
return Object.keys(filterx).every(function(c) {
return obj[c].toLowerCase().indexOf(filterx[c].toLowerCase()) !== -1
});
});
}
}
component.ts
slotFilter:any={start:'',practitionerCodeDisplay:'',practitionerName:''};
componet.html
<tr>
<th class="text-center"> <input type="text" [(ngModel)]="slotFilter.start"></th>
<th class="text-center"><input type="text" [(ngModel)]="slotFilter.practitionerCodeDisplay"></th>
<th class="text-left"><input type="text" [(ngModel)]="slotFilter.practitionerName"></th>
<th></th>
</tr>
<tbody *ngFor="let item of practionerRoleList | filterx: slotFilter">...
Most simple and easy way to limit your ngFor is given below
<li *ngFor="let item of list | slice:0:10; let i=index" class="dropdown-item" >{{item.text}}</li>

Categories