i dont know why i get this error. It is in AppComponent.html:4
Error trying to diff '[object Object]'. Only arrays and iterables are allowed
app.component.html
<h2>Movies</h2>
<ul>
<li *ngFor='let movie of movies'>
<h2>{{movie.title}}</h2>
</li>
</ul>
app.component.ts
import {Component} from '#angular/core';
import {ApiService} from './api.service';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers: [ApiService]
})
export class AppComponent {
movies = [{title: 'test'}];
constructor(private api: ApiService) {
this.getMovies();
}
getMovies = () => {
this.api.getAllMovies().subscribe(
data => {
this.movies = data;
},
error1 => {
console.log('error');
}
);
};
}
app.module.ts
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import {HttpClientModule} from '#angular/common/http';
import { AppComponent } from './app.component';
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
HttpClientModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Here is the service
import { Injectable } from '#angular/core';
import {HttpClient, HttpHeaders} from '#angular/common/http';
import {Observable} from 'rxjs';
#Injectable({ providedIn: 'root' })
export class ApiService {
baseurl = "http://127.0.0.1:8000"; httpHeader = new HttpHeaders({'Content-Type': 'application/json'});
constructor(private http: HttpClient) {}
getAllMovies(): Observable<any>
{
return this.http.get(this.baseurl + '/movies/', {headers: this.httpHeader});
}
}
Can you tell me what im doing wrong?
When using *ngFor you can only use arrays / iterables, what I can imagine is happening here is your API call is returning an object. You should find a way to change this to an array for your existing template to work. Providing what your response looks like should help with that.
For the mean while what you could do is use the keyvalue pipe. Which would make your code look like so.
<h2>Movies</h2>
<ul>
<li *ngFor='let movie of movies | keyvalue'>
<h2>{{movie.title}}</h2>
</li>
</ul>
After JSON was added
looking at the picture of your JSON (please try and use snippets rather than images) you could have. This means the above solution would not work amazingly but is something to remember as its useful for certain scenarios.
<h2>Movies</h2>
<ul>
<li *ngFor='let movie of movies.results'>
<h2>{{movie.title}}</h2>
</li>
</ul>
As the documentation will show, this will allow you to use *ngfor with an object as opposed to an array. This is part of commonModule.
Related
Currently I am struggling with an error after try to use a few mat components like "mat-form-field" or "mat-date-range-input" etc..
I've already imported them in the app.module file as I always do with these kind of component, but I get many errors like this:
If 'mat-form-field' is an Angular component, then verify that it is part of this module
If 'mat-label' is an Angular component, then verify that it is part of this module.
I have to say that in the same project I am using mat-tab-group and mat-tab etc... and I have not any errors with them.
These is my code:
app.module.ts
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { AngularMaterialModule } from './angular-material.module/angular-material.module.module';
import { MatButtonToggleModule } from '#angular/material/button-toggle';
import { MatSlideToggleModule } from '#angular/material/slide-toggle';
import { MatCardModule } from '#angular/material/card';
import { MatFormFieldModule } from '#angular/material/form-field';
import { MatInputModule } from '#angular/material/';
import { MatIconModule } from '#angular/material/icon';
import { ClipboardModule } from '#angular/cdk/clipboard';
#NgModule({
declarations: [], // I've omitted this part because is not relevant to this issue
imports: [
BrowserModule,
AppRoutingModule,
AngularMaterialModule,
MatProgressSpinnerModule,
MatButtonToggleModule,
MatSlideToggleModule,
MatCardModule,
MatFormFieldModule,
MatInputModule,
MatIconModule,
ClipboardModule
],
providers: [{provide: LocationStrategy, useClass: PathLocationStrategy}],
bootstrap: [AppComponent]
})
export class AppModule { }
And my component : create.report.component.html
<mat-form-field appearance="fill">
<mat-label>Enter a date range</mat-label>
<mat-date-range-input [formGroup]="reportForm" [rangePicker]="picker">
<input matStartDate formControlName="from" placeholder="Start date">
<input matEndDate formControlName="to" placeholder="End date">
</mat-date-range-input>
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-date-range-picker #picker></mat-date-range-picker>
</mat-form-field>
create.report.component.ts
import { Component, OnInit } from '#angular/core';
import { FormBuilder, Validators, FormGroup, FormArray, FormControl } from '#angular/forms';
import { Router } from '#angular/router';
#Component({
selector: 'app-report',
templateUrl: './create.report.component.html'
})
export class CreateReportComponent implements OnInit {
reportForm = new FormGroup({
from: new FormControl(),
to: new FormControl()
});
constructor(
private router: Router,
private fb: FormBuilder) {
this.buildForm();
}
ngOnInit(): void {
}
buildForm() {
// console.log('***buildForm this.hmService***', this.hmService);
this.reportForm = this.fb.group( {
from : [ '', [Validators.required, Validators.minLength(5)]],
to: [ '', [Validators.required, Validators.minLength(5)]],
published : [ '', [Validators.required, Validators.minLength(5)]]
});
}
}
Ok, finally I solved!
With this line I fix the issue I had with material components. Now I can use them in my create.report.component .
import { CreateReportComponent } from './pages/report/create/create.report.component';
Please help, i'm trying to lift up my django web page with REST API and use Angular FrontEnd where I am beginning. I have followed some tutorials on how to consume REST api and somehow I'm making a mistake there. The browser shows no errors when requesting the content but it is not coming. I appreciate every bit of help.....
here we go usluga-list.component.ts:
import { Component, OnInit } from '#angular/core';
import { Observable } from "rxjs";
import { Usluga } from "../models/usluga";
import { UslugaService } from "../services/usluga.service";
#Component({
selector: 'app-usluga-list',
templateUrl: './usluga-list.component.html',
styleUrls: ['./usluga-list.component.css']
})
export class UslugaListComponent implements OnInit {
uslugi: Observable<Usluga[]>;
constructor(private uslugaService: UslugaService) { }
ngOnInit() {
this.loadUslugiData();
}
loadUslugiData(){
this.uslugi = this.uslugaService.getAllUslugi();
then i have usluga.service.ts:
import { Injectable } from '#angular/core';
import { HttpClient } from "#angular/common/http";
import { Observable } from "rxjs";
import { Usluga } from "../models/usluga";
#Injectable({
providedIn: 'root'
})
export class UslugaService {
private endpoint ='http://localhost:8000/uslugi/';
constructor(private http: HttpClient) { }
getAllUslugi(): Observable<any>{
return this.http.get(this.endpoint)
}
getUsluga(id: number): Observable<any> {
return this.http.get(this.endpoint + id);
}
}
Then I have app-routing.ts:
import { NgModule } from '#angular/core';
import { Routes, RouterModule } from '#angular/router';
import { UslugaListComponent } from './usluga-list/usluga-list.component';
const routes: Routes = [
{path: '', redirectTo: 'uslugi', pathMatch: 'full'},
{path: 'uslugi', component: UslugaListComponent}
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
It is odd as i get the json via localhost:8000/uslugi and it must be something wrong in the Angular:
[{"id": 1, "title": "Wizyta", "text": "Wizyta", "price": "10.99", "slug": "wizyta-p", "category": "psyc", "lekarz_id": 1}, {"id": 2, "title": "Wizyta d", "text": "Wizyta dia to...", "price": "199.30", "slug": "wiz", "category": "sek", "lekarz_id": 1}]
In order for the http call to be made you need to subscribe to it.
In your component:
this.uslugi = this.uslugaService.getAllUslugi();
// if you just want to test that it works:
this.uslugi.subscribe();
The proper way is to unsubscribe, there are many ways of doing this. This is one way that only require 'rxjs' and operators. So what you do is essentially like this:
#Component({
selector: 'app-usluga-list',
templateUrl: './usluga-list.component.html',
styleUrls: ['./usluga-list.component.css']
})
export class UslugaListComponent implements OnInit, OnDestroy {
uslugi: Observable<Usluga[]>;
unsubscribe = new Subject();
constructor(private uslugaService: UslugaService) { }
ngOnInit() {
this.loadUslugiData();
}
ngOnDestroy() {
this.unsubscribe.next();
this.unsubscribe.complete();
}
loadUslugiData(){
this.uslugi = this.uslugaService.getAllUslugi();
this.uslugi.pipe(takeUntil(this.unsubscribe)).subscribe();
}
Then that should make the http call for you
The observable variable (that stores the object from the api) name must be the same as the name in the html template, alike (flightss):
#Component({
selector: 'app-flight-list',
templateUrl: './usluga-list.component.html',
styleUrls: ['./usluga-list.component.css']
})
export class UslugaListComponent implements OnInit {
flightss: Observable<Usluga[]>;
constructor(private uslugaService: UslugaService) { }
ngOnInit() {
this.loadUslugisData();
}
loadUslugisData() {
this.flightss = this.uslugaService.getAllUslugi();
}
}
and in the template:
<tr *ngFor="let flight of flightss | async; let i=index">
<td class="text-center">{{flight.id}}</td>
<td>{{flight.title}}</td>
<td>{{flight.text}}</td>
<td>{{flight.price}}</td>
<td>{{flight.slug}}</td>
<td>{{flight.category}}</td>
<td class="td-actions text-right">
<a type="button" class="btn btn-success btn-just-icon btn-sm "
style="margin-left:10px;">
<i class="material-icons">Edit</i>
</a>
<button type="button" rel="tooltip" class="btn btn-danger btn-just-icon btn-sm" data-original-title="" title="" style="margin-left:10px;">
<i class="material-icons">Delete</i>
</button>
</td>
</tr>
this is my anglar4 creating app name as search i want to fatch data from django rest API. I want that when i give username it give me only this username data.
app.components.tc
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 {
username:string = '';
constructor(private httpClient:HttpClient){}
onNameKeyUP(event:any){
this.username = event.target.value;
}
getProfile(){
console.log(this.username)
this.httpClient.get("http://127.0.0.1:8000/loginAdmin/?username=${this.username}")
.subscribe(
(data:any[]) => {
console.log(data);
}
)
}
}
app.module.ts
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { AppComponent } from './app.component';
import { HttpClientModule } from '#angular/common/http';
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
HttpClientModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
app.components.html
it is my simlple Html which is use to send username from Django API
<input type="text" (keyup)="onNameKeyUP($event)">
<button (click)="getProfile()">Get Profile</button>
Try this:
app.component.ts
import { Component, OnInit } from '#angular/core';
import { FormControl } from '#angular/forms';
import { HttpClient } from '#angular/common/http';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';
import {
filter,
map,
switchMap,
debounceTime,
distinctUntilChanged,
} from 'rxjs/operators';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
profile$: Observable<any>;
ctrl = new FormControl();
constructor(private httpClient: HttpClient) {}
ngOnInit() {
this.profile$ = this.ctrl.valueChanges.pipe(
filter(v => v.length > 2),
debounceTime(400),
distinctUntilChanged(),
switchMap(username =>
this.httpClient.get(
`http://127.0.0.1:8000/loginAdmin/?username=${username}`,
),
),
);
}
}
app.component.html
<input [formControl]="ctrl">
<div> {{ profile$ | async | json }} </div>
app.module.ts
import { ReactiveFormsModule } from '#angular/forms';
#NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
ReactiveFormsModule,
...
],
providers: [...],
bootstrap: [AppComponent],
})
export class AppModule {
constructor() {}
}
I have spent a while getting the hang of modules in Angular2 and really like them but I am a little uncertain as to best approach for testing both my modules and the components within. (I also realise that my app.component can and probably should be broken out more but for now it is helpful while learning the testing framework to be a little more complex)
For example this is my app.module:
import { createStore, compose, applyMiddleware } from 'redux';
import ReduxThunk from 'redux-thunk';
import { AUTH_PROVIDERS } from 'angular2-jwt';
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { FormsModule } from '#angular/forms';
import { ComponentsModule } from './components';
import { MaterialModule} from './material';
import { RouterModule } from '#angular/router';
import { AppComponent } from './app.component';
import { ViewsModule } from './+views';
import { rootReducer } from './dataStore';
import { CurrentUserModel } from './models/current-user'
const appStore = createStore(rootReducer, applyMiddleware(ReduxThunk));
const APP_DECLARATIONS = [
AppComponent
];
const APP_PROVIDERS = [
{ provide: 'AppStore', useValue: appStore },
CurrentUserModel
];
#NgModule({
imports:[
FormsModule,
BrowserModule,
RouterModule,// here as well as in our Views Module because of router-outlet
ViewsModule,
MaterialModule, // here as well as in our Views & componet Module because used in App componet
ComponentsModule
],
declarations: APP_DECLARATIONS,
bootstrap:[AppComponent],
providers: APP_PROVIDERS,
})
export class AppModule {
}
and this is what my app.component looks like:
import { Component, ViewChild, AfterViewInit } from '#angular/core';
import { Router } from '#angular/router';
#Component({
selector: 'app',
styleUrls:['app.component.scss'],
template: `
<md-toolbar>
<!-- <i class="material-icons demo-toolbar-icon">menu</i> -->
<span class="toolbar-brand">Franks</span>
<span *ngIf="searchActive" role="search" class="fill-remaining-space">
<span class="search-input-container flex flex-1">
<i class="material-icons search-link">search</i>
<input class="search-input" placeholder="Search" type="text" id="searchInput" #searchInput (keyup.esc)="exitSearch($event)"/>
</span>
</span>
<i *ngIf="searchActive" class="material-icons right selectable" (click)="exitSearch($event)">close</i>
<span *ngIf="!searchActive" class="fill-remaining-space">
</span>
<span *ngIf="!searchActive" role="navmenu">
<span class="hlink" routerLink="/" routerLinkActive="active">home</span>
<span class="hlink" routerLink="/profile" routerLinkActive="active">Profile</span>
<span class="hlink" routerLink="/login" routerLinkActive="active">Login</span>
<span class="hlink" routerLink="/signup" routerLinkActive="active">Sign Up</span>
<i class="material-icons search-link" (click)="activeSearch($event)">search</i>
</span>
</md-toolbar>
<div class="container">
<router-outlet></router-outlet>
</div>
`,
})
export class AppComponent {
#ViewChild('searchInput') searchInputRef;
ngAfterViewChecked() {
if(this.searchActive && this.searchInputRef){
console.log(this.searchInputRef);
this.searchInputRef.nativeElement.focus();
}
}
searchActive: boolean;
constructor(public router: Router) {
this.searchActive = false;
}
activeSearch(event):void {
this.searchActive = true;
}
exitSearch(event) : void {
this.searchActive = false;
}
}
So I know I can potentially mock out all the Components with for example the MaterialComponents (this is just a wrapper module for the material components) within my tests but this seems a little tedious. Is that my only options and if so does it make sense to make creating a mock of components when creating the components part of the process.
for informational purposes this is what my material module looks like and my views and components modules are similar:
import { NgModule } from '#angular/core';
// Material
import { MdCardModule } from '#angular2-material/card';
import { MdButtonModule } from '#angular2-material/button';
import { MdInputModule } from '#angular2-material/input';
import { MdToolbarModule } from '#angular2-material/toolbar';
import { MdListModule } from '#angular2-material/list';
import { MdIconModule, MdIconRegistry } from '#angular2-material/icon';
const MATERIAL_UI_MODULES = [
MdCardModule,
MdButtonModule,
MdInputModule,
MdToolbarModule,
MdIconModule,
MdListModule
]
const MATERIAL_UI_REGISTRIES = [
MdIconRegistry
]
#NgModule({
imports:[
...MATERIAL_UI_MODULES,
],
providers: MATERIAL_UI_REGISTRIES,
exports:[
...MATERIAL_UI_MODULES,
]
})
export class MaterialModule {
}
So I eventually figured out how to do this and this is my current solution:
/* tslint:disable:no-unused-variable */
import { TestBed, inject, async } from '#angular/core/testing';
import { RouterTestingModule } from '#angular/router/testing';
import { By } from '#angular/platform-browser';
import { Component,ViewChild, AfterViewChecked } from '#angular/core';
import { Router } from '#angular/router';
import { Location, CommonModule } from '#angular/common';
import { MaterialModule} from './material';
#Component({
template: '<div></div>'
})
class DummyComponent {
}
import { AppComponent } from './app.component';
describe('component: TestComponent', function () {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
CommonModule,
RouterTestingModule.withRoutes([
{ path: 'profile', component: DummyComponent },
{ path: 'login', component: DummyComponent },
{ path: 'signup', component: DummyComponent },
{ path: '', component: DummyComponent }
]),
MaterialModule
],
declarations: [ AppComponent, DummyComponent ]
});
});
it('should create the app', async(() => {
let fixture = TestBed.createComponent(AppComponent);
let app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
it('should be navigate to correct url for each option in navmenu',
async(inject([Router, Location], (router: Router, location: Location) => {
let fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
fixture.debugElement.query(By.css('span.hlink[routerLink="/profile"]')).nativeElement.click();
fixture.whenStable().then(() => {
expect(location.path()).toEqual('/profile');
expect(fixture.debugElement.query(By.css('span.hlink[routerLink="/profile"]')).classes['active']).toBeTruthy();
expect(fixture.debugElement.nativeElement.querySelectorAll('span.hlink.active').length).toEqual(1)
fixture.debugElement.query(By.css('span.hlink[routerLink="/login"]')).nativeElement.click();
return fixture.whenStable();
}).then(() => {
expect(location.path()).toEqual('/login');
expect(fixture.debugElement.query(By.css('span.hlink[routerLink="/login"]')).classes['active']).toBeTruthy();
expect(fixture.debugElement.nativeElement.querySelectorAll('span.hlink.active').length).toEqual(1)
fixture.debugElement.query(By.css('span.hlink[routerLink="/signup"]')).nativeElement.click();
return fixture.whenStable();
}).then(() => {
expect(location.path()).toEqual('/signup');
expect(fixture.debugElement.query(By.css('span.hlink[routerLink="/signup"]')).classes['active']).toBeTruthy();
expect(fixture.debugElement.nativeElement.querySelectorAll('span.hlink.active').length).toEqual(1)
fixture.debugElement.query(By.css('span.hlink[routerLink="/"]')).nativeElement.click();
return fixture.whenStable();
}).then(() => {
expect(location.path()).toEqual('/');
expect(fixture.debugElement.query(By.css('span.hlink[routerLink="/"]')).classes['active']).toBeTruthy();
expect(fixture.debugElement.nativeElement.querySelectorAll('span.hlink.active').length).toEqual(1)
});
})));
});
I have a listing component with following code:
///<reference path="../../node_modules/angular2/typings/browser.d.ts"/>
import { Component, OnInit } from 'angular2/core';
import { ROUTER_DIRECTIVES } from 'angular2/router';
import { Employee } from '../models/employee';
import { EmployeeListServiceComponent } from '../services/employee-list-service.component';
#Component({
selector: 'employee-list',
template: `
<ul class="employees">
<li *ngFor="#employee of employees">
<a [routerLink]="['EmployeeDetail', {id: employee.id}]">
<span class="badge">{{employee.id}}</span>
{{employee.name}}
</a>
</li>
</ul>
`,
directives: [ROUTER_DIRECTIVES],
providers: [EmployeeListServiceComponent]
})
export class EmployeeListComponent implements OnInit {
public employees: Employee[];
public errorMessage: string;
constructor(
private _listingService: EmployeeListServiceComponent
){}
ngOnInit() {
this._listingService.getEmployees().subscribe(
employees => this.employees = employees,
error => this.errorMessage = <any>error
);
}
}
I wish to write unit tests for the ngOninit hook. I have written following test:
/// <reference path="../../typings/main/ambient/jasmine/jasmine.d.ts" />
import {
it,
describe,
expect,
TestComponentBuilder,
injectAsync,
setBaseTestProviders,
beforeEachProviders,
} from "angular2/testing";
import { Component, provide, ApplicationRef, OnInit } from "angular2/core";
import {
TEST_BROWSER_PLATFORM_PROVIDERS,
TEST_BROWSER_APPLICATION_PROVIDERS
} from "angular2/platform/testing/browser";
import {
ROUTER_DIRECTIVES,
ROUTER_PROVIDERS,
ROUTER_PRIMARY_COMPONENT,
APP_BASE_HREF
} from 'angular2/router';
import {XHRBackend, HTTP_PROVIDERS} from "angular2/http";
import { MockApplicationRef } from 'angular2/src/mock/mock_application_ref';
import {MockBackend } from "angular2/src/http/backends/mock_backend";
import {Observable} from 'rxjs/Rx';
import 'rxjs/Rx';
import { Employee } from '../models/employee';
import { EmployeeListComponent } from './list.component';
import { EmployeeListServiceComponent } from '../services/employee-list-service.component';
class MockEmployeeListServiceComponent {
getEmployees () {
return Observable.of([
{
"id": 1,
"name": "Abhinav Mishra"
}
]);
}
}
#Component({
template: '<employee-list></employee-list>',
directives: [EmployeeListComponent],
providers: [MockEmployeeListServiceComponent]
})
class TestMyList {}
describe('Employee List Tests', () => {
setBaseTestProviders(TEST_BROWSER_PLATFORM_PROVIDERS, TEST_BROWSER_APPLICATION_PROVIDERS);
beforeEachProviders(() => {
return [
ROUTER_DIRECTIVES,
ROUTER_PROVIDERS,
HTTP_PROVIDERS,
provide(EmployeeListServiceComponent, {useClass: MockEmployeeListServiceComponent}),
provide(XHRBackend, {useClass: MockBackend}),
provide(APP_BASE_HREF, {useValue: '/'}),
provide(ROUTER_PRIMARY_COMPONENT, {useValue: EmployeeListComponent}),
provide(ApplicationRef, {useClass: MockApplicationRef})
]
});
it('Should be true',
injectAsync([TestComponentBuilder], (tcb) => {
return tcb
.createAsync(TestMyList)
.then((fixture) => {
fixture.detectChanges();
var compiled = fixture.debugElement.nativeElement;
console.log(compiled.innerHTML);
expect(true).toBe(true);
});
})
);
});
However, the output of console.log in the test is an empty ul tag as follows:
'<employee-list>
<ul class="employees">
<!--template bindings={}-->
</ul>
</employee-list>'
Can anyone suggest me the proper way of writing unit tests for component hooks?
SOLUTION
Mock the http request in the injectAsync block as follows:
backend.connections.subscribe(
(connection:MockConnection) => {
var options = new ResponseOptions({
body: [
{
"id": 1,
"name": "Abhinav Mishra"
}
]
});
var response = new Response(options);
connection.mockRespond(response);
}
);
However now i am getting another error as follows:
Failed: EXCEPTION: Component "EmployeeListComponent" has no route config. in [['EmployeeDetail', {id: employee.id}] in EmployeeListComponent#3:7]
ORIGINAL EXCEPTION: Component "EmployeeListComponent" has no route config.
ORIGINAL STACKTRACE:
Error: Component "EmployeeListComponent" has no route config.
If you call async code in ngOnInit() you can't assume it is completed when console.log(...) is executed. this.employees is only set when the callback you passed to subscribe(...) gets called after the response arrived.
If you use MockBackend you can control the response and after the response was passed you have to run fixture.detectChanges() again to make the component re-render with the updated data, then you can read innerHTML and expect it to contain the rendered content.