Angular 2 *ngFor doesn't display data - javascript

I'm working on an application using Ionic 2 together with Angular 2. Now I'm trying to the data from an API and display this on a page.
I can log the data and I think it's correct, but for some reason nothing is being displayed on the page itself:
The API where I'm receiving the data from is located here: http://peerligthart.com/grotekerk/v1/api.php/zerken?transform=1
*ngFor on my view
<ion-content padding>
<h1 *ngFor="let z of zerken">
{{ z.naam }}
</h1>
</ion-content>
Controller
import { Component } from '#angular/core';
import { NavController, PopoverController } from 'ionic-angular';
import { PopoverPage } from '../popover/popover';
import { ZerkenProvider } from '../../providers/zerken';
#Component({
selector: 'page-lijst',
templateUrl: 'lijst.html',
providers: [ZerkenProvider]
})
export class LijstPage {
zerken: Array<any>;
constructor(public navCtrl: NavController, public popoverCtrl: PopoverController, public zerkenProvider: ZerkenProvider) {
this.zerkenProvider.getZerken().subscribe(
data => {
console.log(data.zerken);
this.zerken = data.zerken.results;
}
)
}
openPopover(event) {
let popover = this.popoverCtrl.create(PopoverPage);
popover.present({
ev: event
});
}
}
And last, the provider
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import 'rxjs/add/operator/map';
#Injectable()
export class ZerkenProvider {
static get parameters() {
return [[Http]];
}
constructor(public http: Http) {
}
getZerken() {
var url = "http://peerligthart.com/grotekerk/v1/api.php/zerken?transform=1";
var response = this.http.get(url).map(res => res.json());
    
return response;
}
}
So, what the page is displaying itself:
As you can see.. nothing. I hope someone has a solution, kind regards!
-------------EDIT-------------
I changed this.zerken = data.zerken.results to this.zerken. After doing this it's giving me an error:

Your zerken in your response doesn't seem to have an results object, so
this.zerken = data.zerken.results;
should be:
this.zerken = data.zerken;
Remember to initialize the array in your component:
zerken: Array<any> = [];
so that you won't get an error that zerken is undefined, since view is usually rendered before data has been received. Having it initialized will prevent that.

You need an *ngIf encapsulating the *ngFor since zerken is obtained at a later point of time.
Try:
<ion-content padding>
<div *ngIf="zerken">
<h1 *ngFor="let z of zerken">
{{ z.naam }}
</h1>
</div>
</ion-content>
Also you need to set zerken = data.zerken; as mentioned in the other answer by #AJT_82.

Related

Angular: How to get value from one component's frontend (app.compont.html) to another component's backend (other.component.ts)

Consider a simple crud scenario. I have a lot of input fields and buttons in app.component.html. When i press a button from app.component.html, it will send html field value to 'other.component.ts' component and will display the result back in app.component.html after processing (like add, subtract or other).
Here is app.component.html
<a routerLink="posts/">Show Posts</a>
<input type="number" [(ngModel)]="get-one-post-id">
<a routerLink="/post-by-id">Show One Posts</a>
<router-outlet>
</router-outlet>
post-by-id-component.ts
import { Component, OnInit } from '#angular/core';
import { DataService } from '../data.service';
import { Observable } from 'rxjs';
#Component({
selector: 'app-post-by-id',
templateUrl: './post-by-id.component.html',
styleUrls: ['./post-by-id.component.css']
})
export class PostByIdComponent implements OnInit {
posts: object;
constructor(private dataService: DataService) { }
ngOnInit(): void {
// const id = ??
this.GetPost(1);
}
async GetPost(id: number)
{
const response = await this.dataService.Get_A_Post(id);
const dataService = await response.json();
this.posts = dataService;
}
}
post-by-id-component.html
<div *ngFor="let post of posts">
<h3>{{post.title}}</h3>
<p>{{post.body}}</p>
</div>
I just want to get value from the field called get-one-post-id from app.component.html to post-by-id-component.ts [where I commented // const id = ??]. But i can't find a way to import it.
To share Data between Angular Components exists 4 different ways:
Parent to Child: Sharing Data via Input
Child to Parent: Sharing Data via ViewChild
Child to Parent: Sharing Data via Output() and EventEmitter
Unrelated Components: Sharing Data with a Service
You can read this useful article to see how it works.

Angular how to hide a global component when a specific route is opened? [duplicate]

This question already has answers here:
How to Update a Component without refreshing full page - Angular
(7 answers)
Closed 3 years ago.
I'm not sure whether this is possible or not in angular but I wanted to hide a global component when a specific route is opened.
Say for example I have the following:
app.component.html
<app-header></app-header>
<app-banner></app-banner> <!-- Global Component I want to hide -->
<div class="body-container">
<router-outlet></router-outlet>
</div>
<app-footer></app-footer>
app-routing.module.ts
import {NgModule} from '#angular/core';
import {Route, RouterModule} from '#angular/router';
import { StudentListComponent } from './Components/StudentList/StudentList.component';
import { SchoolMottoComponent } from './Components/SchoolMotto/SchoolMotto.component';
const routes: Routes = [
{path: 'StudentList', component: StudentListComponent },
{path: 'SchoolMotto', component: SchoolMottoComponent }
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
export const routingComponents = [StudentListComponent, SchoolMottoComponent]
With this, its a given that when I want to view the StudentList Component, then the url by default becomes localhost4200:/StudentList and the same with SchoolMotto it becomes localhost4200:/SchoolMotto.
Within the StudentListComponent, is an ag-grid that displays list of students, and when you click one of those students the url becomes something like this: localhost4200:/StudentList/view/cf077d79-a62d-46e6-bd94-14e733a5939d and its another sub-component of SchoolList that displays the details of that particular student.
I wanted to hide the Global banner component when the url has something like that: localhost4200:/StudentList/view/cf077d79-a62d-46e6-bd94-14e733a5939d. Only when the user views the specific details of a student.
Something like this:
app.component.html
<app-header></app-header>
**<app-banner *ngIf="router != '/StudentList/view/'"></app-banner> <!-- Global Component I want to hide -->**
<div class="body-container">
<router-outlet></router-outlet>
</div>
<app-footer></app-footer>
Is this doable or not? If it is, how?
You could use event emitter or subject to emit an event when you're in StudentList/view and use ngIf to hide the banner.
In your StudentList component.ts :
export class StudentList {
bannerSubject: Subject<any> = new Subject<any>();
ngOnInit() {
bannerSubject.next(true);
}
}
subscribe to this in your parent component and you can easily hide the banner.
You can acheieve that with the help of component interation using a service
You will use the help of Rxjs Observables here
You will emit an event when you reach the student view component, then recieve that event in app component then change the view condition
New Service:
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs';
#Injectable()
export class RouteService {
private routeChangedSource = new Subject<string>();
// Observable string streams
routeChanged$ = this.routeChangedSource.asObservable();
// Service message commands
changeRoute(mission: string) {
this.routeChangedSource.next(mission);
}
}
Student View Component.
import { Component } from '#angular/core';
import { routeService } from './mission.service';
#Component({
})
export class MissionControlComponent implements ngOnInit{
constructor(private routeService: routeService) {}
ngOnInit() {
this.routeService.changeRoute(mission);
}
}
App Component:
import { Component, Input, OnDestroy } from '#angular/core';
import { RouteService } from './route.service';
import { Subscription } from 'rxjs';
export class AppComponent implements OnDestroy {
studentView = false;
constructor(private routeService: RouteService) {
this.subscription = routeService.routeChanged$.subscribe(
value => {
this.studentView = true;
});
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
Now, your App Component can be:
<app-header></app-header>
<app-banner *ngIf="!studentView"></app-banner>
<div class="body-container">
<router-outlet></router-outlet>
</div>
<app-footer></app-footer>
<app-header></app-header>
<app-banner *ngIf="myService.hideGlobalComp"></app-banner> <!-- Global Component I want to hide -->
<div class="body-container">
<router-outlet></router-outlet>
</div>
<app-footer></app-footer>
in the ts file:
onCellClicked($event) { // place into your method there you want.
this.route.parent.url.subscribe(urlPath => {
this.url = urlPath[urlPath.length - 1].path;
});
if(this.url === 'StudentList/view') {
this.myService.hideGlobalComp = true;
}
}
}
In you ts file do like this.
add new variable router: string;
add in construction add this
constructor(private _router: Router){
this.router = _router.url;
}
Then in HTML use same code.
Let me know if this does not work.

Making ajax call from a component to a service and access response from another component

I am learning Angular2 by creating an example where I want to have a button click on Component1 that makes an ajax call to a Service and the response of the ajax should be used and displayed in another component.
I am able to create the Component1 and able to get the response by making ajax call in Service class. Now how can I display the result in another component
This is my first component:
import { Component } from '#angular/core';
import { ProfileService } from '../shared/index';
#Component({
selector: 'home-page',
template: `
<div>
<button (click)="loadUser()">Load profile</button>
{{ profile | json }}
</div>
`
})
export class ProfileComponent {
constructor(private profileService: ProfileService) {}
profile = {};
loadUser() {
this.profileService.getUser().subscribe(data => this.profile = data);
}
}
This is my service class:
import { Injectable } from '#angular/core';
import { HttpClient, Response } from '#angular/common/http';
import 'rxjs/add/operator/map';
#Injectable()
export class ProfileService {
constructor (
private http: HttpClient
) {}
getUser() {
return this.http.get(`https://conduit.productionready.io/api/profiles/eric`)
.map(res => res );
}
}
This is my second component where I want to see the result:
import { Component } from '#angular/core';
#Component({
selector: 'result-page',
template: `
<div>Result Page :
{{ profile | json }}
</div>
`
})
export class ResultComponent {
constructor(private profileService: ProfileService) {}
profile = {};
username = "";
bio = "";
}
Basically the ajax response is a Json content, I want to store whole json in profile file. This json contains fields for username and bio, I want to store them in my variables username and bio of my result component.
I am stuck how to build my result component, can you please help me.
I am want to communicate between components, don't want to use any routers here.
The json response is :
{
"profile": {
"username": "eric",
"bio": "Cofounder of Thinkster.io, kinda looks like Peeta from the Hunger Games",
"image": "http://i.imgur.com/S66L2XZ.jpg",
"following": false
}
}
Edit: If the component you are trying to pass the data to is the child of that component you can use the #Input decorator to pass the data to it. The #Input will automatically register the changes and update the template. If you need to do any update functions when this input changes you can use ngOnChanges, but if you are simple displaying the changes you can just use the #Input and it will update the view accordingly.
If the two components are both children of a shared parent you can use the #Ouput decorator on the component1 to output the data to the parent and set the variable that is being passed into the Input of the other.
in results component
export class ResultComponent implements OnChanges {
#Input results: any;
constructor(private profileService: ProfileService) {}
ngOnChanges(changes: SimpleChanges) {
if(changes['profile'] && changes['profile'].currentValue){
// do any update functions here if needed
}
}
profile = {};
username = "";
bio = "";
}
and in the profile template
<results-page [profile]="profile"></results-page>
in component1 if that component is also a child
export class ProfileComponent {
#Ouput() emitProfile = new EventEmitter<any>()
constructor(private profileService: ProfileService) {}
profile = {};
loadUser() {
this.profileService.getUser().subscribe(data => this.profile = data);
}
}
and then in the parent you would handle the data emit like so:
handleEmitProfile(profile) { this.profile = profile }
option 2 - add another function in the service.
#Injectable()
export class ProfileService {
constructor (
private http: HttpClient
) {}
private profile$ = new Subject();
getUser() {
return this.http.get(`https://conduit.productionready.io/api/profiles/eric`) .map(res => res );
}
returnProfile() {
return this.profile$;
}
updateProfileObject(event) {
this.profile$.next(event);
}
}
in your results component add this:
this.profileService.returnProfile().subscribe(event => this.profile = event}
and in your profile component
this.profileService.updateProfileObject(this.profile);
and that function will update the profile$ variable in the service calling the function in the results component.

How to use cloud firestore to get data in an ionic 4 app

I was following a tutorial where a guy showed how to build a news app with ionic 4 using the news API. I also want to create a news app that shows summarized news from different sources on a particular topic but the problem is that I am thinking of using the Firebase cloud firestore for this purpose instead of using the news API and I can't figure out how to get the data from the firestore collection. You can look at the following code for reference.
news.page.ts
import { Component, OnInit } from '#angular/core';
import { NewsService } from '../news.service';
import { Router } from '#angular/router';
#Component({
selector: 'app-news',
templateUrl: './news.page.html',
styleUrls: ['./news.page.scss']
})
export class NewsPage implements OnInit {
data: any;
page = 1;
constructor(private newsService: NewsService, private router: Router) {}
ngOnInit() {
this.newsService
.getData(`top-headlines?country=us&category=business&pageSize=5&page=${this.page}`)
.subscribe(data => {
console.log(data);
this.data = data;
});
}
onGoToNewsSinglePage(article) {
this.newsService.currentArticle = article;
this.router.navigate(['/news-single']);
}
}
news.service.ts
import { Injectable } from '#angular/core';
import { environment } from '../environments/environment';
import { HttpClient } from '#angular/common/http';
const API_URL = environment.apiUrl;
const API_KEY = environment.apiKey;
#Injectable({
providedIn: 'root'
})
export class NewsService {
currentArticle: any;
constructor(private http: HttpClient) { }
getData(url) {
return this.http.get(`${API_URL}/${url}&apiKey=${API_KEY}`);
}
}
news.page.html
<ion-header>
<ion-toolbar>
<ion-title>News</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-card *ngFor="let article of data?.articles" (click)="onGoToNewsSinglePage(article)">
<!-- <ion-img [src]="article.urlToImage"></ion-img> -->
<ion-card-content>
<ion-card-title>{{article.title}}</ion-card-title>
<p>{{article.description}}</p>
</ion-card-content>
</ion-card>
</ion-content>
I have installed the angularfire 2 plugin in my project, imported all the files in app.module.ts and also prepared a config file for all the Firebase details. I just want to know how to get the data from Firebase collection and bind it to the html code.
Instead of calling your service this.newsService.getData(...) you will have to use firebase service public AngularFirestore from angularfire2/firestore . Here is an example:
Import the service and inject it in your component news.page.ts:
import {AngularFirestore} from 'angularfire2/firestore';
...
data: any;
constructor ( public db: AngularFirestore ) {
}
...
To retrieve a single post, create the function
getPostEntry ( postTitle: string ): Observable<any> {
return this.db.collection<any> ( "posts" , ref => ref.where ( 'title' , '==' , postTitle ) ).valueChanges ();
}
This will search all entries in your firestore collection called "posts" with attribute title being your postTitle.
Simillarly to retrieve all posts
getAllPosts (): Observable<any> {
return this.db.collection<any>( "post" ).valueChanges ();
}
Then invoke the functions and consume the observables. For instance you can do it in your ngOnInit:
ngOnInit() {
this.getAllPosts().subscribe((data)=>{
this.data = data;
console.log(data);
});
}
Now you have your data in your variable data, you just have to draw it in your html as you would normally do. Have in mind that it will probably be an array with all your posts (if there are any).
here is a gist with the code I edited from your class:
https://gist.github.com/HugoJBello/73fb3c5c0964f29934a2d8021efb128d
EDIT
renamed firebase collection and added subscription to observable

Data-binding ng2 component's template only set OnInit

I have an angular 2 (RC5) component which makes an HTTP call and sets the result as the template of the component. I want to inject a value into the HTML that is returned by the HTTP call. so for example, one of the lines in the returned HTML is:
<a class="d2h-file-name" href="{{chapterURL}}">app/views/login/login.xml</a>
However, that is rendered exactly as is, without having the chapterURL injected. Presumably, this is because the template isn't set during the initialization process? If so, How should I inject these dynamic values into the templates?
Here's the component.
#Component({
selector: 'codestep',
template: `<div class="codestep" [innerHTML]="content"></div>`
})
export class codeStepComponent {
#Input() step: string;
private content: string = '';
private chapterURL;
constructor(private route: ActivatedRoute, private http: Http) { }
ngOnInit() {
this.chapterURL = './diff/' + this.step + '.html';
this.getChapter()
.subscribe(
chapterContent => this.content = chapterContent,
error => this.errorMessage = <any>error);
}
getChapter(): Observable<any> {
return this.http.get(this.chapterURL)
.map(this.extractData)
.catch(this.handleError);
}
private extractData(res: Res) {
let body = res._body;
return body;
}
//Error handling function here...
}
Edit:
I have changed the source html file which is returned by the http call, to:
<a class="d2h-file-name" href={{chapterURL}}>app/views/login/login.xml</a>
and then changed the component's template to:
template: `<div class="codestep" [innerHTML]="content|rawHtml"></div>`
where rawHtml is a pipe that sanitises the content with the bypassSecurityTrustHtml() function on the DomSanitizationService however, I still get the same result, the rendered result is:
<a class="d2h-file-name" href="gitURL">app/views/login/login.xml</a>
if I do ng.probe($0) with the component selected in the browser, then the returned resultant object has properties, but the only property listed is innerHTML, nothing else...
2 Methods
Method 1 - search and replace
This is simple and easy, if the data only need to be updated once during initialization.
ngOnInit() {
this.chapterURL = './diff/' + this.step + '.html';
this.getChapter()
.subscribe(
chapterContent:string => {
// Pre-process the content
processedContent = chapterContent.replace('{{chapterURL}}',this.chapterURL);
this.content = processedContent;
},
error => this.errorMessage = <any>error);
}
Method 2 - dynamic component
Angular 2 does not support component template run time update.
innerHTML will not meet your requirement as Angular2 will not parse the content of it. So data binding within innerHTML will not work.
To archive run time template update, or more precisely, run time template generation is using dynamic component.
There is a detail answer with example here by Radim Köhler:
https://stackoverflow.com/a/38888009/1810391
http://plnkr.co/edit/iXckLz?p=preview
Following is a very minimalistic example I put together:
cf.com.ts
import { Component, ComponentRef, ViewChild, ViewContainerRef } from '#angular/core';
import { RuntimeCompiler } from '#angular/compiler';
import { CfModule } from './cf.module';
#Component({
selector: 'cf-com',
template: `
<h1>{{title}}</h1>
<button (click)="template1()">Template 1</button>
<button (click)="template2()">Template 2</button>
<button (click)="moreChild()">More Child</button>
<template [ngIf]="childRef" #child></template>`
})
export class CfCom {
title = 'Component Factory Test';
// reference for html element with #child tag
#ViewChild('child', { read: ViewContainerRef }) protected childComTarget: ViewContainerRef;
// Child component reference
protected childRef: ComponentRef<any>;
constructor(private compiler: RuntimeCompiler) { }
// Child Input. Use object, not basic type
childInput = { counter: 0 };
// Click to get more children
moreChild() {
this.childInput.counter++;
}
// Click to use template 1
template1() {
let t = 'Child:{{j.counter}}';
this.createChild(t);
}
// Click to use template 1
template2() {
let t = 'Children:{{j.counter}}';
this.createChild(t);
}
createChild(t: string) {
// Destroy child if exist
if (this.childRef) {
this.childRef.destroy();
this.childRef = null;
}
// cf-child class
#Component({
selector: 'cf-child',
template: t // template from parameter t
})
class CfChildCom {
j; // will be bind with parent childInput, see below
}
this.compiler.compileComponentAsync<any>(CfChildCom, CfModule)
.then(factory => {
this.childRef = this.childComTarget.createComponent(factory, 0);
// This is how parent variable bind with child variable
this.childRef.instance.j = this.childInput;
});
}
}
cf.module.ts
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { COMPILER_PROVIDERS } from '#angular/compiler';
import { CfCom } from './cf.com';
#NgModule({
imports: [BrowserModule],
exports: [CfCom],
providers: [COMPILER_PROVIDERS],
declarations: [CfCom]
})
export class CfModule { }
I have not tested this. Try and let me know how it goes
import {Component, Output, Input,AfterViewInit} from '#angular/core';
export class codeStepComponent implements AfterViewInit {
ngAfterViewInit() {
this.content.innerHTML.replace('{{chapterURL}}', this.chapterURL);
}
}
This assumes you have one instance on {{ChapterUrl}} on your page and this placeholder will get replaced the old fashioned way after the view has been initialized.

Categories

Resources