OnInit list not displaying - html

I'm creating an Angular6 crud app and I have a list of users in my database. I am able to retrieve my list when I call getAllUsers but it does not display in the UI. The only time I see a row being displayed in the table is when I manually enter a new User but it does not display the data previous to my entry. What am I doing wrong?
import { Component, OnInit } from '#angular/core';
import { UserDataService } from './user-data.service';
import { User } from './user';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers: [UserDataService]
})
export class AppComponent implements OnInit{
users: User[]=[];
constructor(private userDataService: UserDataService) {}
public ngOnInit() {
debugger
this.userDataService
.getAllUsers()
.subscribe(
(users) => {
this.users = users;
}
);
}
onAddUser(user) {
this.userDataService
.addUser(user)
.subscribe(
(newUser) => {
this.users = this.users.concat(newUser);
}
)
}
onRemoveUser(user){
this.userDataService
.deleteUserById(user.id)
.subscribe(
(_) => {
this.users = this.users.filter((u) => u.id !== user.id);
}
);
}
getUser() {
return this.userDataService.getAllUsers();
}
}
APP COMPONENT HTML
<app-user-list-header (add)="onAddUser($event)"></app-user-list-header>
<table>
<th>ID</th>
<th>NAME</th>
<tr *ngFor = "let user of users">
<td>{{user.id}}</td>
<td>{{user.userName}}</td>
</tr>
</table>
USER DATA SERVICE
import { Injectable } from '#angular/core';
import { User } from './user';
import { ApiService } from './api.service';
import { Observable } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class UserDataService {
constructor(private api: ApiService) { }
addUser(user: User): Observable<User> {
return this.api.createUser(user);
}
getAllUsers(): Observable<User[]> {
return this.api.getAllUsers();
}
updateUser(user: User): Observable<User> {
return this.api.updateUser(user);
}
getUserById(userId: number): Observable<User> {
return this.api.getUserById(userId);
}
deleteUserById(userId: number): Observable<User> {
return this.api.deleteUserById(userId);
}
}
USER LIST HEADER
import { Component, OnInit, Output, EventEmitter } from '#angular/core';
import { User } from '../user';
#Component({
selector: 'app-user-list-header',
templateUrl: './user-list-header.component.html',
styleUrls: ['./user-list-header.component.css']
})
export class UserListHeaderComponent implements OnInit {
newUser: User = new User();
#Output()
add: EventEmitter<User> = new EventEmitter();
constructor() { }
ngOnInit() {
}
addUser() {
this.add.emit(this.newUser);
this.newUser = new User();
}
}
USER LIST HEADER HTML
<header class="userHeader">
<h1>Users</h1>
<input class="new-user" placeholder="Enter User" autofocus="" [(ngModel)]="newUser.userName" (keyup.enter)="addUser()">
</header>
API SERVICE
import { Injectable } from '#angular/core';
import { environment } from '../environments/environment';
import { User } from './user';
import { Http } from '#angular/http';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '#angular/common/http';
import { Observable } from 'rxjs';
import { map, catchError } from "rxjs/operators";
const API_URL = environment.apiUrl;
#Injectable({
providedIn: 'root'
})
export class ApiService {
constructor(private http: HttpClient) {
}
public createUser(user: User): Observable<User> {
const headers = {headers: new HttpHeaders({
'Content-Type': 'application/json'
})};
return this.http
.post(API_URL + '/users', user).pipe(
map(response => {
return new User(response);
}), catchError(this.handleError)
)
}
public getAllUsers(): Observable<User[]> {
return this.http
.get(API_URL + '/users').pipe(
map(response => {
var users = [response];
return users.map((user)=> new User(user));
}), catchError(this.handleError))
}

The issue is, you don't need to subcribe a new user to the observable here in the AppComponent again after you have done it before:
.subscribe(
(newUser) => {
this.users = this.users.concat(newUser);
}
)
because it is already added to the user array by this mean: this.userDataService.addUser(user), that says you inserted the same value twice to the observer.
So what you need to do is, in the userdata service, plug an observer to the rest api that fetches data regularily to the adapter getAllUsers like this:
getAllUsers(): Observable<user[]> {
// some intermediate post call happens here
this.api.getAllUsers().subscribe((users) => users.map((user)=>this.users.push(user)));
return of(this.users);
}
This will update the state of users whenever a new value added to the users base, or piped from the rest call, so you do never have to subcribe the same value more than once from the same observer, this will cause duplication of data.
I couldn't find an online api that allows post calls, so you could find your ease figuring that I said above using your own tools, but for a same-domain GET api service, I made an example in this term visible to your eyes here:
https://stackblitz.com/edit/angular-rndqsd.
Taking countries as users for sake of vocabulary-allowance.

Related

How can I change the routing for a component taking in the status code

I am working on an angular project for learning. I want to find a way to change the routing based on the routing code that I receive. See my code below for my service.ts file. We were not able to get the toke api set up so I am just looking to switch routing based on 200 status code.
import { Injectable } from '#angular/core';
import {HttpClient} from '#angular/common/http';
import { Observable, throwError, catchError } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class LoginService {
token:string = ""
// Login Logic
login(userName:string, password:string):Observable<any>{
return this.http.post('https://vanquish-p2.azurewebsites.net/api/UserC/Authenticate?UserName=' + userName +'&password=' + password,
// We need to add headers to specify content type
{headers: {'Content-Type':'application/json'}}
)
.pipe(
catchError((e) =>{
return throwError(e)
}
))
}
// Inject HttpClient into our service
constructor(private http:HttpClient) { }
}
This right here is my login component
import { Component, OnInit } from '#angular/core';
import { Router } from '#angular/router';
import { LoginService } from '../login.service';
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
// Adding in defaults to store info
userName:string = "";
password:string = "";
error:boolean = false;
// Create onsubmit to handle submissions
onSubmit():void{
console.log(this.userName, this.password)
this.loginService.login(this.userName, this.password)
.subscribe((data) =>{
console.log(data)
// Let's store the data in our service's string
this.loginService.token = data.token;
console.log(this.loginService.token)
// If we successfully login, let's redirect to the home page
this.router.navigate(['home'])
},
(error) =>{
console.log(error)
// Makes error message appear through ngIf
this.error = true;
})
}
//Inject login service to component to use methods
// Inject router for navigation
constructor(private loginService:LoginService, private router:Router) { }
ngOnInit(): void {
}

Using Angular11, how does my HomeComponent retrieve the data provided by the Subject in DataService?

In order to make the data accessible through out the app, I created a new service called the DataService where I want to store my data coming from the API in a Subject.
While I do get the data, I cen see the array of objects in a log from DataService, my array in HomeComponent that should get the data is undefined in the console:
browser inspector console output
I imagine I have some stupid errors in my code, I am a beginer. Could you help me ?
HomeComponent:
import {Component, OnInit, Output} from '#angular/core';
import {DataService} from '../../shared/services/data.service';
import {Subscription} from 'rxjs';
import {Article} from '../../shared/models/article';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
#Output() articles?: Article[];
articleSubscription?: Subscription;
constructor(private dataService: DataService) { }
ngOnInit(): void {
this.dataService.emitArticlesSubject(this.dataService.loadArticles());
this.articleSubscription =
this.dataService.articlesSubject.subscribe(
(articles) => {
this.articles = articles;
}
);
console.log('HOME COMPONENT: ngOnInit: this.articles : ' + JSON.stringify(this.articles));
}
}
DataService:
import { Injectable } from '#angular/core';
import {BehaviorSubject, Subject} from 'rxjs';
import {ArticleService} from './article.service';
import {Article} from '../models/article';
#Injectable({
providedIn: 'root'
})
export class DataService {
articles?: Article[];
message = 'Aucun résultat ne correspond à votre recherche.';
articlesSubject = new Subject<Article[]>();
constructor(private articleService: ArticleService) { }
emitArticlesSubject(action: any): void {
this.articlesSubject.next(action);
}
/**
* Method to be served as a parameter
* to the 'emitArticlesSubject' method
* to load articles sorted by date.
*/
loadArticles(): any {
this.articleService.getAll().subscribe(
data => {
this.articles = data._embedded.articles;
console.log('DataService: loadArticles() : ' + JSON.stringify(this.articles));
},
error => {
console.log('ERROR: DataService not able to loadArticles !' );
}
);
}
/**
* Method to be served as a parameter
* to the 'emitArticlesSubject' method
* to load articles sorted by last activity.
*/
loadArticlesByActivity(): any {
this.articleService.getAllSortedByActivity().subscribe(
data => {
this.articles = data._embedded.articles;
},
error => {
console.log('ERROR: DataService not able to loadArticlesByActivity');
}
);
}
}
ArticleService:
import { Injectable } from '#angular/core';
import {HttpClient, HttpHeaders} from '#angular/common/http';
import {Observable} from 'rxjs';
import {Article} from '../models/article';
import {ResponseEntities} from '../../core/ResponseEntities';
const baseUrl = 'http://localhost:8080/articles';
const queryUrl = '?search=';
const dateUrl = '?sort=date,desc';
#Injectable({
providedIn: 'root'
})
export class ArticleService {
constructor(private http: HttpClient) { }
getAll(): Observable<ResponseEntities<Article[]>> {
return this.http.get<ResponseEntities<Article[]>>(`${baseUrl}${dateUrl}`);
}
getAllSortedByActivity(): Observable<ResponseEntities<Article[]>> {
return this.http.get<ResponseEntities<Article[]>>(`${baseUrl}/${dateUrl}`);
}
search(term: string): Observable<ResponseEntities<Article[]>> {
return this.http.get<ResponseEntities<Article[]>>(`${baseUrl}/${queryUrl}${term}`);
}
get(id: any): Observable<Article> {
return this.http.get<Article>(`${baseUrl}/${id}`);
}
create(data: any): Observable<any> {
return this.http.post(baseUrl, data);
}
update(id: any, data: any): Observable<any> {
return this.http.put(`${baseUrl}/${id}`, data);
}
delete(id: any): Observable<any> {
return this.http.delete(`${baseUrl}/${id}`);
}
deleteAll(): Observable<any> {
return this.http.delete(baseUrl);
}
findByTag(tag: any): Observable<Article[]> {
return this.http.get<Article[]>(`${baseUrl}?tag=${tag}`);
}
}
The problem could be related to subscription in data service.
this.dataService.emitArticlesSubject(this.dataService.loadArticles());
in this line emitArticlesSubject() called. but loadArticles() subscribed to underlaying service. emitArticlesSubject() only call loadArticles() and does not wait for its subscription to get complete. that causes articlss to be undefined. you should use promise in loadArticles() or change your service structures and call ArticleService directly in your HomeComponent.
In your HomeComponent you are console logging the contents of this.articles before the articles have actually been fetched. If you want to log the articles after they have been fetched, you can console log in the subscription instead:
this.articleSubscription =
this.dataService.articlesSubject.subscribe(
(articles) => {
this.articles = articles;
console.log('HOME COMPONENT: ngOnInit: this.articles : ' + JSON.stringify(this.articles));
}
);

IONIC API Undefined

I have an IONIC APP with CORDOVA. I Just want to GET a JSON from an URL.
I Created a service call rest.service.ts
rest.service.ts
import { Injectable } from '#angular/core';
import { HTTP } from '#ionic-native/http/ngx';
#Injectable({
providedIn: 'root'
})
export class RestService {
BASE_URL = 'http://whatever.....';
constructor(public http: HTTP) {}
getProjects() {
const URL = this.BASE_URL + 'getProjects';
this.http.get(URL, {}, { 'Content-Type': 'application/json' })
.then(answer => {
return JSON.parse(answer.data);
})
.catch(error => {
console.log(error.status);
console.log(error.error); // error message as string
console.log(error.headers);
});
}
}
Here in this file I can see the info. If I insert something like...
console.log(JSON.parse(answer.data));
I can see the results in JSON just as I Want.
The problem is when I try to use this methods in other files...
otherpage.page.ts
import { Platform } from '#ionic/angular';
import { RestService } from './../rest.service';
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-otherpage',
templateUrl: './otheropage .page.html',
styleUrls: ['./otherpage .page.scss']
})
export class OtherPage implements OnInit {
projects;
constructor(
public platform: Platform,
public rest: RestService,
) {
this.projects = this.rest.getProjects();
console.log(this.projects); // UNDEFINED
}
ngOnInit() { }
}
Here... this.projects... is undefined... ¿What is happening? I tried platform.ready, insert in ngOnInit... nothing works.
You need to modify the service and subscribe this service your page.
BASE_URL = 'http://whatever.....';
getProjects() {
const URL = this.BASE_URL + 'getProjects';
return this.http.get(URL, {}, { 'Content-Type': 'application/json' });
}
Subscribe this service observable in your page.ts file.
this.rest.getProjects().subscribe((answer)=>{
this.projects = JSON.parse(answer.data);
console.log(this.projects); // here you get the json
},error=>{
consoole.log(error)
});
Note:
console.log(this.projects); // UNDEFINED
Because this line executes before the http observable send the response, you need to subscribe that http observable to get the json.

Angular 6: use a service to get local json data

I have a movies.json that contain a list of movies and I want to create a MoviesServices to get the data where I want.
My MoviesServices:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { HttpErrorResponse } from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class MoviesService {
movies: string[];
constructor(private httpService: HttpClient) {
this.getMovies();
}
getMovies() {
this.httpService.get('../../assets/movies.json').subscribe(
data => {
this.movies = data as string[];
console.log(this.movies); // My objects array
},
(err: HttpErrorResponse) => {
console.log(err.message);
}
);
console.log(this.movies); // Undefined
}
}
Firstly, I have no idea why the first console.log() works and the second not, can you tell me why ?
Here is my component where I need to get the data:
import { Component, OnInit } from '#angular/core';
import { MoviesService } from '../services/movies/movies.service';
#Component({
selector: 'app-movies',
templateUrl: './movies.component.html',
styleUrls: ['./movies.component.css']
})
export class MoviesComponent implements OnInit {
title = 'films-synopsys';
movies;
constructor(private myService: MoviesService) {}
ngOnInit() {
console.log(this.myService.movies); // Undefined
}
}
Of course this is not working. Can you tell me how must I do ? I'm newbie angular
So basically you need to return an Observable from your service and then subscribe to it from your Component. You can then assign your response to the Component property movies
Try this:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable()
export class MoviesService {
constructor(private httpService: HttpClient) { }
getMovies() {
return this.httpService.get('../../assets/movies.json');
}
}
And in your Component:
import { Component } from '#angular/core';
import { MoviesService } from './movies.service';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
title = 'films-synopsys';
movies;
constructor(private myService: MoviesService) {}
ngOnInit() {
this.myService.getMovies()
.subscribe(res => this.movies = res);
}
}
Here's a Sample StackBlitz for your ref.
Change your method to return an Observable which you can subscribe to:
import { Observable } from 'rxjs/Observable';
...
getMovies(): Observable<string []> {
this.httpService.get('../../assets/movies.json').subscribe(
data => {
this.movies = data as string[];
return this.movies;
},
(err: HttpErrorResponse) => {
console.log(err.message);
}
);
}
In your calling code:
import { Subscription } from 'rxjs/Subscription';
this.myService.getMovies().subscribe(movies => {
console.log(movies); // My objects array
}
The reason the first console log works is because you are doing it within an observable's subscription. Subscriptions have three states, Next, Error, Complete and so when you console log the first time, within the subscription next state you get the value that was pushed out from the event stream.
In your component the reason why it doesn't work is due to the fact that observables are lazy, and that you need to initialize the data by calling this.myService.getMovies() first to make the subscription happen.
A better way to do this would been to pass observables around and use async pipe in the html template.

Parent / Child component communication angular 2

I am failing to implement action button in child_1 component but the event handler is in sub child component child_2 as shown in the following code:
app.component.html (Parent Html)
<div style="text-align:center">
<h1>
Welcome to {{title}}!
</h1>
<app-navigation></app-navigation> <!-- Child1-->
</div>
app.component.html (Parent Component)
import { Component } from '#angular/core';
import { ProductService } from './productservice';
import {Product} from './product';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
title = 'MobileShirtShoeApp';
}
app.module.ts (Main Module)
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { HttpModule } from '#angular/http';
import { Product } from './product';
import { ProductService } from './productservice';
import { AppComponent } from './app.component';
import { NavigationComponent } from './navigation/navigation.component';
import { DataTemplateComponent } from './data-template/data-template.component';
#NgModule({
declarations: [AppComponent,NavigationComponent,DataTemplateComponent],
imports: [BrowserModule,HttpModule],
providers: [ProductService],
bootstrap: [AppComponent]
})
export class AppModule { }
navigation.component.html (Child 1 HTML)
<fieldset>
<legend>Navigate</legend>
<div>
<button (click)="loadMobiles()">Mobiles</button> <!--Child_1 Action-->
</div>
<app-data-template></app-data-template>
</fieldset>
navigation.component.ts (Child 1 Component.ts)
import { Component, OnInit } from '#angular/core';
import { ProductService } from '../productservice';
import {Product} from '../product';
import {DataTemplateComponent} from '../data-template/data-template.component';
#Component({
selector: 'app-navigation',
templateUrl: './navigation.component.html',
styleUrls: ['./navigation.component.css']
})
export class NavigationComponent implements OnInit {
error: string;
productArray: Product[];
constructor(private myService: ProductService){
this.myService = myService;
}
dataTemplateComponent: DataTemplateComponent = new DataTemplateComponent(this.myService);
ngOnInit() {
}
loadMobiles() {
return this.dataTemplateComponent.loadMobiles();
}
}
data-template.component.html (Child 2 HTML) (NOT DISPLAYING DATA)
<fieldset>
<legend>Requested Data</legend>
Welcome
<div>
<ul>
<li *ngFor="let product of productArray">
{{product.id}} {{product.name}} {{product.price}}
<img src="{{product.url}}">
</li>
</ul>
</div>
</fieldset>
data-template.component.ts (Child 2 Component) (Contains Product service calling code)
import { Component} from '#angular/core';
import {Product} from '../product';
import {ProductService} from '../productservice';
#Component({
selector: 'app-data-template',
templateUrl: './data-template.component.html',
styleUrls: ['./data-template.component.css']
})
export class DataTemplateComponent {
error: string;
productArray: Product[];
constructor(private productService: ProductService) {
this.productService = productService;
}
loadMobiles(){
let promise = this.productService.fetchMobiles();
promise.then(productArr => {
return this.productArray = productArr;
}).catch((err) => {
this.error = err;
});
}
}
ProductService.ts
import 'rxjs/add/operator/toPromise';
import {Http, HttpModule} from '#angular/http';
import {Injectable} from '#angular/core';
import {Product} from './product';
#Injectable()
export class ProductService{
http: Http;
constructor(http: Http){
this.http = http;
console.log(http);
}
fetchMobiles(): Promise<Product[]>{
let url = "https://raw.githubusercontent.com/xxxxx/Other/master/JsonData/MobileData.json";
return this.http.get(url).toPromise().then((response) => {
return response.json().mobiles as Product[];
}).catch(this.handleError);
}
private handleError(error: any): Promise<any> {
console.error('An error occurred', error);
return Promise.reject(error.message || error);
}
}
Sorry if the code bothers you. So basically i am failing to display service data in child_2.html when an action made in child_1.html.The service working fine and name is ProductService which uses Product.ts as an object to get the data in JSON format. Any kind of help is appreciated.
This doesn't work because the DataTemplateComponent you're instantiating in app-navigation isn't the same instance of DataTemplateComponent as the one on the page. It's a brand new one that you instantiated and that isn't bound to the page at all. What you're trying to achieve is component communication. Specifically, parent / child component communication. There are a number of ways to do this, the cleanest and most flexible / extensible way is with a shared service pattern. Basically, you declare a service with an observable in it that you inject into both services and one updates the observable while the other is subscribed to it, like this:
#Inject()
export class MyComponentCommunicationService {
private commSubject: Subject<any> = new Subject();
comm$: Observable<any> = this.commSubject.asObservable();
notify() {
this.commSubject.next();
}
}
Then provide this service, either at the app module or possibly at the parent component depending on needs then in app navigation:
constructor(private commService: MyComponentCommunicationService) {}
loadMobiles() {
this.commservice.notify();
}
and in data template:
constructor(private commService: MyComponentCommunicationService, private productService: ProductService) {}
ngOnInit() {
this.commSub = this.commService.comm$.subscribe(e => this.loadMobiles());
}
ngOnDestroy() { this.commSub.unsubscribe(); } // always clean subscriptions
This is probably a little unneccessary since you already have the product service there. You could probably just move the load mobiles logic into the product service and have that trigger an observable that the data template service is subscribed to, and have the nav component call the load mobile method on the product service, but this is just meant to illustrate the concept.
I'd probably do it like this:
#Inject()
export class ProductService {
private productSubject: Subject<Product[]> = new Subject<Product[]>();
products$: Observable<Product[]> = this.productSubject.asObservable();
loadMobiles() {
this.fetchMobiles().then(productArr => {
this.productSubject.next(productArr);
}).catch((err) => {
this.productSubject.error(err);
});
}
}
then nav component:
loadMobiles() {
this.myService.loadMobiles();
}
then data template:
ngOnInit() {
this.productSub = this.productService.products$.subscribe(
products => this.productArray = products,
err => this.error = err
);
}
ngOnDestroy() { this.productSub.unsubscribe(); } // always clean subscriptions