ANGULAR Iterate over an Array within a Model - arrays

Synopsis:
I am trying to iterate over an array that is returned as part of an object. The object has three properties 2 string, 1 array. I want to iterate over the array i my html but can't seem to get it to populate. I can get both strings to show, but cannot figure out how to iterate the inner array for values.
Policy.ts
import { Document } from './Document';
export interface Policy {
insuredName: string;
policyNumber: string;
documents: Document[];
}
Document.ts
export interface Document {
url: string,
docType: string
}
I bind the model("policy") in my parent component
#Component({
selector: 'app-search',
templateUrl: './search.component.html',
styleUrls: ['./search.component.css']
})
export class SearchComponent implements OnInit {
policy: any = {};
constructor(private policyService: PolicyService, private alertify: AlertifyService) { }
ngOnInit() {
}
loadPolicy() {
this.policyService.getPolicy(this.policy.policyNumber).subscribe((res) => {
this.policy.insuredName = res.insuredName;
this.policy.policyNumber = res.policyNumber;
this.documents = res.documents;
}, error => {
this.alertify.error(error);
})
}
I pass the data to my child component
Search.component.html
<app-documentList [policy]=policy></app-documentList>
and then bind it in the child
export class DocumentListComponent implements OnInit {
#Input() policy: Policy;
ngOnInit() {
}
but when I finally try the iteration all I get is the first property (insuredName) and nothing for the *ngFor
<div>
<div class="test">
<p>{{policy.insuredName}}</p>
<h2 *ngFor="let doc of policy.documents">{{doc.url}}</h2>
</div>
</div>

Try replacing this.documents = res.documents; with this.policy.documents = res.documents;.
Looks like you are binding the result to a wrong variable.
Also you might not have to assign values manually. You could do the following
import { Policy } from './Policy';
#Component({
selector: 'app-search',
templateUrl: './search.component.html',
styleUrls: ['./search.component.css']
})
export class SearchComponent implements OnInit {
policy: Policy = {};
constructor(private policyService: PolicyService, private alertify: AlertifyService) { }
ngOnInit() {
}
loadPolicy() {
this.policyService.getPolicy(this.policy.policyNumber).subscribe((res: Policy) => {
this.policy = res;
}, error => {
this.alertify.error(error);
});
}
}

Related

Angular - Why after changing components, the data in the variables gets lost?

I have two components, one that creates and changes one array, and another that gets the array, the problem is that when getting the array in the second component, the array is the default one without data:
array creation:
export class AddPlayerComponent implements OnInit {
team = [
{
summonerName: '',
rank: '',
role: '',
}
];
// code that changes the array
}
the other component calling for the array:
export class WaitingComponent implements OnInit {
actualTeam;
constructor(
private addPlayerComponent: AddPlayerComponent
) { }
ngOnInit(): void {
console.log("add player team:", this.addPlayerComponent.team);
this.actualTeam = this.addPlayerComponent.team;
console.log("actual team:", this.actualTeam);
}
}
By logic when i get the array, it should come with the data that i placed, but it comes with the default empty data.
How can i get the real array with the data between the two components?
You might want to try using a behavior subject in this case. You'll want to create this in a service, which you can then inject into any number of components for easy access.
#Injectable()
export class TeamsService {
private teams: ITeam[];
private observableTeams: BehaviorSubject<ITeam[]>;
constructor() {
this.teams = new Array<ITeam>;
this.observableTeams = <BehaviorSubject<ITeam[]>>new BehaviorSubject([]);}
get teams() {
return this.observableTeams.asObservable();}
addTeam(team: ITeam) {
this.teams.push(team);
this.observableTeams.next(Object.assign({}, this.teams));}}
Create a service to share the data between the two components. Say your service appears like this
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class ShowService {
private data=new BehaviorSubject<any>();
public castTeam = this.data.asObservable();
showTeam(team){
this.data.next(team);
}
}
then import this service in both of your components and then call showTeam() in ngOnInit of your components like this
export class WaitingComponent implements OnInit {
actualTeam;
constructor(
private showdata: ShowService,
private addPlayerComponent: AddPlayerComponent
) { }
ngOnInit(): void {
console.log("add player team:", this.addPlayerComponent.team);
this.actualTeam = this.addPlayerComponent.team;
this.showdata.castTeam.subscribe(actualTeam => { this.actualTeam= actualTeam; });
console.log("actual team:", this.actualTeam);
}
}
and then import the same service in the other component and then in ngOnInit just subscribe to the service methodlike this
export class AddPlayerComponent implements OnInit {
constructor(
private showdata: ShowService
) { }
ngOnInit(): void {
this.showdata.castTeam.subscribe(actualTeam => { this.team = actualTeam; });
}
team = [
{
summonerName: '',
rank: '',
role: '',
}
];
}

How to access DOM element in other component in Angular 6?

I have two components header & a. In header component there is a hidden element and I want to show it from component a, but I don't know how do I do this.
header.component.html
<p>
header works!
</p>
<div #hidden_element style="display: none">
<h1>Hidden Element in header</h1>
</div>
a.component.html
<div (click)="show()">Show</div>
a.component.ts
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-a',
templateUrl: './a.component.html',
styleUrls: ['./a.component.css']
})
export class AComponent implements OnInit {
constructor() { }
show() {
// code to display hidden element in header component
}
ngOnInit() {
}
}
app.component.html
<app-header></app-header>
<app-a></app-a>
You can do it by sending events between directives via a custom service. A simple example would look something like this:
// my-service.component.ts
import { Injectable } from "#angular/core";
import { Subject } from "rxjs/index";
#Injectable()
export default class MyService {
private messageSource = new Subject<string>();
listener = this.messageSource.asObservable();
send(message: string): void {
this.messageSource.next(message);
}
}
Your 'a' component will look something like this:
// a.component.ts
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-a',
templateUrl: './a.component.html',
styleUrls: ['./a.component.css']
})
export class AComponent implements OnInit {
constructor(private myService: MyService) { }
show() {
// code to display hidden element in header component
this.myService.send('some message');
}
ngOnInit() {
}
}
and this is your header component:
// header.component.ts
#Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: []
})
export class HeaderComponent implements OnDestroy {
private serviceSubscription: Subscription;
constructor(private myService: MyService) {
this.serviceSubscription = this.myService.listener.subscribe(message => {
// TODO: Do whatever you want to do here to show the hidden element
});
}
ngOnDestroy(): void {
this.serviceSubscription.unsubscribe();
}
}

Unable to loop array of objects in angular6

I'm trying to fetch movie data from omdbapi but i'm not getting able to print this value in html
.ts
import { Component, } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
constructor(private _httpClient: HttpClient) {}
results: any = [];
getMovies(title) {
`enter code here`
this._httpClient.get("http://www.omdbapi.com/?apikey=d5dc2a5a&s=" + title)
.subscribe((data) => {
this.results = data;
//this.results.Search;
console.log(this.results)
})
}
}
Console value
You're probably using *ngFor in your template on results. But since you're assigning data to results and data is an Object, it's giving an error as *ngFor assumes an iterative Data Structure.
As can be seen from your Screenshot, there's a Search array on your data.
Change this.results = data; to this.results = data.Search;
import { Component } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
constructor(private _httpClient: HttpClient) {}
results: any = [];
getMovies(title) {
this._httpClient.get("https://www.omdbapi.com/?apikey=157f9eb7&s=" + title)
.subscribe((data: any) => {
this.results = data.Search;
console.log(this.results);
})
}
ngOnInit() {
this.getMovies('The Dark Knight');
}
}
Here's a Sample StackBlitz for your ref.
You can achieve this using keyvalue pipe as below:
<div *ngFor="let item of results | keyvalue">
<b>{{item.key}}</b> : <b>{{item.value}}</b>
</div>
You are getting an object in your response. You need use ? operator in *ngFor loop like this to get array for asynchronous http service.
<ul>
<li *ngFor="let result of results?.Search">{{result.Title }}</li>
</ul>
and you can display totalResults like this :
<span>Total: {{results?.totalResults}}</span>

Angular2 Typescript View not updating data after save changes and update the array (model)

I have parent component which call a REST api to get profile (JSON) data. the parent component pass this data to child component using input (array) to create dynamic form (input fields). All fields have been created successfully. the form is saving the data to DB using API post.
Problem: If the user navigate to home page and come back to profile dynamic form the data value is showing with the data version before save (old version of the data). If I refresh the screen, then I can get the correct data. the view has not been updated when load the form again. I checked the log and the JSON data being returned updated, but the form is getting the previous version of the values.
Parent Html
<df-profile [profileData]="profileData"></df-profile>
Parent Component
import { Component, ChangeDetectorRef, Input, OnInit} from '#angular/core';
import { Http } from '#angular/http';
import { Router } from '#angular/router';
import { AuthenticationService, UserService } from '../services/index';
import { ProfileModel } from '../models/index';
#Component({
moduleId: module.id,
selector: 'user-profile',
templateUrl: 'user.profile.component.html'
})
export class UserProfileComponent implements OnInit {
profileData: ProfileModel[] = [];
public isDataAvailable: boolean = false;
constructor(
public router: Router,
private userService: UserService,
private authenticationService: AuthenticationService,
) {}
ngOnInit() {
console.info("Starting GameDay Component ngOnInit ... ...");
console.info(this.authenticationService.isLoggedIn());
if (this.authenticationService.isLoggedIn()) {
this.getData().then(() => this.isDataAvailable = true);
} else {
this.authenticationService.logout();
}
}
getData(): Promise<ProfileModel<any>[]> {
return this.userService.get.profile().then(data => {
this.profileData = data[0]['profileList'];
});
}
}
Child Dynamic Form Html
<form (ngSubmit)="form.valid && onSubmit()" [formGroup]="form">
<div class="kode-team-match" *ngFor="let item of profileData">
<ul>
<li class="home-kode-field"><df-field [field]="item.field" [form]="form"></df-field></li>
</ul>
<div class="clearfix"></div>
</div>
<div class="form-group">
<div class="col-md-12" style="text-align:center;">
<button type="submit" [disabled]="loading" class="kode-modren-btn thbg-colortwo"> Salvar</button>
</div>
</div>
</form>
<div *ngIf="payLoad" class="form-row">
<strong>Saved the following values</strong><br>{{payLoad}}
</div>
Child Dynamic Form Component
import { Component, Input, ChangeDetectorRef, OnInit } from '#angular/core';
import { FormControl, FormGroup } from '#angular/forms';
import { AuthenticationService, UserService } from '../services/index';
#Component({
moduleId: module.id,
selector: 'df-profile ',
templateUrl: 'profile.form.component.html',
providers: [ProfileControlService]
})
export class ProfileFormComponent implements OnInit {
#Input() profileData: ProfileModel[];
form: FormGroup;
payLoad = '';
constructor(
private pcs: ProfileControlService,
) {
this.profileData = [];
}
ngOnInit() {
//console.log('Starting form component ... ...');
this.form = this.pcs.toFormGroup(this.profileData);
}
onSubmit() {
this.userService.saveUserProfile(array)
.subscribe(result => {
this.isFailed = result;
if (result === true) {
this.service.success(this.translate.instant('register login'), this.translate.instant('register success'));
} else {
this.error = this.userService.errorMessage;
}
this.loading = false;
});
}
}
I'm struggling to see what I'm doing wrong here. Can anyone help?
I don't know if I need to invoke change detection and where.

Does array change to an object after #Input() into child?

I'm quite new to Angular 2 and I would like to transfer an array made in a parent component, via #Input(), to its child.
In the parent I create the array, add data from a service, and display it in the console (Console output 1). In the child component I then use ngOnChanges to display it in the console again (Console output 2). As you can see below, the length of the array changes from 12 to 0. I suppose this is because the array changes to an object when it's passed to the child?
How would I fix this?
Parent
import { Component, OnInit } from '#angular/core';
import { Module, MapMarkerData } from './coreclasses';
import { TimelineService } from './input.service';
#Component({
selector: 'my-app',
templateUrl: 'app/app.component.html',
providers: [TimelineService]
})
export class AppComponent implements OnInit {
modules: Module[];
mapMarkerData: any;
constructor(private timelineService: TimelineService) {
this.mapMarkerData = new Array<MapMarkerData>();
}
getModules(): void {
this.timelineService.getModules().then(modules => {this.modules = modules; this.setMapModuleData(this.modules);});
}
setMapModuleData(modules: Array<any>): void {
for (let module of modules) {
if (module.className) {
var id = module.id;
var className = module.className;
let contents: Object = {id: id, className: className};
this.mapMarkerData.push(contents);
}
}
console.log(this.mapMarkerData); // CONSOLE OUTPUT 1
console.log(this.mapMarkerData.length);
}
}
Child
import { Component, Input, OnInit, OnChanges, SimpleChanges } from '#angular/core';
import { MapMarkerData } from './coreclasses';
#Component({
selector: 'timeline-map',
templateUrl: 'app/timeline.map.component.html'
})
export class TimelineMapComponent implements OnChanges {
#Input()
mapMarkerData: any;
ngOnChanges(changes: any) {
console.log(this.mapMarkerData); // CONSOLE OUTPUT 2
console.log(this.mapMarkerData.length);
}
}
Parent Template
...
<div id="map" class="mapLarge">
<timeline-map [mapMarkerData] = "mapMarkerData"></timeline-map>
</div>
...
Console Output 1
Array[12]: [Object, Object, ... ]
Console Output 2
Array[0]: [Object, Object, ... ]
EDIT Important
because you're passing same reference into child component, so the ngOnChanges lifecycle only fired 1 time.
please checkout this version, open your console tabs: https://plnkr.co/edit/WUDGOx?p=preview
so, if you wanna catch every changes in ngOnChanges lifecycle, you must passing a difference array, like this: https://plnkr.co/edit/8awiqe?p=preview
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-root',
template: `
<h2>App Component</h2>
<p><strong>This app will trigger ngOnChanges with immutable array</strong></p>
<app-content [posts]="posts">
</app-content>
`
})
export class AppComponent implements OnInit {
latestPosts: any[] = [];
posts: any[] = [];
ngOnInit() {
// fake api call
setTimeout(() => {
this.latestPosts.push.apply(this.latestPosts, [
{name: 'Post no.1'},
{name: 'Post no.2'},
{name: 'Post no.3'}
]);
this.posts = [].concat(this.latestPosts);
}, 300);
}
}
=== 2nd option === you could check by yourself in DoChecklifecycle: https://plnkr.co/edit/oxsISD?p=preview
import { Component, Input, DoCheck, IterableDiffers } from '#angular/core';
#Component({
selector: 'app-content',
template: `
Status: {{ status }}
<div *ngFor="let post of pp">
{{ post.name }}
</div>
`
})
export class ContentComponent implements DoCheck {
#Input()
posts: any[];
differ: IterableDiffers;
status: string = '';
constructor(private differs: IterableDiffers) {
this.differ = this.differs.find([]).create(null);
}
ngDoCheck() {
var changes = this.differ.diff(this.posts);
if (changes) {
console.log('ngDoCheck');
this.status = 'ngDoCheck invoked!'
}
}
}
Note that you must pay a cost because the above ngDoCheck method will invoke on every change detection run.
https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html
https://angular.io/docs/ts/latest/api/core/index/DoCheck-class.html
https://angular.io/docs/ts/latest/api/core/index/SimpleChange-class.html
https://angular.io/docs/ts/latest/api/core/index/IterableDiffers-class.html
END
for the initial state, it's empty, then the value will assign to this property.
js log async

Resources