I am building my first Angular app and need some help. The component I'm working on is an image search box. The user enters search query, request is sent to API, API responds with JSON data. Why is my *ngFor loop not working? The iterable is updated when the server sends response.
image-search.component.ts
import { Component, OnInit } from '#angular/core';
import { ImageSearchService } from './image-search.service';
import { Image } from '../shared/image';
#Component({
selector: 'vb-image-search',
templateUrl: './image-search.component.html',
styleUrls: ['./image-search.component.css'],
providers: [ImageSearchService]
})
export class ImageSearchComponent implements OnInit {
images: Image[] = [];
constructor(private ImageSearchService: ImageSearchService) { }
ngOnInit() {
}
getImages(query: string) {
this.ImageSearchService.getImages(query)
.subscribe(function(images) {
this.images = images;
});
}
onClick(query:string) {
this.getImages(query);
}
}
image-search.service.ts
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { Http, Response } from '#angular/http';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
import { Image } from '../shared/image';
#Injectable()
export class ImageSearchService {
constructor(private http: Http) {}
getImages(query: string): Observable<any[]> {
return this.http.get(`http://localhost:3000/api/search/${query}`)
.map(this.extractData)
}
private extractData(res: Response) {
let body = res.json();
return body.data.map(e => new Image(e.farmID, e.serverID, e.imageID, e.secret)) || {};
}
}
image.ts
export class Image {
constructor(public farmID: string, public serverID: string, public imageID: string, public secret: string) {
this.farmID = farmID;
this.serverID = serverID;
this.imageID = imageID;
this.secret = secret;
}
}
image-search.component.html
<div class="col-lg-6 col-md-6">
<div class="input-group">
<input type="text" [(ngModel)]="query" class="form-control" placeholder="Search for images..." />
<span class="input-group-btn">
<button (click)="onClick(query)" class="btn btn-default" type="button">Go!</button>
</span>
</div>
<h2>Images</h2>
<div *ngFor="let image of images">
{{image.imageID}}
</div>
</div>
The reason is very simple. In typescript the function call back loses the current scope if you use function(){} so instead you have to used => {} to retain the current scope. So please modify your current getImages method as mentioned below:
getImages(query: string) {
this.ImageSearchService.getImages(query)
.subscribe(images => {
this.images = images;
});
}
Related
Recently I delve into Angular through some videos and start creating an website/app.
It's an website/app that an user can login and add their restaurants that get stored in a array, in Firebase. All very simple. The most important point in this project is that there is a page that is called generate and its objective is after clicking a button it would generate randomly one restaurant, from those firebase restaurants.
I can't seem to understand what I'm doing wrong. The closest that I have been was to generate randomly the number(length) of the array.
After this I want to display it on the HTML code.
Here is my code
data-storage.service.ts
import { Injectable } from "#angular/core";
import { HttpClient } from "#angular/common/http";
import {RestaurantService} from "../addremove/restaurant.service";
import {Restaurant} from "./restaurant.model";
import {tap, map} from "rxjs/operators";
#Injectable({providedIn: 'root'})
export class DataStorageService {
constructor(private http: HttpClient, private restaurantService: RestaurantService) {
}
storeRestaurants() {
const restaurant = this.restaurantService.getRestaurant();
this.http.put('url/restaurants.json', restaurant)
.subscribe(response => {console.log(response);
});
}
fetchRestaurants() {
return this.http.get<Restaurant[]>('url/restaurants.json')
.pipe(tap(restaurant => {
this.restaurantService.setRestaurants(restaurant)
}))
}
}
restaurant.service.ts
import {Restaurant} from "../shared/restaurant.model";
import { Subject} from "rxjs";
import {Injectable} from "#angular/core";
#Injectable()
export class RestaurantService {
restaurantChanged = new Subject<Restaurant[]>();
restaurantSelected = new Subject<Restaurant>();
private restaurant: Restaurant[] = [];
setRestaurants(restaurants: Restaurant[]) {
this.restaurant = restaurants;
this.restaurantChanged.next(this.restaurant.slice());
}
//Function to get all te restaurants. Return them from the component
getRestaurant() {
return this.restaurant.slice();
}
//Get Single Restaurant
getRestaurantId (index: number) {
return this.restaurant[index];
}
addRestaurant(restaurant: Restaurant) {
this.restaurant.push(restaurant);
this.restaurantChanged.next(this.restaurant.slice());
}
updateRestaurant(index: number, newRestaurant: Restaurant){
this.restaurant[index] = newRestaurant;
this.restaurantChanged.next(this.restaurant.slice());
}
deleteRestaurant(index: number) {
this.restaurant.splice(index, 1);
this.restaurantChanged.next(this.restaurant.slice());
}
}
generate.component.html
<div class="row">
<label>Category</label>
<div>
Dropmenu select
</div>
<button class="btn btn-primary" (click)="onClick()">Random Pick</button>
</div>
<hr>
<div class="row">
<p> PLACE TO PUT THE RESULT OF THE RANDOM PICK RESTAURANT</p>
</div>
generate.component.ts
import {Component, OnInit} from '#angular/core';
import {RestaurantService} from "../addremove/restaurant.service";
import {GenerateService} from "./generate.service";
import {DataStorageService} from "../shared/data-storage.service";
import {Restaurant} from "../shared/restaurant.model";
#Component({
selector: 'app-generate',
templateUrl: './generate.component.html',
styleUrls: ['./generate.component.css']
})
export class GenerateComponent implements OnInit {
collapsed = true;
constructor( private restaurantService: RestaurantService, private generateService: GenerateService,
private dataStorageService: DataStorageService) { }
ngOnInit(): void {
}
onClick() {
var result = String;
var random = result(Math.floor(Math.random() * Restaurant.length));
console.log(random);
}
}
Firebase
Firebase - restaurants
I am trying to pass the items written in a text-box field on my webpage to variables in the component file. These variables will then be used in the service file that has a function link to the POST request I made on the python/SQLAlchemy side. I am using Flask with python/SQLAlchemy on the backend, and Angular CLI 7.0.5. Here's what I have:
<div class = "container">
<input id="InsertItemName" [(ngModel)]="InsertItemName"/>
<input id="InsertItemManu" [(ngModel)]="InsertItemManu"/>
<input id="InsertItemType" [(ngModel)]="InsertItemType"/>
<button (click)="sendValues()">Send</button>
</div>
modify-page.component.html:
import { Component, OnInit, Input } from '#angular/core';
import { ActivatedRoute, Data } from '#angular/router';
import { HttpResponse } from '#angular/common/http';
import { SelectItem } from 'primeng/components/common/selectitem';
import { MenuItem } from 'primeng/api';
import { ModifyService, ItemsData } from '../modify.service'
import { PARAMETERS } from '#angular/core/src/util/decorators';
#Component({
selector: 'app-modify-page',
templateUrl: './modify-page.component.html',
styleUrls: ['./modify-page.component.css']
})
export class ModifyPageComponent implements OnInit {
InsertItemName: string;
InsertItemManu: string;
InsertItemType: string;
insertItems: ItemsData;
constructor(
private modifyService: ModifyService,
private route: ActivatedRoute
) {
}
ngOnInit() {
this.insertItems.name = this.InsertItemName;
this.insertItems.manufacture = this.InsertItemManu;
this.insertItems.type = this.InsertItemType;
}
sendValues(): void {
console.log(this.InsertItemName, this.InsertItemManu, this.InsertItemType)
this.modifyService.postInputItems(this.insertItems)
}
}
modify.service.ts:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
export interface ItemsData {
name: string;
manufacture: string;
type: string;
}
#Injectable({
providedIn: 'root'
})
export class ModifyService {
constructor(
public http: HttpClient
) { }
postInputItems(data: ItemsData){
return this.http.post('/modify/insertItems', data)
}
}
And if you need it, this is the database.py:
def insert_dbItems(a, b, c):
with engine.connect() as con:
ins = Items.insert().values(name = a, manufacture = b, type = c)
con.execute(ins)
return "Successfully inserted data into Items table"
and the init.py:
#app.route('/api/modify/insertItems', methods=["POST"])
def insert_Itemsdb():
body = json.loads(request.data)
a = body['name']
b = body['manufacture']
c = body['type']
return jsonify(database.insert_dbItems(a, b, c))
The database and init files work, I can use the Postman app and correctly insert the variables into my database from it. My problem is that when I run all the code above, I get left with this:
enter image description here
In conclusion: everything here is for me to take an input from a user and insert it into my database. Any help would be fantastic, thank you!
I think you forgot to initialize insertItems at the top of your ModifyPageComponent.
insertItems: ItemsData = {
name: null;
manufacture: null;
type: null;
}
Hope that will help!
There are two address shipping and billing Country. Both has different value.
Components are reused here.
A select-country component was made for this .
<select-country [addressType]="'shipping'"></select-country>
<select-country [addressType]="'billing'"></select-country>
The type can be shipping or billing.
Now in select-country
import { Component, OnInit,ViewChild, ElementRef,Input } from '#angular/core';
import { ConfigService } from '../services/config.service';
import { DataService } from '../services/data.service';
import { CheckOutService } from '../services/checkout/check-out.service';
import { HomeService } from './../services/banner/home.service';
import {MdlService} from './../services/material-design-lite/mdl.service';
import { Http } from "#angular/http";
import { apiUrl,used_currency,used_language } from './../services/global.constant';
import { Router,ActivatedRoute } from '#angular/router';
import {NgSelectModule, NgOption} from '#ng-select/ng-select';
import {HttpClient, HttpClientModule} from '#angular/common/http';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/switchMap';
declare var componentHandler: any;
#Component({
moduleId: module.id + '',
selector: 'select-country',
templateUrl: './get-country.component.html',
styleUrls: ['./get-country.component.css']
})
export class GetCountryComponent implements OnInit {
#Input('addressType') addressType;
searchQuery: string = '';
items;va;
countries = new Array;
constructor(public http: Http,public router: Router,
public configz: ConfigService,public shared: DataService,
private checkOutservice: CheckOutService ,
private service: HomeService, public material:MdlService) {
if(this.addressType=='shipping')
{va=shared.orderDetails.delivery_country}
else{va=shared.orderDetails.billing_country}
var data = { type: 'null' };
http.post(this.configz.url + 'getCountries', data).map(res => res.json()).subscribe(data => {
this.items = this.countries = data.data;
console.log(this.items);
setTimeout(() => { this.material.render(); }, 550);
});
}
ngAfterViewInit(){
}
ngOnInit() {
}
static mdlWrapper(element: ElementRef) {
componentHandler.upgradeElement(element.nativeElement);
}
}
<div tabindex="-1">
<select [(ngModel)]="shared.orderDetails.delivery_country" name="orderby" >
<option *ngFor="let item of items" value="" >{{ item.countries_name }}</option>
</select>
</div>
Shared is service which is shared among all component.
For shipping shared.orderDetails.delivery_country is used and for billing shared.orderDetails.billing_country
How to dynamically change ngModel and set shared.orderDetails .
I am making changes in shared only because there are multiple component and they need to share the same service to retain data.
EDIT : I tried setting a variable in Get Country Component. Edited it please check. It does not update the shared.orederDetails.
Why don't you build the <select-country> as a proper form control? That way you can use ngModel binding and forget about the details. Something like this:
<form>
<input [(ngModel)]="order.name" name="name">
<input [(ngModel)]="order.count" name="count">
<select-country [(ngModel)]="order.shippingCountry" name="shippingCountry"></select-country>
<select-country [(ngModel)]="order.billingCountry" name="billingCountry"></select-country>
</form>
The component you can build like this:
import { Component, Input, forwardRef, Output, EventEmitter, OnInit } from '#angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '#angular/forms';
import { HttpClient } from '#angular/common/http';
interface Country {
value: string;
countryName: string;
}
const SELECT_COUNTRY_VALUE_ACCESSOR = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SelectCountryComponent),
multi: true,
};
#Component({
selector: 'select-country',
template: `
<div tabindex="-1">
<select [(ngModel)]="value" name="orderby" (ngModelChange)="updateValue($event)">
<option *ngFor="let country of countries" [selected]="country.value === value?.value" [value]="country.value">{{ country.countryName }}</option>
</select>
</div>`,
styles: [`:host { display: inline-block; }`],
providers: [SELECT_COUNTRY_VALUE_ACCESSOR]
})
export class SelectCountryComponent implements ControlValueAccessor, OnInit {
_value: Country;
countries = []
private onChangeCallback: Function;
private onTouchedCallback: Function;
constructor(private http: HttpClient) {}
ngOnInit() {
this.http.get('https://www.mocky.io/v2/5a90341e2f000061006caba7')
.subscribe((countries: Country[]) => this.countries = countries,
err => console.error('Error getting countries:', err));
}
/** that mock should return something like this:
* [{
"countryName": "Ireland",
"value": "IRELAND" // or whatever
}, ...]
*/
// update coming from external, e.g. form control
writeValue(value) {
// outside change of selected item
console.log('Update value:', value);
this._value = value;
}
// update coming from the view (our dropdown), we update it and then go on to inform Angular Forms.
// called by the dropdown selector.
updateValue(value) {
this._value = this.countries.filter(country => country.value === value)[0];
this.valueChange();
}
private valueChange() {
this.onTouchedCallback();
this.onChangeCallback(this._value);
}
// these two are set
registerOnChange(onChangeCallback: Function) {
this.onChangeCallback = onChangeCallback;
}
registerOnTouched(onTouchedCallback: any) {
this.onTouchedCallback = onTouchedCallback;
}
}
Of course, adjust to oyur needs, add validation, styling etc. You can see it in action here: https://stackblitz.com/edit/angular-country-picker?file=app/app.component.html
In addition to my other answer (with proposal to make a lil' standalone component you could reuse elsewhere), let's try a more direct approach, fixing your code:
First, your template options all lack values. Even when your user picks something, ngModel doesn't change (because it's always the same value, "").
<div tabindex="-1">
<select [(ngModel)]="shared.orderDetails.delivery_country" name="orderby">
<option *ngFor="let item of items" [value]="item.countries_name">{{ item.countries_name }}</option>
</select>
</div>
Second, your init logic is in the constructor. At that instance, you still don't have your inputs settled. Move init parts to your ngOnInit.
constructor(public http: Http,
public router: Router,
public configz: ConfigService,
public shared: DataService,
private checkOutservice: CheckOutService,
private service: HomeService,
public material:MdlService) {
}
ngOnInit() {
if(this.addressType === 'shipping') {
this.va = shared.orderDetails.delivery_country
} else {
this.va = shared.orderDetails.billing_country;
}
const data = { type: 'null' };
http.post(this.configz.url + 'getCountries', data).map(res => res.json()).subscribe(data => {
this.items = this.countries = data.data;
console.log(this.items);
setTimeout(() => { this.material.render(); }, 550);
});
}
Please help my subscribe to changed variable. I make simply spinner. Spinner state(true|false) storage in service:
import { Injectable } from '#angular/core';
import { Response, Headers, URLSearchParams } from '#angular/http';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class GlobalVarsService {
private isVisibleSpinner: boolean = false;
getSpinnerState(): Observable<boolean> {
return this.isVisibleSpinner;
};
setSpinnerState(state): void {
console.log('setSpinnerState', state);
this.isVisibleSpinner = state;
};
}
In component-template i display spinner via condition:
<div class="nav">
<a [routerLink]="['/select']">select</a>
<a [routerLink]="['/output']">output</a>
</div>
<router-outlet></router-outlet>
<div class="spinner-backdrop" *ngIf="isVisibleSpinner"></div>
<div class="spinner-area" *ngIf="isVisibleSpinner">
<span class="spinner">loading...</span>
</div>
In component i try subscribe to change in service isVisibleSpinner variable:
import { Component } from '#angular/core';
import { Response } from '#angular/http';
import 'rxjs/add/operator/map'
import { GlobalVarsService } from './services/global-vars.service';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
private isVisibleSpinner: boolean;
constructor(private globalVarsService: GlobalVarsService) {
this.globalVarsService.getSpinnerState().subscribe(data => {
console.log(data);
this.isVisibleSpinner = data;
});
}
}
But console output follow eror message:
Type 'boolean' is not assignable to type 'Observable'.
because you are returning an observable and putting the value in boolean, you can change the type of your is variable to Observable and use the async pipe to get the value or map your "data" to boolean if thats what you want
export class AppComponent {
private isVisibleSpinner: Observable<boolean>;
constructor(private globalVarsService: GlobalVarsService) {
this.globalVarsService.getSpinnerState().subscribe(data => {
console.log(data);
this.isVisibleSpinner = data;
});
}
}
app.component.html
<div class="spinner-backdrop" *ngIf="isVisibleSpinner | async"></div>
<div class="spinner-area" *ngIf="isVisibleSpinner | async">
<span class="spinner">loading...</span>
</div>
My http-data.service accepts json for output in the component template. Initially, the console shows that the first few calls are given undefined, and the following calls are already taking json, but also if you check the component, then the component shows that the method that outputs the data to the component is called only once and since the data has not yet arrived it writes undefined , But not updated after the arrival of json. Help please understand why? Thank you
My http-data.service:
import {Injectable} from '#angular/core';
import {Http} from '#angular/http';
import {Response} from '#angular/http';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/map';
#Injectable()
export class HttpService{
constructor(private http: Http) {}
getDataOrganizations(): Observable<any[]>{
return this.http.get('http://localhost:3010/data')
.map((resp:Response)=>{
let dataOrganizations = resp.json().organization;
return dataOrganizations;
});
}
getDataModules(): Observable<any[]> {
return this.http.get('http://localhost:3010/data')
.map((resp: Response)=> {
let dataModules = resp.json().modules;
return dataModules;
});
}
getDataPresets(): Observable<any[]> {
return this.http.get('http://localhost:3010/data')
.map((resp: Response)=> {
let dataPresets = resp.json().presets;
return dataPresets;
});
}
getDataModuleItems(): Observable<any[]> {
return this.http.get('http://localhost:3010/data')
.map((resp: Response)=> {
let dataModuleItems = resp.json().module_items;
return dataModuleItems;
});
}
}
My data-all.service
import { Injectable, EventEmitter } from '#angular/core';
import {Response} from '#angular/http';
import { ModuleModel } from './model-module';
import { ModuleItemsModel } from './model-module-items';
import data from '../data/data-all';
import { PriceService } from './price.service';
import { HttpService } from './http-data.service';
#Injectable()
export class ModuleDataService {
constructor(private priceService: PriceService, private httpService: HttpService){
this.dataMinMaxSum = {minSum: 0, maxSum: 0}
}
private currentPopupView: EventEmitter<any> = new EventEmitter<any>();
private dataModules: ModuleModel[] = this.getDataModules();
private dataMinMaxSum: {};
private dataCalculateVariationOrg: any[];
private dataChangeExecutor: any[];
subscribe(generatorOrNext?: any, error?: any, complete?: any) {
this.currentPopupView.subscribe(generatorOrNext, error, complete);
}
calculte(){
return this.priceService.getDataPrice();
}
getDataModules(){
this.httpService.getDataModules().subscribe(((modules)=>{this.dataModules = modules; console.log(this.dataModules);}));
console.log('dataModules');
console.log(this.dataModules);
return this.dataModules;
}
---------------------------------------------------------------------------
}
My left-block.component
import { Component, OnInit} from '#angular/core';
import { ModuleDataService } from '../../service/data-all.service';
import { ModuleModel } from '../../service/model-module';
#Component({
moduleId: module.id,
selector: 'modules-left-block',
templateUrl: './modules-left-block.html',
styleUrls: ['modules-left-block.css']
})
export class ModuleLeft implements OnInit{
modules: ModuleModel[];
constructor(private modulesAll: ModuleDataService){}
ngOnInit(){
this.modules = this.modulesAll.getDataModules();
console.log("view");
console.log(this.modulesAll.getDataModules());
}
onToggle(module: any){
this.modulesAll.toggleModules(module);
}
}
My left-block.component.html
<div class="modules-all">
<div class="modules-all-title">Все модули</div>
<div class="module-item" *ngFor="let module of modules" [ngClass]="{ 'active': module.completed }" (click)="onToggle(module)">{{module?.title}}</div>
</div>
In the component this.modulesAll.getDataModules () method is why it is executed only once without updating (write in console => undefined), if there are any thoughts, write, thanks.
This behaviour is due to the .subscribe() method does not wait for the data to arrive and I'm guessing you already know this. The problem you're facing is because, you have .subscribe to the getDataModules() service in the wron place. You shouldn't subscribe to a service in another service (at leat in this case). Move the subscribe method to the left-block.component and it should work.
getDataModules() {
this.httpService.getDataModules().subscribe(((modules) => {
this.dataModules = modules;
console.log(this.dataModules);
}));
console.log('dataModules');
console.log(this.dataModules);
return this.dataModules;
}
It should look somethig like this:
#Component({
moduleId: module.id,
selector: 'modules-left-block',
templateUrl: './modules-left-block.html',
styleUrls: ['modules-left-block.css']
})
export class ModuleLeft implements OnInit {
modules: ModuleModel[] = new ModuleModel();
constructor(private modulesAll: ModuleDataService, private httpService: HttpService) {}
ngOnInit() {
this.getDataModles();
//this.modules = this.modulesAll.getDataModules();
console.log("view");
//console.log(this.modulesAll.getDataModules());
}
onToggle(module: any) {
this.modulesAll.toggleModules(module);
}
getDataModules(): void {
this.httpService.getDataModules().subscribe(((modules) => {
this.modules = modules;
console.log(this.dataModules);
}));
}
}