I'm trying to return a single article from a Express API using Angular 2.
The Express app has this section of code for single get request:
router.get('/articles/:articleId', (req, res) => {
let articleId = req.params.articleId;
Article.findById(articleId, (err, article) => {
if(err) {
res.send(err);
} else {
res.json(article)
}
});
});
If I do console.log(article) it returns the whole JSON object in the terminal so it's working.
Next, the Article Service looks like this:
import { Injectable } from '#angular/core';
import { Http, Headers } from '#angular/http';
import 'rxjs/add/operator/map';
#Injectable()
export class ArticleService {
constructor( private http:Http ) {
console.log('Article service initialized...')
}
getArticles() {
}
getArticle(id) {
return this.http.get('http://localhost:3000/api/articles/'+id)
.map(res => res.json());
}
addArticle(newArticle){
}
deleteArticle(id){
return this.http.delete('http://localhost:3000/api/articles/'+id)
.map(res => res.json());
}
}
With the code above the deleteArticle(id) works.
And finally, the ArticleDetailComponent looks like this:
import { Component, OnInit } from '#angular/core';
import { ArticleService } from '../services/article.service';
import { Router, ActivatedRoute, Params } from '#angular/router';
import { ArticleComponent } from '../article/article.component'
import 'rxjs/add/operator/switchMap';
#Component({
selector: 'app-articledetail',
templateUrl: './articledetail.component.html',
styleUrls: ['./articledetail.component.css']
})
export class ArticleDetailComponent implements OnInit {
article: ArticleComponent;
title: string;
text: string;
constructor(
private router: Router,
private route: ActivatedRoute,
private articleService:ArticleService){
}
ngOnInit() {
var id = this.route.params.subscribe(params => {
var id = params['id'];
this.articleService.getArticle(id)
.subscribe(article => {this.article = article});
console.log(id) //returns article id correctly
});
}
}
The articledetail.component.html looks like this:
<div class="article-container">
<div class="col-md-12">
<h2>{{article.title}}</h2>
{{article.text}}
<br><br>
</div>
</div>
When I run the application I can get a list of articles and delete articles by Id, but I can't get single articles to be displayed in the ArticleDetailComponent.
If I do console.log(id) within the ArticleDetailComponent it shows the article id, but I can't get the JSON object in the response and show it in the HTML.
Could somebody please tell me what's missing?
Thanks
I actually see where your mistake is.. you need to initialize article to empty object, because angular is probably throwing errors in the console that it cannot find article.title. The error is probably: cannot find title of undefined. And when angular throws an error like that the whole app freezes, and you cannot do anything. So initialize article like this:
article: any = {} and it will work
The other alternative would be to use the "safe operator" (?) in the template like
{{article?.title}}. This prevents the error, so if article is undefined it wont throw the exception, but its not a good practice rly
The third alternative would be to add *ngIf on the HTML which is throwing errors if article is undefined. Like this:
<div class="article-container" *ngIf="article">
<div class="col-md-12">
<h2>{{article.title}}</h2>
{{article.text}}
<br><br>
</div>
</div>
Related
I am trying to list users from a REST-API reqres. but when I click the button to list users, I get
Cannot find a differ supporting object '[object Object]' of type 'object'. NgFor only supports binding to Iterables such as Arrays.
I can list users in console but not in page. I read that last Angular version does not read map function and I do not know why I am getting this error.
This is my users.component.ts file:
import { Component, OnInit } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import 'rxjs/add/operator/map'
#Component({
selector: 'app-users',
templateUrl: './users.component.html',
styleUrls: ['./users.component.css'],
})
export class UsersComponent implements OnInit {
users: any;
constructor(private http: HttpClient) {}
ngOnInit() {}
public getUsers() {
this.users = this.http.get('https://reqres.in/api/users')
}
}
And this is my users.component.html file:
<button (click)="getUsers()">list users</button>
<div *ngFor="let user of users">
{{ user | json }}
</div>
this.http.get() returns an Observable. When you assign this.users = this.http.get(), the users object will be an Observable and ngFor won't be able to iterate over it.
ngOnInit() {
this.users = []; // to prevent ngFor to throw while we wait for API to return data
}
public getUsers() {
this.http.get('https://reqres.in/api/users').subscribe(res => {
this.users = res.data;
// data contains actual array of users
});
}
I'm working on autocomplete-search with angular 4. This search bar will get books information from Google Books API. It works fine when I input any search terms. But it causes an error if I remove the entire search term or input a space.This is the error I got
This is my SearchComponent.ts
import { Component, OnInit } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { Observable } from 'rxjs/Observable';
#Component({
selector: 'app-admin-search',
templateUrl: './admin-search.component.html',
styleUrls: ['./admin-search.component.css']
})
export class AdminSearchComponent implements OnInit {
books: any[] = [];
searchTerm$ = new Subject<string>();
constructor (private bookService: BookService,
private http: HttpClient
) {
this.bookService.search(this.searchTerm$)
.subscribe(results => {
this.books = results.items;
});
}
ngOnInit() {
}
This is my SearchComponent.html
<div>
<h4>Book Search</h4>
<input #searchBox id="search-box"
type="text"
placeholder="Search new book"
(keyup)="searchTerm$.next($event.target.value)"/>
<ul *ngIf="books" class="search-result">
<li *ngFor="let book of books">
{{ book.volumeInfo.title }}
</li>
</ul>
</div>
This is my BookService.ts
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { Book } from './book';
import { BOOKS } from './mock-books';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/switchMap';
#Injectable()
export class BookService {
private GoogleBookURL: string = "https://www.googleapis.com/books/v1/volumes?q=";
constructor (private http: HttpClient) { }
search(terms: Observable<string>) {
return terms.debounceTime(300)
.distinctUntilChanged()
.switchMap(term => this.searchEntries(term));
}
searchEntries(searchTerm: string) {
if (searchTerm.trim()) {
searchTerm = searchTerm.replace(/\s+/g, '+');
let URL = this.GoogleBookURL + searchTerm;
return this.http.get(URL);
}
}
}
Can someone help me out? Thanks in advance!
Your method searchEntries returns value (Observable<Response>) only if searchTerm.trim() is true (so it must return non-empty string).
There can be situation that searchEntries will return undefined instead of Obervable<Response> if trim() returns '' (empty string which is false). You can't pass undefined returned from searchEntries into .switchMap(term => this.searchEntries(term));.
For that case your code will look like this:
.switchMap(term => undefined) which is not valid construction.
I am trying to implement async validator in my Reactive Form in angular 4.3.4 which will check that entered email exists or not in your system.
but this does not work properly, earlier it was invoking on every key up so I made some changes and make it Observable now only Checking after a given debounce time. checking...' text is displaying but the response comes but no error is being displayed on the page.
what can be the issue? I have very base knowledge of Observable and angular 4. please help me what is the issue. I have checked in the console and it is going and print the value in the asyncvalidator function.
here is the relevant code.
signup.component.html
<form [formGroup]="myForm" novalidate #formDir="ngForm" (ngSubmit)="doSignup()">
<input type="email" formControlName="email" pattern="{{email_pattern}}"/>
<div [hidden]="myForm.controls.email.valid || myForm.controls.email.pristine" class="text-danger">
<div *ngIf="myForm.controls.email.required">Please enter Email</div>
<div *ngIf="myForm.controls.email.pattern">Invalid Email</div>
<div *ngIf="myForm.controls.email.status === 'PENDING'">
<span>Checking...</span>
</div>
<div *ngIf="myForm.controls.email.errors && myForm.controls.email.errors.emailTaken">
Invitation already been sent to this email address.
</div>
</div>
<button type="submit" [disabled]="!myForm.valid">Invite</button>
</form>
signup.component.ts
import { FormBuilder, FormGroup, Validators, FormControl } from '#angular/forms';
import { ValidateEmailNotTaken } from './async-validator';
export class SignupComponent implements OnInit {
public myForm: FormGroup;
constructor(
private httpClient: HttpClient,
private fb: FormBuilder
) {
}
ngOnInit(): void {
this.buildForm();
}
private buildForm() {
this.inviteForm = this.fb.group({
firstname: [''],
lastname: [''],
email: [
'',
[<any>Validators.required, <any>Validators.email],
ValidateEmailNotTaken.createValidator(this.settingsService)
]
});
}
asyn-validator.ts
import { Observable } from 'rxjs/Observable';
import { AbstractControl } from '#angular/forms';
import { UserService } from './user.service';
export class ValidateEmailNotTaken {
static createValidator(service: UserService) {
return (control: AbstractControl): { [key: string]: any } => {
return Observable.timer(500).switchMapTo(service.checkEmailNotTaken(control.value))
.map((res: any) => {
const exist = res.item.exist ? { emailTaken: true } : { emailTaken: false };
console.log('exist: ', exist);
return Observable.of(exist);
})
.take(1);
};
}
}
user.service.ts
checkEmailNotTaken(email) {
const params = new HttpParams().set('email', email);
return this.httpClient.get(`API_END_POINT`, {
headers: new HttpHeaders({
'Content-type': 'application/json'
}),
params: params
});
}
You use Observable.timer(500) without a second argument, so after 500 milliseconds, it completes and never runs again. So first thing to do is to pass that argument - Observable.timer(0, 500).
switchMapTo cancels its previous inner Observable (service.checkEmailNotTaken(control.value) in your case) every time source Observable emits new value (so every 500 milliseconds). So if your http request lasts longer, you wont get its response. Thats why usually switchMap and switchMapTo are not suitable for http requests.
Here is an illustration:
const source = Rx.Observable.timer(0, 500);
const fail = source.switchMapTo(Rx.Observable.of('fail').delay(600))
const success = source.switchMapTo(Rx.Observable.of('success').delay(400))
const subscribe = fail.subscribe(val => console.log(val));
const subscribe2 = success.subscribe(val => console.log(val));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.6/Rx.js"></script>
So you should pick another flattening operator, like flatMap:
const source = Rx.Observable.timer(0, 500);
const success = source.flatMap(()=>Rx.Observable.of('success').delay(600))
const subscribe = success.subscribe(val => console.log(val));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.6/Rx.js"></script>
I know its too late for the answer but anyone facing same issue might find it useful:
apart from above answer the AsyncValidatorFn should return Promise<ValidationErrors | null> | Observable<ValidationErrors | null>.
Return value of ValidationErrors | null isn't correct.
Check out official docs
Today I am trying to switch from using mock data stored in a const to using the same data stored on my local MongoDB, but I'm getting the error:
Uncaught (in promise): Error: Error in ./FoodListComponent class FoodListComponent - inline template:2:30 caused by: Cannot read property 'filter' of undefined TypeError: Cannot read property 'filter' of undefined
at SearchPipe.transform (search.pipe.ts:15)
The error occurs because of a search pipe on my *ngFor # inline template:2:30
<div *ngFor="let food of foods | searchPipe: 'mySearchTerm'">
The error message is especially odd to me because the service is returning an Observable, not a Promise.
If I remove that search pipe then every thing works fine, but I have no search functionality. It's as if the template is compiling before the data gets there. How can I correct this?
food-list.component.ts
import { Component, OnInit, OnDestroy } from '#angular/core';
import { Food } from '../../../interfaces/diet/food'
import { FoodsService } from '../../../services/foods/foods.service';
#Component({
selector: 'food-list',
templateUrl: './food-list.component.html',
styleUrls: ['./food-list.component.scss'],
providers: [ WorkingDataService, FoodsService ]
})
export class FoodListComponent implements OnInit, OnDestroy {
foods: Food[];
constructor ( private _foodsService: FoodsService) { }
ngOnInit(): void {
// this._foodsService.getFoods().subscribe(foods => this.foods = foods); // this worked fine
this._foodsService.getMongoFoods().subscribe(foods => this.foods = foods);
}
}
foods.service.ts
import { Injectable } from '#angular/core';
import { Food } from '../../interfaces/diet/food'
import { FOODS } from './mock-foods';
import { Observable } from "rxjs/Rx";
import { Http, Response } from '#angular/http';
#Injectable()
export class FoodsService {
baseURL: string;
constructor(private http: Http) {
this.baseURL = 'http://localhost:3000/'
}
getFoods(): Observable<Food[]> { // this worked with my search pipe
return Observable.of(FOODS); // I'm returning an observable to a const
}
getMongoFoods(): Observable<Food[]>{
return this.http.get(this.baseURL + 'api/foods')
.map(this.extractData)
.catch(this.handleError);
}
// ... standard response and error handling functions
}
search.pipe.ts
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'searchPipe',
pure: false
})
export class SearchPipe implements PipeTransform {
transform(foods: any[], mySearchTerm: string): any[] {
let mySearchTerm = mySearchTerm.toUpperCase();
foods = foods.filter(food => { // The failure hits here because foods isn't defined yet
// my filter logic
});
}
}
Until your observable resolves itself, your foods array is undefined to start with in food-list.component.ts because you haven't initialised it:
foods: Food[];
if you change that to
foods: Food[] = [];
it should work.
Alternatively you can do a check for undefined at the start of your pipe, something like:
if (!foods) return foods;
at the moment iam implementing a signIn into my ionic 2 app.
I want to use ionic 2 native facebook and somehow save the data to my firebase app.
Is there any way to archive that?
One way is to create a new firebase auth user with the facebook email adress and some password hash, but maybe there is a better solution.
Here is what i got so far (i know, not much) :)
import {NavController, Loading, Platform, Storage, LocalStorage} from "ionic-angular";
import {OnInit, Inject, Component} from "#angular/core";
import {ForgotPasswordPage} from "../forgot-password/forgot-password";
import {SignUpPage} from "../sign-up/sign-up";
import {HomePage} from "../../home/home";
import * as firebase from 'firebase';
import {Facebook} from 'ionic-native';
/*
Generated class for the LoginPage page.
See http://ionicframework.com/docs/v2/components/#navigation for more info on
Ionic pages and navigation.
*/
#Component({
templateUrl: 'build/pages/auth/login/login.html',
})
export class LoginPage {
private local: any;
constructor(private navCtrl: NavController, private platform:Platform) {
this.local = new Storage(LocalStorage);
}
openForgotPasswordPage():void {
this.navCtrl.push(ForgotPasswordPage);
}
openSignUpPage():void {
this.navCtrl.push(SignUpPage);
}
login() {
firebase.auth().signInWithEmailAndPassword("test#test.com", "correcthorsebatterystaple").then(function (result) {
console.log("AUTH OK "+ result);
}, function (error) {
console.log("dawdaw");
});
}
facebookLogin() {
Facebook.login(['public_profile', 'user_birthday']).then(() => {
this.local.set('logged', true);
this.navCtrl.setRoot(HomePage);
}, (...args) => {
console.log(args);
})
} }
facebookLogin() {
Facebook.login(['public_profile', 'user_birthday']).then((result) => {
var creds = firebase.auth.FacebookAuthProvider.credential(result.access_token);
return firebase.auth().signInWithCredential(creds);
})
.then((_user) => {
console.log("_user:", _user);
})
.catch((_error) => {
console.error("Error:", _error);
});
}
see more info here - https://firebase.google.com/docs/auth/web/facebook-login#advanced-handle-the-sign-in-flow-manually
I have not tried this, so might not be 100% working, but try this Gist I found: https://gist.github.com/katowulf/de9ef6b04552091864fb807092764224