Angular2 Auto suggester - json

I have been searching a lot on INTERNET but still unable to figure out how i can make custom auto suggester without any third party. After a lot of google I found this
but the issue is that my response from api is a bit different i am getting response as :
[{"id":"1","name":"aa"},{"id":"2","name":"bb"}...]
due to which i am getting [object][object] as value in pipe.
Can anyone please help how i can handle this request with this pipe. I want that their should be a text box on whose click there should be listing and on user input the below suggestions may vary.
Pipe:
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'FilterPipe',
})
export class FilterPipe implements PipeTransform {
transform(value: any, input: string) {
if (input) {
input = input.toLowerCase();
return value.filter(function (el: any) {
return el.toLowerCase().indexOf(input) > -1;
})
}
return value;
}
}
in ts:
import { Component } from '#angular/core';
import {FilterPipe} from './pipes'
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title:String;
names:any;
constructor(){
this.title = "Angular 2 simple search";
this.names = ['Prashobh','Abraham','Anil','Sam','Natasha','Marry','Zian','karan']
}
}
*** this works perfectly but in my case this.name array is deferent as told above.

Given the fact that the data source is an array of objects, to be dynamic I will use the following pipe that will iterate each object for values, then if a match is found will keep the object for display:
Pipe
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'filter',
pure: false
})
export class FilterPipe implements PipeTransform {
transform(items: Array<any>, filter: string): any {
if (!filter) {
return items;
}
return items.filter(item => {
for (let field in item) {
if (item[field] === null || item[field] === undefined){
continue;
}
if (item[field].toString().toLowerCase().indexOf(filter.toLowerCase()) !== -1) {
return true;
}
}
return false;
}
);
}
}
HTML
<input type="text" [(ngModel)]="search" placeholder="Filter...">
<div *ngFor="let item of datasource | filter:search"></div>
Look at pure: false declaration of the pipe. This tells the pipe to filter continuously the data, so if you have a service that is dynamically pushing data into your datasource, your filtered display will update automatically. Also using a pipe like this you can filter on all values of your objects and objects can change structure dynamically without impact on your filter.

You can try something similar like below
<input type="text" [(ngModel)]="queryString" id="search" placeholder="Search to type">
<ul>
<li *ngFor="let n of list | FilterPipe: queryString : searchableList ">
{{n.name}} ({{n.id}})
</li>
</ul>
pass required field to be searched
this.searchableList = ['name','id']
pipe
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'FilterPipe',
})
export class FilterPipe implements PipeTransform {
transform(value: any, input: string,searchableList : any) {
if (input) {
input = input.toLowerCase();
return value.filter(function (el: any) {
var isTrue = false;
for(var k in searchableList ){
if(el[searchableList[k]].toLowerCase().indexOf(input) > -1){
isTrue = true;
}
if(isTrue){
return el
}
}
})
}
return value;
}
}
You can update the pipe according to your needs.

Related

Angular: ERROR TypeError: Cannot read property 'choice_set' of null, while data displayed correctly

i hope you're doing well.
I am trying to implement a FormsBuilder in Angular by accessing the data from an API. The data is pushed down to its child-component via #Input().
However the data gets pushed down, are provided and shown successfully, but still I get this Error, when the first attempt from ngOnChangess tries to receive the data.
ERROR TypeError: Cannot read property 'choice_set' of null
at StyleTypeQuestionComponent.setFormValues (style-type-question.component.ts:34)
at StyleTypeQuestionComponent.ngOnChanges (style-type-question.component.ts:26)
at StyleTypeQuestionComponent.rememberChangeHistoryAndInvokeOnChangesHook (core.js:1471)
at callHook (core.js:2490)
at callHooks (core.js:2457)
at executeInitAndCheckHooks (core.js:2408)
at refreshView (core.js:9207)
at refreshEmbeddedViews (core.js:10312)
at refreshView (core.js:9216)
at refreshComponent (core.js:10358)
The data is provided through an data-service and are subscribed through an async pipe from its parent-component and as mentioned above pushed down via property binding.
I tried to use the ? operator in my template and tried to set an Timeout on the childcomponent. Also i tried to initialize the data via default values. Still thats making no sense for me right know, because the data is already available through his parent component and getting checked via an *ngIf directive.
I hope i could provided as much as information as needed.
I guess there is an initializing problem in the first seconds of ngChanges.
Parent-Component
import { Component, Input, OnChanges, OnInit } from '#angular/core';
import { Question } from '../shared/models/question';
import { QuestionStoreService } from '../shared/question-store.service';
import { Observable } from 'rxjs';
#Component({
selector: 'pc-style-type-detection',
templateUrl: './style-type-detection.component.html',
styleUrls: ['./style-type-detection.component.css'],
})
export class StyleTypeDetectionComponent implements OnInit, OnChanges {
question$: Observable<Question>;
#Input() question_Input: Question;
question_index: number = 1;
constructor(private qs: QuestionStoreService) {}
ngOnInit(): void {
this.question$ = this.qs.getSingle(1);
}
ngOnChanges(): void {}
updateBook(question: Question): void {
console.log(question);
}
}
Parent-Template
<pc-style-type-question
*ngIf="question$"
(submitQuestion)="updateBook($event)"
[question]="question$ | async"
></pc-style-type-question>
Child-Component
import {
Component,
EventEmitter,
Input,
OnChanges,
OnInit,
Output,
} from '#angular/core';
import { FormArray, FormBuilder, FormGroup } from '#angular/forms';
import { Choice, Question } from '../shared/models/question';
#Component({
selector: 'pc-style-type-question',
templateUrl: './style-type-question.component.html',
styleUrls: ['./style-type-question.component.css']
})
export class StyleTypeQuestionComponent implements OnInit, OnChanges {
questionForm: FormGroup;
#Input() question: Question;
#Output() submitQuestion = new EventEmitter<Question>();
constructor(private fb: FormBuilder) {}
ngOnChanges(): void {
this.initForm();
this.setFormValues(this.question);
}
ngOnInit(): void {
this.initForm();
}
private setFormValues = (question: Question) => {
this.questionForm.patchValue(question.choice_set);
this.questionForm.setControl(
'choice_set',
this.buildChoiceSetArray(question.choice_set)
);
};
initForm = () => {
if (this.questionForm) {
return;
}
this.questionForm = this.fb.group({
choice_set: this.buildChoiceSetArray([
{
choice_text: '',
choice_value: false,
},
]),
});
};
get choiceSet(): FormArray {
return this.questionForm.get('choice_set') as FormArray;
}
private buildChoiceSetArray = (values: Choice[]): FormArray => {
if (values) {
return this.fb.array(
values.map((choice) => {
return this.fb.control(choice.choice_value);
})
);
}
return this.fb.array(
this.question.choice_set.map((choices) =>
this.fb.control(choices.choice_value)
)
);
};
submitForm() {}
}
Child-Template
<form class="ui form" [formGroup]="questionForm" (ngSubmit)="submitForm()">
<div
formArrayName="choice_set"
*ngFor="let choiceset of choiceSet?.controls; index as i"
>
<div>
<input type="checkbox" [formControl]="choiceset" />
<label>
{{ question.choice_set[i].choice_text }}
</label>
</div>
</div>
</form>
Thank you in advance and wish you a nice weekend.
You are not using ngOnChanges the right way, its going to be triggered everytime your input change no matter what the value is which means you need to check if that value is what you expect it to be with SimpleChanges.
ngOnChanges(changes: SimpleChanges) {
if(changes.question.currentValue) {
this.initForm();
this.setFormValues(this.question);
}
}

ERROR Error: InvalidPipeArgument: '[object Object]' for pipe 'AsyncPipe' even when returning an observable

I found few questions with same title and, as far as I could see, some of them are suggesting that the solution is basically return an Observable instead of an array (others are about FireBase which isn't my case). Well, as far I am concern, the code below does return an Observable (look at "getServerSentEvent(): Observable {return Observable.create ...")
My final goal is get all events from a stream returned from a Rest WebFlux. I didn't past bellow the backend because I am pretty sure the issue is related to some mistake in Angular.
On top of that, I can debug and see the events properlly comming to extratos$ from app.component.ts(see image bellow).
Whole logs
core.js:6185 ERROR Error: InvalidPipeArgument: '[object Object]' for pipe 'AsyncPipe'
at invalidPipeArgumentError (common.js:5743)
at AsyncPipe._selectStrategy (common.js:5920)
at AsyncPipe._subscribe (common.js:5901)
at AsyncPipe.transform (common.js:5879)
at Module.ɵɵpipeBind1 (core.js:36653)
at AppComponent_Template (app.component.html:8)
at executeTemplate (core.js:11949)
at refreshView (core.js:11796)
at refreshComponent (core.js:13229)
at refreshChildComponents (core.js:11527)
app.component.ts
import { Component, OnInit } from '#angular/core';
import { AppService } from './app.service';
import { SseService } from './sse.service';
import { Extrato } from './extrato';
import { Observable } from 'rxjs';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
providers: [SseService],
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
//extratos: any;
extratos$ : Observable<any>;
constructor(private appService: AppService, private sseService: SseService) { }
ngOnInit() {
this.getExtratoStream();
}
getExtratoStream(): void {
this.sseService
.getServerSentEvent("http://localhost:8080/extrato")
.subscribe(
data => {
this.extratos$ = data;
}
);
}
}
sse.service.ts
import { Injectable, NgZone } from '#angular/core';
import { Observable } from 'rxjs';
import { Extrato } from './extrato';
#Injectable({
providedIn: "root"
})
export class SseService {
extratos: Extrato[] = [];
constructor(private _zone: NgZone) { }
//getServerSentEvent(url: string): Observable<Array<Extrato>> {
getServerSentEvent(url: string): Observable<any> {
return Observable.create(observer => {
const eventSource = this.getEventSource(url);
eventSource.onmessage = event => {
this._zone.run(() => {
let json = JSON.parse(event.data);
this.extratos.push(new Extrato(json['id'], json['descricao'], json['valor']));
observer.next(this.extratos);
});
};
eventSource.onerror = (error) => {
if (eventSource.readyState === 0) {
console.log('The stream has been closed by the server.');
eventSource.close();
observer.complete();
} else {
observer.error('EventSource error: ' + error);
}
}
});
}
private getEventSource(url: string): EventSource {
return new EventSource(url);
}
}
app.component.html
<h1>Extrato Stream</h1>
<div *ngFor="let ext of extratos$ | async">
<div>{{ext.descricao}}</div>
</div>
Evidence that the observable extratos$ is filled in
When you write this observer.next(this.extratos);, that means this.extratos is what you get on component side in the data argument of the callback, so when you do this this.extratos$ = data; you are actually storing the extratos Array. TypeScript doesn't complain about it, probably because it's not smart enough to infer the types when you build an Observable from scratch like you did.
Try this:
this.extratos$ = this.sseService
.getServerSentEvent("http://localhost:8080/extrato");
and in the template: <div *ngFor="let ext of extratos$ | async">

How to iterate over json data if there is anr array inside an array in the JSON output

I have to iterate over the templte by adding the index like
{{single.amazon_data[i].Category}}
here is api :https://test.pickcel.com/api/v1/getAmazonDeals. I have to explicitly provide the index of the array how to automate it?
</component.ts>
/*this list component calls api to fetch deals from amazon*/
import { Component, OnInit } from '#angular/core';
/*importing services*/
import { DealsService } from '../deals.service'
#Component({
selector: 'app-list',
templateUrl: './list.component.html',
styleUrls: ['./list.component.css']
})
export class ListComponent implements OnInit {
public deals;
constructor(public dealsHttpService : DealsService) {
console.log('List component constructor is called');
}
ngOnInit() {
console.log('List component onInit called');
/*subscribing to observables*/
this.dealsHttpService.getAmazonDeals().subscribe(
data=>{
console.log('logging data');
this.deals = data["data"];
console.log(this.deals)
},
/*handle error if not subscribed*/
error => {
console.log("some error occured");
console.log(error.errorMessage);
}
)
}
}
/>
</html template
<td>{{single.amazon_data[0].Category}}</td>
<td>{{single.amazon_data[0].Title}}</td>
<td>{{single.amazon_data[0].Details}}</td>
/>
You can use *ngFor="let item of deals.amazon_data"
<ng-container *ngFor = "let item of deals">
<tr *ngFor="let item1 of item.amazon_data">
<td>{{item1.Category}}</td>
...
</ng-container>
Hope this helps !

Highlight the search text in angular 2

I am pretty new to Angular 2. I am trying to achieve the same task as Highlight the search text - angular 2 what is mentioned in above post.
I have created the pipe filter my question is where should i keep the pipe filter and where should i place the inner html div.
Copying the problem:
A messenger displays the search results based on the input given by the user. Need to highlight the word that is been searched, while displaying the result. These are the html and component that is been used.
component.html
<div *ngFor = "let result of resultArray">
<div>Id : result.id </div>
<div>Summary : result.summary </div>
<div> Link : result.link </div>
</div>
Component.ts
resultArray : any = [{"id":"1","summary":"These are the results for the searched text","link":"http://www.example.com"}]
This resultArray is fetched from hitting the backend service by sending the search text as input. Based on the search text, the result is fetched. Need to highlight the searched text, similar to google search.
How should i apply the search filter and where should i keep the inner html?
There are some tweaks to be made to the regex replacement regarding case, but here's a starting point:
//our root app component
import {Component, NgModule, VERSION, Pipe, PipeTransform} from '#angular/core'
import {BrowserModule, DomSanitizer} from '#angular/platform-browser'
#Pipe({
name: 'highlight'
})
export class HighlightSearch implements PipeTransform {
constructor(private sanitizer: DomSanitizer){}
transform(value: any, args: any): any {
if (!args) {
return value;
}
// Match in a case insensitive maneer
const re = new RegExp(args, 'gi');
const match = value.match(re);
// If there's no match, just return the original value.
if (!match) {
return value;
}
const result = value.replace(re, "<mark>" + match[0] + "</mark>");
return this.sanitizer.bypassSecurityTrustHtml(result);
}
}
#Component({
selector: 'my-app',
template: `
<input (input)="updateSearch($event)">
<div *ngFor="let result of resultArray" [innerHTML]="result.summary | highlight: searchTerm"></div>
`,
})
export class App {
results: string[]
searchTerm: string;
constructor() {
this.resultArray = [
{
"id": "1",
"summary": "These are the results for the searched text",
"link": "http://www.example.com"
},
{
"id": "2",
"summary": "Here are some more results you searched for",
"link": "http://www.example.com"
}
]
}
updateSearch(e) {
this.searchTerm = e.target.value
}
}
#NgModule({
imports: [ BrowserModule ],
declarations: [ App, HighlightSearch ],
bootstrap: [ App ]
})
export class AppModule {}
Plnkr
Edit: Plnkr seems unhappy. StackBlitz
you can easily use this directive
usage:
<label [jsonFilter]="search">{{text}}</label>
directive
import {
AfterViewInit,
Directive,
ElementRef,
Input,
OnChanges,
SimpleChanges
} from '#angular/core';
#Directive({
selector: '[jsonFilter]'
})
export class FilterDirective implements OnChanges, AfterViewInit {
#Input() jsonFilter = '';
constructor(
private el: ElementRef,
) {
}
ngOnChanges(changes: SimpleChanges) {
this.ngAfterViewInit();
}
ngAfterViewInit() {
const value = this.el.nativeElement?.innerText.split('\n').join('');
if (!value) return;
const re = new RegExp(this.jsonFilter, 'gi');
const match = value.match(re);
if (!match || match.some(x => !x)) {
this.el.nativeElement.innerText = value;
} else {
this.el.nativeElement.innerHTML = value.replace(re, "<mark>" + match[0] + "</mark>")
}
}
}

Display Subscribe Data in Angular 4

I need help in displaying the output of subscribe from an api in Angular 4. How can i do this since I wrote data.data.data but it says property data doesnt exist on type object. How would i output it in the browser? Here's my code below and the api picture below
import { Component, OnInit } from '#angular/core';
import { NewsService } from '../news.service';
#Component({
selector: 'app-news-list',
templateUrl: './news-list.component.html',
styleUrls: ['./news-list.component.css']
})
export class NewsListComponent implements OnInit {
constructor(private newsService: NewsService) { }
ngOnInit() {
this.newsService.getNews()
.subscribe(
data => {
alert("News Success");
console.log(data);
},
error => {
alert("ERROR");
});
}
}
create a property in component
myData: any[] = [];
and in your subscriber function
import { Component, OnInit } from '#angular/core';
import { NewsService } from '../news.service';
#Component({
selector: 'app-news-list',
templateUrl: './news-list.component.html',
styleUrls: ['./news-list.component.css']
})
export class NewsListComponent implements OnInit {
constructor(private newsService: NewsService) { }
ngOnInit() {
this.newsService.getNews()
.subscribe(
(res: any) => {
alert("News Success");
this.myData = res.data;
// Where you find the array res.data or res.data.data
console.log('res is ', res.data);
},
error => {
alert("ERROR");
});
}
}
and in your template
1) option to view JSON
<pre>{{myData | json}}</pre>
2) option to loop if you got the array
<div *ngFor="let d of myData">
{{d}}
</div>
Your data is type of array,
Create a variable of type any
myData : any;
and assign the data to myData,
this.newsService
.getNews()
.subscribe(
data => {
this.myData = data.data;
},
error => {
alert("ERROR");
}
);
you can use ngFor to iterate over array and display in the HTML
<li *ngFor="let item of myData">
{{item}}
</li>
You need to do it like this
ngOnInit() {
this.newsService.getNews()
.subscribe(
data => {
data = data.json();
console.log(data.data);
},
error => {
alert("ERROR");
});
}
the data.json() part is important, it converts the response into proper json so can access its data.
Now you can assign it to instance variable like this
this.myArrayData = data.data
inside your subscribe() method
Then in your template
<div *ngFor="let data of myArrayData">
<!-- do whatever with the data properties -->
</div>