Angular2(v4.2.5) click event doesn't work inside *ngIF - javascript

I have the following problem in Angular: as I understand, *ngIf directive doesn't allow me to add event on nesting DOM's component.
This is the code:
Template
<div class="bf-search-field">
<input #productSearch
[(ngModel)]="term"
(ngModelChange)="search()"
(blur)="closeAutocomplete()"
(focus)="activeAutocomplete()"
autocomplete="off" type="search" id="search"
placeholder="Search black friday deals"
/>
<button class="bf-search-field__search-btn"></button>
</div>
<div class="bf-search-hints">
<!--use for not duplicate full term in autocomplete-->
<ng-container *ngFor="let product of products | async">
<div *ngIf="(term !== product.title) && autocomplete" (click)="update(product.title)"
class="bf-search-hint" >
{{ product.title }}
</div>
</ng-container>
</div>
Component
/**
* Created by lizarusi on 02.07.17.
*/
import {Component, OnInit, ViewChild, ElementRef} from '#angular/core';
import { ProductService } from '../shared/product.service'
import {Product} from '../shared/product.model';
import {Subject} from 'rxjs/Subject';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/switchMap';
#Component({
selector: 'product-search',
templateUrl: './product-search.component.html',
styleUrls: ['./product-search.component.scss'],
providers: [ProductService]
})
export class ProductSearchComponent implements OnInit {
#ViewChild('productSearch') searchField: ElementRef;
products: Observable<Product[]>;
autocomplete = true;
term = '';
private searchTerms = new Subject<string>();
constructor(private productService: ProductService) {};
search(): void {
console.log('term ' + this.term);
this.searchTerms.next(this.term);
}
ngOnInit(): void {
this.products = this.searchTerms
.debounceTime(300)
.distinctUntilChanged()
.switchMap( term => this.productService.searchProducts(term))
.catch((err) => {
console.log(err);
return Observable.of<Product[]>([]);
});
}
closeAutocomplete(): void {
this.autocomplete = false;
}
activeAutocomplete(): void {
this.autocomplete = true;
this.search();
}
update(value: string): void {
console.log('asaa');
this.term = value;
this.searchField.nativeElement.focus();
}
}
In this case my (click) doesn't work. I suppose that it happens
because *ngIf removing my element from DOM and than creating it again, but event listeners wasn't assigned.
The question is: How can I use (click) inside/together with *ngIf? Or any other suggestions to figured this out.

Update:
I looked through your code beyond the ngIf and ngFor and realized you were attempting to directly use your Observable in your ngFor instead of a Subscription to the Observable. The general idea is that after you observe your data you'll want to then push the data into a variable of the proper format. Check out this guide for more information: https://angular-2-training-book.rangle.io/handout/observables/using_observables.html
And specifically this part of the code:
let subscription = this.data.subscribe(
value => this.values.push(value),
error => this.anyErrors = true,
() => this.finished = true
);
In your case this.data will be this.products, and this.values will be a new variable. If you use this.finished, you can replace that as your conditional in the ngIf

Related

How to remove matchips and implement two way binding using NgModel?

I have an Input with MatChip connected thru my Firestore, I've been following these documentations MatChip Documentation, StackBlitz Sample 1, StackOverflow Issue 1.
StackOverflow Issue 2 also credits to #Chris Hamilton for the initial guides.
Everything is running okay besides the removal of a specific Chip when added and the method [removable]="true" is also there.
AutoComplete:
Array Storing and interpolation:
Firestore DB:
However, when I click the close icon it doesn't remove the chip. No errors are being shown as well. I also want to know how to two way bind it using NgModel that when I reload the page / component the add value is also there as chips.
Here's my
HTML Code:
<mat-form-field class="form-date" appearance="standard">
<mat-label>Type Plumber Name Here</mat-label>
<mat-chip-list #chipList>
<mat-chip
*ngFor="let plumber of plumbers"
[selectable]="selectable"
[removable]="removable"
(removed)="remove(plumber)">
{{plumber}}
<mat-icon matChipRemove *ngIf="removable">cancel</mat-icon>
</mat-chip>
<input
placeholder="Add Plumber"
#plumberInput
[formControl]="plumberCtrl"
[matAutocomplete]="auto"
[matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="addOnBlur"
(matChipInputTokenEnd)="add($event)">
</mat-chip-list>
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)">
<mat-option *ngFor="let plumber of filteredPlumbers| async" [value]="plumber">
{{plumber.plumberName}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
TS Component Code:
import { Component, ElementRef, OnInit, ViewChild } from '#angular/core';
import { CustomerService } from 'src/app/services/customer.service';
import { Customer } from 'src/app/models/customer.model';
import { Observable, of} from 'rxjs';
import {COMMA, ENTER} from '#angular/cdk/keycodes';
import {FormControl} from '#angular/forms';
import {MatAutocompleteSelectedEvent} from '#angular/material/autocomplete';
import {MatChipInputEvent} from '#angular/material/chips';
import {map, startWith} from 'rxjs/operators';
#Component({
selector: 'app-update-jo',
templateUrl: './update-jo.component.html',
styleUrls: ['./update-jo.component.scss']
})
export class UpdateJOComponent implements OnInit {
visible = true;
selectable = true;
removable = true;
addOnBlur = false;
separatorKeysCodes: number[] = [ENTER, COMMA];
plumberCtrl = new FormControl();
filteredPlumbers: Observable<any[]>;
plumbers: any[] = [];
allPlumber: plumberModel[] =[]
plumberList: plumberModel[];
#ViewChild('plumberInput') plumberInput: ElementRef;
constructor(
public afs: AngularFirestore,
public dialogRef: MatDialogRef<UpdateJOComponent>,
private customerService: CustomerService)
{
this.plumberCtrl.valueChanges.subscribe(search => {
this.filteredPlumbers = of(this.allPlumber.filter(item =>
item.plumberName.toLowerCase().includes(search)))})
}
ngOnInit(): void {
this.customerService.getPlumbers().subscribe(plubObs => {
this.plumberList = plubObs;
this.allPlumber = plubObs;
})
}
add(event: MatChipInputEvent): void {
const value = (event.value || '').trim();
if(value) {
this.plumbers.push(value.trim());
}
event.chipInput!.clear();
this.plumberCtrl.setValue(null)
}
remove(plumber: string) {
console.log(plumber)
const index = this.plumbers.indexOf(plumber);
console.log(this.plumbers)
console.log(index)
if(index >= 0) {
this.plumbers.slice(index,1);
}
}
selected(event: MatAutocompleteSelectedEvent): void {
this.plumbers.push(event.option.viewValue);
this.plumberInput.nativeElement.value = '';
this.plumberCtrl.setValue(null);
}
_filter(value: any) : plumberModel[] {
return this.allPlumber.filter(plumber => plumber.plumberName.toLowerCase().includes(value))
}
}
Thank you for those who could help.

How to update parent component view when data is fetched from child component?

I have a parent and a child component, the child component is actually a modal where we can enter emails and click on 'Add to List'.
The entered emails are then fetched from parent component using #ViewChild property. The array details are obtained in parent because I can console.log() and see them from parent.
But how do I update a part of view in Parent with this new data is received ?
The code and explanations are as below :
Child component :
"emails" is an array with email ids.
I am fetching this array from child.
import { Component, OnInit, ViewEncapsulation } from '#angular/core';
#Component({
selector: 'app-add-student-popup',
templateUrl: './add-student-popup.component.html',
styleUrls: ['./add-student-popup.component.scss']
})
export class AddStudentPopupComponent implements OnInit {
emails: string[] = [];
constructor() { }
ngOnInit(): void {
}
**** Some other code that adds user entered emails to an array 'emails' ****
----
----
sendData() {
console.log(this.emails);
}
}
Parent
import { AfterViewInit, Component, OnInit, ViewChild } from '#angular/core';
// Add students modal popup
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal/';
import { AddStudentPopupComponent } from './add-student-popup/add-student-popup.component';
#Component({
selector: 'app-create-course',
templateUrl: './create-course.component.html',
styleUrls: ['./create-course.component.scss']
})
export class CreateCourseComponent implements OnInit, AfterViewInit {
bsModalRef!: BsModalRef;
emails: string[] = [];
isEmpty: boolean = true;
#ViewChild(AddStudentPopupComponent) addStudent!: AddStudentPopupComponent;
constructor(private modalService: BsModalService) { }
ngOnInit(): any {}
ngAfterViewInit(): void {
if (this.addStudent) {
this.emails = this.addStudent.emails;
isEmpty = false;
}
}
openAddStudentModal() {
this.bsModalRef = this.modalService.show(AddStudentPopupComponent, {class: 'modal-lg'});
this.bsModalRef.content.closeBtnName = 'Close';
}
}
I want to use the updated "emails" array in my front end view. The view part using this array should be updated.
View part
When the popup is filled and submitted the emails array is populated and it should update the this view part.
<div class="multipleInput-container" *ngIf="!isEmpty;else noEmails">
<div class="col-lg-12">
<ul *ngFor="let email of emails">
<li class="multipleInput-email">{{ email }}</li>
</ul>
</div>
</div>
<ng-template #noEmails>
<div class="col-lg-3">
<input type="text" class="form-control form-control-sm"
placeholder="No Students Added" aria-label="No Students Added">
</div>
</ng-template>
Thanks in Advance
You should use #Output
In AddStudentPopupComponent
emails: string[] = [];
#Output() sendEmail = new EventEmitter();
sendData() {
this.sendEmail.emit(this.emails);
}
then in create-course.component.html file
<app-add-student-popup (sendEmail)="emails = $event"></app-add-student-popup>

Angular - Structural directive with embedded view does not pass children to ng-template

I've got a structural directive that creates an embedded view by looking up a template ref using ng-template. My problem is that from this parent component (with structural directive), I cannot pass down children.
Parent component with structural directive
import { ViewChild, Component, OnInit, ElementRef } from "#angular/core";
import { TestJsonService } from "../../services/test-json.service";
#Component({
selector: "xfr-json-renderer",
template: `
<template-lookup></template-lookup>
<div class="NA-TEMPLATE-CHOOSER" *replaceWith="'flexCol'">
<div>Why can't i pass this down to the child?</div>
</div>
`,
styleUrls: ["./json-renderer.component.css"],
})
export class JsonRendererComponent implements OnInit {
#ViewChild("childTemplate") childTemplate;
constructor(el: ElementRef, json: TestJsonService) {}
ngOnInit(): void {}
ngAfterViewInit() {}
}
Child component
import { Injectable, TemplateRef, Component, ViewChild } from "#angular/core";
#Injectable()
export class TemplateStore {
templates = new Map<string, TemplateRef<any>>();
}
#Component({
selector: "template-lookup",
template: `
<ng-template #flexRow></ng-template>
<ng-template #flexCol><xfr-flex-col>
// I want to pass the children into here
</xfr-flex-col></ng-template>
`,
})
export class TemplateLookup {
#ViewChild("flexRow") flexRowTemplate;
#ViewChild("flexCol") flexColTemplate;
constructor(private service: TemplateStore) {}
ngAfterViewInit() {
this.service.templates.set("flexRow", this.flexRowTemplate);
this.service.templates.set("flexCol", this.flexColTemplate);
}
}
Structural directive
import { ViewContainerRef } from "#angular/core";
import { TemplateStore } from "./../services/composite-template.service";
import { Directive, Input } from "#angular/core";
#Directive({
selector: "[replaceWith]",
})
export class CompositeTemplateDirective {
#Input() replaceWith: "flex-col" | "flex-row";
constructor(private service: TemplateStore, private view: ViewContainerRef) {}
ngAfterViewInit() {
this.view.createEmbeddedView(this.service.templates.get(this.replaceWith));
}
}
The problem is that you need to use internal API for that, what is not the best thing. I would use it until I stay with the same angular version and would test it before every update - then should work stable.
I was able to do the injection with Angular 9, quite sure a similar solution (but different internal API) can be applied for other angular versions.
The main thing for the injection - where to inject the content, in components we could use ng-content, but here it wouldn't work, because we have different component contexts. In this case we could use <ng-template [ngTemplateOutlet]></ng-template> to tell the script where we want the injection.
here you can find a live demo: https://codesandbox.io/s/nifty-wright-335bm?file=/src/app/json-renderer.component.ts
CompositeTemplateDirective
import {NgTemplateOutlet} from '#angular/common';
import {AfterViewInit, Directive, Input, TemplateRef, ViewContainerRef} from '#angular/core';
import {TemplateStore} from 'src/app/TemplateLookup/TemplateLookup';
#Directive({
selector: '[replaceWith]',
})
export class CompositeTemplateDirective implements AfterViewInit {
#Input() replaceWith: 'flex-col' | 'flex-row';
constructor(
private service: TemplateStore,
private view: ViewContainerRef,
private templateRef: TemplateRef<any>,
) {
}
public ngAfterViewInit(): void {
const wrapper = this.service.templates.get(this.replaceWith);
const source = this.templateRef;
const view: any = this.view.createEmbeddedView(wrapper);
let directive: NgTemplateOutlet;
const nodes: Array<any> = view._lView ? view._lView : view._view && view._view.nodes ? view._view.nodes : [];
for (const node of nodes) {
if (typeof node !== 'object') {
continue;
}
if (node instanceof NgTemplateOutlet) {
directive = node;
}
if (typeof node.instance === 'object' && node.instance instanceof NgTemplateOutlet) {
directive = node.instance;
}
}
if (directive) {
directive.ngTemplateOutlet = source;
directive.ngOnChanges({
ngTemplateOutlet: {
previousValue: null,
currentValue: source,
firstChange: true,
isFirstChange: () => true,
},
});
}
}
}
TemplateLookup
import {AfterViewInit, Component, Injectable, TemplateRef, ViewChild} from '#angular/core';
#Injectable()
export class TemplateStore {
templates = new Map<string, TemplateRef<any>>();
}
#Component({
selector: 'template-lookup',
template: `
<ng-template #flexRow>
<div>
flexRow template
</div>
</ng-template>
<ng-template #flexCol>
<div>
<div>wrap</div>
<ng-template [ngTemplateOutlet]></ng-template>
<div>wrap</div>
</div>
</ng-template>
`,
})
export class TemplateLookup implements AfterViewInit {
#ViewChild('flexRow', {static: false}) flexRowTemplate;
#ViewChild('flexCol', {static: false}) flexColTemplate;
constructor(
private service: TemplateStore,
) {
}
ngAfterViewInit() {
console.log('TemplateLookup:ngAfterViewInit');
this.service.templates.set('flexRow', this.flexRowTemplate);
this.service.templates.set('flexCol', this.flexColTemplate);
}
}
so the most pragmatic thing here seems to be to just put the child you want to pass as a child of the template-lookup component and use ng-content...
do this in the parent:
<template-lookup>
<div>I will pass to child</div>
</template-lookup>
<div class="NA-TEMPLATE-CHOOSER" *replaceWith="'flexCol'">
</div>
and this in the child:
<ng-template #flexRow></ng-template>
<ng-template #flexCol>
<xfr-flex-col>
<ng-content></ng-content>
</xfr-flex-col>
</ng-template>
and that will solve your problem / fulfill the stated requirements.
You could also consider a rewrite to your service to solve timing problems between templates being set and gotten once and for all:
import { Injectable, TemplateRef } from "#angular/core";
import {ReplaySubject} from 'rxjs';
import {map, filter, distinctUntilChanged} from 'rxjs/operators';
#Injectable({providedIn: 'root'}) // provide appropriately, root for example
export class TemplateStore {
private templates = new Map<string, TemplateRef<any>>();
private tmpSource = new ReplaySubject<Map<string, TemplateRef<any>>>(1);
setTemplate(key: string, template: TemplateRef<any>) {
this.templates.set(key, template);
this.tmpSource.next(this.templates)
}
getTemplate(key: string) {
return this.tmpSource.pipe(
map(tmpMap => tmpMap.get(key)),
filter(tmp => !!tmp),
distinctUntilChanged()
)
}
}
and make the associated changes in the directive and child components...
export class CompositeTemplateDirective implements OnInit, OnDestroy {
#Input() replaceWith: "flex-col" | "flex-row";
private sub: Subscription;
constructor(private service: TemplateStore, private viewContainer: ViewContainerRef) { }
ngOnInit() {
this.sub = this.service.getTemplate(this.replaceWith).subscribe(t => {
this.viewContainer.clear()
this.viewContainer.createEmbeddedView(t)
})
}
ngOnDestroy() {
this.sub.unsubscribe()
}
}
export class TemplateLookup {
#ViewChild("flexRow") flexRowTemplate;
#ViewChild("flexCol") flexColTemplate;
constructor(private service: TemplateStore) {}
ngAfterViewInit() {
this.service.setTemplate("flexRow", this.flexRowTemplate);
this.service.setTemplate("flexCol", this.flexColTemplate);
}
}
functioning example: https://stackblitz.com/edit/angular-ygdveu
it's been pointed out that this doesn't support nesting... so make the following adjustments and you can nest. in template lookup, you'll need to use the SkipSelf modifier in your constructor, and also provide the TemplateStore... in the case of no nesting, this will have no effect, SkipSelf just tells the injector to start looking for the service at the parent rather than at the component:
#Component({
selector: "template-lookup",
template: `
<ng-template #flexRow>FLEX ROW</ng-template>
<ng-template #flexCol>
FLEX COL
<div class="flex-col">
<ng-content></ng-content>
</div>
</ng-template>
`,
providers: [TemplateStore]
})
export class TemplateLookup {
#ViewChild("flexRow") flexRowTemplate;
#ViewChild("flexCol") flexColTemplate;
constructor(#SkipSelf() private service: TemplateStore) {}
ngAfterViewInit() {
this.service.setTemplate("flexRow", this.flexRowTemplate);
this.service.setTemplate("flexCol", this.flexColTemplate);
}
}
then you can nest to your hearts content like so:
<template-lookup>
<div>I can pass this to the child!</div>
<template-lookup>NESTED</template-lookup>
<div class="nested-content" *replaceWith="'flexCol'"></div>
</template-lookup>
<div class="NA-TEMPLATE-CHOOSER" *replaceWith="'flexCol'">
</div>
which is a little ugly, as you need to repeat the template-lookup component, but it does get the job done. This works by allowing the directive and template lookup to communicate with a different copy of the TemplateStore so you can nest different content.
working example of this variant: https://stackblitz.com/edit/angular-lpner2

Angular 6 HttpClient using Map

For learning purposes I am trying to get datas from a json fake API and add "hey" to all titles before returning an Observable. So far I can display the data if I don't use Map and even while using map if I console.log my variable it says Observable but it does not display in my template.
<div class="col-6" *ngIf="courseDatas$ | async as courses else NoData">
<div class="card" *ngFor="let course of courses">
<div class="card-body">
<span><strong>User ID is : </strong>{{course.userId}}</span><br>
<span><strong>Title is : </strong>{{course.title}}</span><br>
<span><strong>Body is : </strong>{{course.body}}</span><br>
<span><strong>ID is : </strong>{{course.id}}</span>
</div>
<ng-template #NoData>No Data Available</ng-template>
</div>
</div>
App component :
import {Component, OnInit} from '#angular/core';
import {PostsService} from "./posts.service";
import {Observable} from "rxjs";
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
courseDatas$ : Observable<any>;
constructor(private posts : PostsService){}
ngOnInit(){
this.courseDatas$ = this.posts.getData();
}
}
posts Service :
import { Injectable } from '#angular/core'
import {HttpClient} from "#angular/common/http";
import {Observable} from "rxjs";
import {map} from "rxjs/operators";
#Injectable({
providedIn: 'root'
})
export class PostsService {
private postURL: string = 'https://jsonplaceholder.typicode.com/posts';
constructor(private http: HttpClient) {}
getData(): Observable<any> {
return this.http.get(this.postURL).pipe(
map(data => {
for (let datas of (data as Array<any>)){
datas.title = datas.title + "Hey";
}
}),
);
}
So, if I don't use the map operator in my getData method in my service everything displays properly. If I use the map operator if I console.log coursesDatas$ in App.Component the console says Observable so I don't understand why it does not work with my async pipe in the template. Also, if I use console.log(datas.title) inside my map operator it does log every titles with Hey at the end.
map should return something to mutate current property, in your case I guess you should return the data
getData(): Observable<any> {
return this.http.get(this.postURL).pipe(
map(data => {
for (let datas of (data as Array<any>)){
datas.title = datas.title + "Hey";
}
return data;
}),
);
}
by the way you can use Array.prototype's map instead of for loop too, to mutate your data
getData(): Observable<any> {
return this.http.get(this.postURL).pipe(
map(data => data.map(d => (d.title = d.title +"Hey", d));
}),
);
}
note that if curly braces are missing in arrow function, it will return automatically

Need to set dynamic value for ngModel sent from input in angular 4

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);
});
}

Categories

Resources