angular 2 no providers in karma/jasmine testing - javascript

First I've searched for "No provider for Http", as the error was showing, but I've realised that I've imported http within videoplayer.component, but I diddn't used it. But I've learned much about mocking services and especially the post on an observable abstract mock up is very helpfull. After removing the http from the videoplayer.component, I've got the next error: VideoService is not defined. I've forgotten the import in the testing module! After resolving this in the test component, I've received "No provider for HTTP" again.
So first my testing code (videoplayer.component.spec.ts) with the mock up from the post above
import { ComponentFixture, TestBed } from '#angular/core/testing';
import { By } from '#angular/platform-browser';
import { DebugElement } from '#angular/core';
import { async } from '#angular/core/testing';
import { AbstractMockObservableService }
from '../shared/test/abstract.mock.observable.service';
import { VideoPlayer } from './videoplayer.component';
import { VideoService } from './video.service';
describe('VideoPlayer (inline template)', () => {
class MockService extends AbstractMockObservableService {
getLoading(cb) {
return true;// stubbing?
}
pushLoadState() {
return false;// stubbing?
}
getOptions(requestDisplayId) : Observable<VideoDetail> {
return false;// return Observable???
}
reservePlayer(displayID,domID) { // TODO register ids?
return false;
}
}
let mockService;
let comp: VideoPlayer;
let fixture: ComponentFixture<VideoPlayer>;
let de: DebugElement;
let el: HTMLElement;
// async beforeEach
beforeEach(async(() => {
mockService = new MockService();
TestBed.configureTestingModule({
declarations: [ VideoPlayer ], // declare the test component
providers: [ {provide: VideoService, useValue: mockService } ],
})
.compileComponents(); // compile template and css
}));
// synchronous befareEach...
beforeEach(() => {
fixture = TestBed.createComponent(VideoPlayer);
comp = fixture.componentInstance; // VideoPlayer test instance
// query for the title <h1> by CSS element selector
de = fixture.debugElement.query(By.css('.videocontainer'));
el = de.nativeElement;
console.log(de);
console.log(el);
});
it('SOME CONTENT', () => {
});
});
Now the videoplayer.component.ts, please excuse the direct dom manipulations for the Youtube iFrame-API, I'm still searching for an Angular2 compatible way.
import {Component,Input,Output,Inject,ElementRef} from '#angular/core';
import {NgIf} from '#angular/common';
import {VideoService} from './video.service';
#Component({
selector: 'my-video-player',
providers: [VideoService],
template: require('./videoplayer.html'),
})
export class VideoPlayer implements OnChanges {
#Input() displayid : String;
#Input() displayflag : Number;
// -1 button to load player,0 no display,1 load but dont start,
// 2 autostart after load
#Output() onStateChange = new EventEmitter<any>();
private options : Object;
private loadingFlag : boolean;
private errorFlag : boolean;
private myPlayer : Object;
ngOnChanges(changes: SimpleChanges) {
var refreshed = false;
if (changes.includes('displayid'))
{
let did = changes['displayid'];
if (did.previousValue !== did.currentValue) {
refreshPlayer();
refreshed = true;
}
}
if (!refreshed && changes.includes('displayid'))
{
let did = changes['displayflag'];
if (did.previousValue !== did.currentValue) {
refreshPlayer();
refreshed = true;
}
}
/*for (let propName in changes) {
let chng = changes[propName];
let cur = JSON.stringify(chng.currentValue);
let prev = JSON.stringify(chng.previousValue);
}*/
}
constructor(#Inject(ElementRef) elr: ElementRef,
#Inject(VideoService) videoService: VideoService) {
this.loadingFlag = true;
}
ngOnInit() {
refreshPlayer();
}
loadPlayer() {
this.displayflag = 2;
this.refreshPlayer();
}
refreshPlayer() {
// loadingFlag with Callback
this.loadingFlag = videoService.getLoading(this.refreshPlayer);
if (this.loadingFlag) return;
if (this.myPlayer !== null) {
this.myPlayer.destroy();
this.myPlayer = null;
}
this.errorFlag = false;
if (this.displayid !== null && this.displayflag > 0) {
console.log('refreshPlayer: ' + this.displayid);
videoService.getOptions(this.displayid)
.subscribe(
this.getOptionsOnNext,
this.getOptionsOnError,
this.getOptionsOnCompleted
);
}
}
private getOptionsOnNext(data) {
this.displayid = data.displayid; //?
var myId = 'vp'+this.displayid;
// check if element exists
if (document.getElementById(myId)===null)
{
var newNode = document.createElement("div");
newNode.id = myId;
elr.nativeElement.insertBefore(newNode);
}
this.options = data.options;
this.options.events = {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange,
'onError': onPlayerError,
};
this.myPlayer = new YT.Player(myId, options);
videoService.reservePlayer(this.displayid,myId);
}
private getOptionsOnError(err) {
// console.log(err);
this.errorFlag = true;
}
private getOptionsOnCompleted() {
console.log('getOptionsOnCompleted: ' + this.displayid);
}
private onPlayerReady(event) {
if (this.displayflag == 2)
event.target.playVideo();
}
private onPlayerStateChange(event) {
onStateChange.emit(event.data);
}
private onPlayerError(event) {
console.log(event);
errorFlag = true;
}
}
And here is the stack trace, telling me nothing.
Error: No provider for Http!
Error: DI Error
at NoProviderError.ZoneAwareError (webpack:///~/zone.js/dist/zone.js:811:0 <- src/polyfills.ts:3833:33)
at NoProviderError.BaseError [as constructor] (webpack:///~/#angular/core/src/facade/errors.js:22:0 <- src/test.ts:22301:16)
at NoProviderError.AbstractProviderError [as constructor] (webpack:///~/#angular/core/src/di/reflective_errors.js:54:0 <- src/test.ts:45305:16)
at new NoProviderError (webpack:///~/#angular/core/src/di/reflective_errors.js:116:0 <- src/test.ts:45367:16)
at ReflectiveInjector_._throwOrNull (webpack:///~/#angular/core/src/di/reflective_injector.js:485:0 <- src/test.ts:63446:19)
at ReflectiveInjector_._getByKeyDefault (webpack:///~/#angular/core/src/di/reflective_injector.js:524:0 <- src/test.ts:63485:25)
at ReflectiveInjector_._getByKey (webpack:///~/#angular/core/src/di/reflective_injector.js:456:0 <- src/test.ts:63417:25)
at ReflectiveInjector_.get (webpack:///~/#angular/core/src/di/reflective_injector.js:325:0 <- src/test.ts:63286:21)
at TestBed.get (webpack:///~/#angular/core/bundles/core-testing.umd.js:827:0 <- src/test.ts:8978:67)
at CompiledTemplate.proxyViewClass.AppView.injectorGet (webpack:///~/#angular/core/src/linker/view.js:152:0 <- src/test.ts:64221:45)
at CompiledTemplate.proxyViewClass.DebugAppView.injectorGet (webpack:///~/#angular/core/src/linker/view.js:580:0 <- src/test.ts:64649:49)
at CompiledTemplate.proxyViewClass.View_VideoPlayer_Host0.createInternal (/DynamicTestModule/VideoPlayer/host.ngfactory.js:19:55)
at CompiledTemplate.proxyViewClass.AppView.createHostView (webpack:///~/#angular/core/src/linker/view.js:108:0 <- src/test.ts:64177:21)
at CompiledTemplate.proxyViewClass.DebugAppView.createHostView (webpack:///~/#angular/core/src/linker/view.js:564:0 <- src/test.ts:64633:52)
at ComponentFactory.create (webpack:///~/#angular/core/src/linker/component_factory.js:202:0 <- src/test.ts:33107:25)

Related

Angular Component: Impossible to loop through an array of object with TypeScypt

can any one please tell me why I can not loop through this array?
In ngOnInit, everything works fine. I got an array that I successfully display in the template.
But in ngAfterViewInit, console.log show the array but when looping through with "for of" or "forEach", nothing works.
import { JobsService } from '../jobs.service';
import {Job} from '../models/Job';
#Component({
selector: 'app-job',
templateUrl: 'job.component.html'
})
export class JobComponent implements OnInit, AfterViewInit {
title = 'Job';
jobs: Job[] = [];
InProcess = '';
CurrentPartner = '';
ShowProcess = false;
sended = '';
constructor(private jobsService: JobsService) {
}
ngOnInit() {
this.jobs = this.jobsService.getJobs();
}
ngAfterViewInit() {
console.log(this.jobs); // Show the array
// Nothing happened when looping through the array
this.jobs.forEach((oneJob) => {
console.log(oneJob);
});
}
}
Screenshot of the console in Google Chrome
The content of the service:
import { HttpClient, HttpErrorResponse } from '#angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import {Job} from './models/Job';
interface IJob {
message: string;
jobs: any[];
}
#Injectable({
providedIn: 'root'
})
export class JobsService {
constructor(private httpClient: HttpClient) { }
private REST_API_SERVER = 'http://localhost:8080/myband/api/getjobs.php';
private REST_API_SERVER_SEND = 'http://localhost:8080/myband/api/sendjob.php';
jobList: Job[] = [];
errorMessage: any;
message: string;
static handleError(err: HttpErrorResponse) {
let errorMessage = '';
if (err.error instanceof ErrorEvent) {
errorMessage = `An error occurred: ${err.error.message}`;
} else {
errorMessage = `Server returned code: ${err.status}, error message is: ${err.message}`;
}
console.error(errorMessage);
return throwError(errorMessage);
}
public getJobs() {
this.requestJobs().subscribe(
iJob => {
this.message = iJob.message;
for (const job of iJob.jobs) {
const oneJob: Job = new Job(job);
this.jobList.push(oneJob);
}
},
error => this.errorMessage = error as any
);
return this.jobList;
}
public requestJobs(): Observable<IJob> {
return this.httpClient.get<IJob>(this.REST_API_SERVER).pipe(
catchError(JobsService.handleError)
);
}
}
The first thing I want to say to you is about isolation of responsibilities.
Your service must have just one job: provider one way to access your data; It means your logic inside getJobs() method could be done in your component.
export class JobsService {
constructor(
private httpClient: HttpClient,
) {}
private REST_API_SERVER = 'http://localhost:8080/myband/api/getjobs.php';
public requestJobs(): Observable<IJob> {
return this.httpClient.get<IJob>(this.REST_API_SERVER);
}
}
Now, you can handler your data in your component.
import { JobsService } from '../jobs.service';
#Component({
selector: 'app-job',
templateUrl: 'job.component.html'
})
export class JobComponent implements OnInit, AfterViewInit {
title = 'Job';
jobs$;
InProcess = '';
CurrentPartner = '';
ShowProcess = false;
sended = '';
constructor(private jobsService: JobsService) {
}
ngOnInit() {
this.jobs$ = this.jobsService.requestJobs();
}
ngAfterViewInit() {
this.jobs$
.pipe(
map(() => {}), // change your data here
catchError(() => {}) // handler your error here;
)
.subscribe(
() => {} // have access to your final data here.
);
}
}
Things to know:
You can remove the subscribe() execution and use the async pipe in your template;
The use of the operator map in pipe() is optional, you can handler your final data directly from your first callback subscribe().
You can convert your Observable to Promise using toPromise() method in one observable. Don't forgot async / await in your ngAfterViewInit.
Let me know if there is something I can help.
Try:
Object.keys(this.jobs).forEach(job => {
console.log(this.jobs[job]);
});
Try to assign an iterator function with below part replacement by this code:
// Nothing happened when looping through the array
this.jobs.forEach(oneJob, function(value, key) {
console.log(key + ': ' + value);
});
Usage of forEach in AngularJS:
For documentation try to check AngularJS forEach Docs
Syntax:
someIterable.forEach(object, iterator, [context])
Please check below example
class Job {
id: any;
status: any;
constructor(obj: any) {
this.id = obj.id;
this.status = obj.status;
}
}
let arr = [
{
id: 1,
status: "job"
}, {
id: 2,
status: "job2"
}
];
let newArr: any = [];
arr.forEach(a => {
let obj: Job = new Job(a);
newArr.push(obj);
})
console.log(newArr);
newArr.forEach((a: any) => {
console.log(a);
})

Why change in Observable variable triggers change in View Angular

I'm learning Angular, so I'm building todo app. Todos are fetched from API, and every one of them has a project as parent. In a view I'm adding new project which triggers addNewProject method in service which in turn triggers POST request to the API in another service. Projects are listed in the sidebar, bind to input from parent component. ProjectsService holds array of projects in a private field, and has observable which is used by main component.
I'm struggling to understand why on earth appending private property _projects after API call in the service triggers change in MainComponent property even though _projects is private and change in Observable from array should not trigger functions passed from Observers.
Parent:
#Component({
selector: 'app-main',
templateUrl: './main.component.html',
styleUrls: ['./main.component.css']
})
export class MainComponent implements OnInit {
projects: Project[] = new Array<Project>();
constructor(private projectsService: ProjectsService) {
}
ngOnInit() {
this.projectsService.projects$.subscribe((projects) => {
this.projects = projects;
});
}
addNewProject(newProjectName: string) {
this.projectsService.addNewProject(newProjectName);
}
}
Sidebar:
#Component({
selector: 'app-sidebar',
templateUrl: './sidebar.component.html',
styleUrls: ['./sidebar.component.css']
})
export class SidebarComponent implements OnInit {
#Input() projects: Project[];
#Output() projectEntered = new EventEmitter<string>();
constructor() {
}
ngOnInit() {
}
projectAdded(projectName: string) {
this.projectEntered.emit(projectName);
}
}
Service:
export class ProjectsService {
private _projects$: Observable<Array<Project>> = new Observable<Array<Project>>();
private _projects: Project[] = [];
private _loadedProject$: Observable<Project>;
private _projectsLoaded: boolean;
private _taskForProject: object;
constructor(private userService: UserService, private api: ApiService) {
}
loadAllProjects() {
this._projects$ = this.api.getAllProjectsByUserId(this.userService.userId).pipe(
map((projects) => {
this._projects = projects;
return this._projects;
})
);
}
get projects$() {
if (!this._projectsLoaded) {
this.loadAllProjects();
}
return this._projects$;
}
getAllTasks() {
return this.api.getAllTasksByUserId(this.userService.userId);
}
getProject(projectId: string) {
this._loadedProject$ = this.api.getProjectById(projectId);
return this._loadedProject$;
}
getTodayTasksForProject(tasks: Task[]) {
const todayTasks: Task[] = [];
const todayDate = new Date();
tasks.forEach((task) => {
if (new Date(task.completionPlannedDate).getDate() === todayDate.getDate()) {
tasks.splice(tasks.indexOf(task), 1);
todayTasks.push(task);
}
});
return todayTasks;
}
getTomorrowTasksForProject(tasks: Task[]) {
const tomorrowTasks: Task[] = [];
const tomorrowDate = new Date(new Date().getDate() + 1);
tasks.forEach((task) => {
if (new Date(task.completionPlannedDate).getDate() === tomorrowDate.getDate()) {
tasks.splice(tasks.indexOf(task), 1);
tomorrowTasks.push(task);
}
});
return tomorrowTasks;
}
getUpcomingTasks(tasks: Task[]) {
const upcomingTasks: Task[] = [];
const upcomingDate = new Date(new Date().getDate() + 2);
tasks.forEach((task) => {
if (new Date(task.completionPlannedDate).getDate() > upcomingDate.getDate()) {
tasks.splice(tasks.indexOf(task), 1);
upcomingTasks.push(task);
}
});
return upcomingTasks;
}
addNewProject(projectName: string) {
this.api.postNewProject({
id: null,
userId: this.userService.userId,
title: projectName,
tasks: []
}).subscribe((project: Project) => {
this._projects.push(project);
});
}
}
Please see here:
Basically application state change can be caused by three things:
Events - click, submit
XHR - Fetching data from a remote server
Timers - setTimeout(), setInterval()
If you don't want change detection to fire try changing to ChangeDetectionStrategy.OnPush

infinite loop with angularjs httpInterceptor injecting service using toastr

I'm getting and issue with my httpInterceptor and my toastr Service. It always make infinite loop
AlertService (using toastr)
class AlertService {
constructor(toastr) {
this.toastr = toastr;
this.toastrCommonOpts = {
positionClass: "toast-bottom-center",
allowHtml: true,
closeButton: true,
}
}
notificationError (msg, title) {
let errorMsg = msg || 'error';
this.toastr.error(errorMsg, title || '', this.toastrCommonOpts);
};
notificationWarning(msg, title) {
let warningMsg = msg || 'warning';
this.toastr.warning(warningMsg, title || '', this.toastrCommonOpts);
}
notificationSuccess(msg, title, duration) {
let successMsg = msg || 'Success';
this.toastr.success(successMsg, title || '', this.toastrCommonOpts);
}
}
AlertService.$inject = ['toastr'];
export default AlertService ;
myHttpInterceptor
class HttpInterceptor {
constructor() {
['request', 'response']
.forEach((method) => {
if(this[method]) {
this[method] = this[method].bind(this);
}
});
}
}
class MyHttpInterceptor extends HttpInterceptor{
constructor(AlertService) {
super();
this.AlertService = AlertService;
}
request(config) {
this.AlertService.notificationSuccess();
return config;
};
response(response){
this.AlertService.notificationSuccess();
return response;
}
}
MyHttpInterceptor.$inject = ['AlertService'];
export default MyHttpInterceptor;
myModule
import MyHttpInterceptor from './myHttpInterceptor';
import AlertService from "./alert.service";
export default angular.module('app.core', [toastr])
.service('AlertService', AlertService)
.factory('MyHttpInterceptor', MyHttpInterceptor)
.config(($httpProvider) => {
$httpProvider.defaults.withCredentials = true;
$httpProvider.interceptors.push('MyHttpInterceptor');
});
I extends myHttpInterceptor and bind my methods to prevent loosing context 'this' (cf : angularjs http interceptor class (ES6) loses binding to 'this' ). I did not succeed to use arrowFunctions as class methods.
I also tried to do it following this issue
Would any of you have already encountered this problem?
Thanks :)

Typescript/Angular2 TypeError: Cannot read property of undefined

Ang2 Component:
import { Component, OnInit } from '#angular/core';
import { AngularFire, FirebaseListObservable, FirebaseObjectObservable } from 'angularfire2';
import Chart from 'chart.js'
#Component({
selector: 'app-prchart',
templateUrl: './app-prchart.component.html',
styleUrls: ['./app-prchart.component.css']
})
export class AppPRChartComponent implements OnInit {
userEmail: any;
email: string;
uid: any;
records = [];
newRecords = [];
filteredRecords = [];
labels = [];
public barChartOptions:any = {
scaleShowVerticalLines: false,
responsive: true,
legend: {
display: false
}
};
public barChartColors: any [] =[
{
backgroundColor:'rgba(30, 136, 229, 1)'
}
];
public barChartType:string = 'bar';
constructor(public af: AngularFire) {
var auth = this.af.auth.subscribe( (user) => {
if (user) {
this.userEmail = this.af.auth.subscribe(auth => {
const queryObservable = af.database.list('/users/'+ auth.auth.uid +'/records/', {
});
queryObservable.subscribe(queriedItems => {
this.records.push(queriedItems);
});
// Filter records into PR's
this.newRecords =
this.records[0].sort((a, b) => {
if (a.movement === b.movement) {
return a.weight >= b.weight ? -1 : 1
}
return a.movement > b.movement ? 1 : -1
})
.filter((rec, i, arr) => {
if (i === 0) return true
return rec.movement !== arr[i - 1].movement
});
let recordString = JSON.stringify(this.newRecords);
let recordParse = JSON.parse(recordString);
this.filteredRecords.push(recordParse);
});
} else {
}
});
this.filteredRecords[0].forEach(function(snapshot) {
this.labels.push(snapshot.movement);
//barChartData.push(snapshot.weight);
});
//console.log(barChartLabels);
//console.log(barChartData);
}
ngOnInit() {
}
}
I'm trying to push items into an array but I keep getting the following error:
TypeError: Cannot read property 'labels' of undefined
The error occurs towards the bottom where this line of code is being run:
this.labels.push(snapshot.movement);
I've been playing with this for hours and can't figure out what I'm doing wrong, any help is appreciated.
The issues is that this changes inside callback functions. You can fix this by using arrow functions which will capture the correct this:
this.filteredRecords[0].forEach((snapshot) => {
this.labels.push(snapshot.movement);
//barChartData.push(snapshot.weight);
});
Or by capturing the this in another variable:
let that = this;
this.filteredRecords[0].forEach(function (snapshot) {
that.labels.push(snapshot.movement);
//barChartData.push(snapshot.weight);
});
This might be useful: What is 'this' in TypeScript

What is the correct way to convert an Angular 1 provider to Angular 2

There are lot of documentation and examples on how to convert Angular 1 services and factories to Angular2 but I couldnt find anything on how to convert a ng1 provider to something equivalent in ng2.
Example provider
function AlertService () {
this.toast = false;
this.$get = getService;
this.showAsToast = function(isToast) {
this.toast = isToast;
};
getService.$inject = ['$timeout', '$sce'];
function getService ($timeout, $sce) {
var toast = this.toast,
alertId = 0, // unique id for each alert. Starts from 0.
alerts = []
return {
factory: factory,
add: addAlert
};
function factory(alertOptions) {
var alert = {
type: alertOptions.type,
msg: $sce.trustAsHtml(alertOptions.msg),
id: alertOptions.alertId,
toast: alertOptions.toast
};
alerts.push(alert);
return alert;
}
function addAlert(alertOptions) {
alertOptions.alertId = alertId++;
var alert = this.factory(alertOptions);
return alert;
}
}
}
angular
.module('angularApp', [])
.provider('AlertService', AlertService);
What would be the correct equivalent for this in Angular 2?
Ok so finally we figured it out thanks to https://github.com/jhipster/generator-jhipster/issues/3664#issuecomment-251902173
Here is the Service in NG2
import {Injectable, Sanitizer, SecurityContext} from '#angular/core';
#Injectable()
export class AlertService {
private alertId: number;
private alerts: any[];
constructor(private sanitizer: Sanitizer, private toast: boolean) {
this.alertId = 0; // unique id for each alert. Starts from 0.
this.alerts = [];
}
factory(alertOptions): any {
var alert = {
type: alertOptions.type,
msg: this.sanitizer.sanitize(SecurityContext.HTML, alertOptions.msg),
id: alertOptions.alertId,
toast: alertOptions.toast
};
this.alerts.push(alert);
return alert;
}
addAlert(alertOptions, extAlerts): any {
alertOptions.alertId = this.alertId++;
var alert = this.factory(alertOptions);
return alert;
}
isToast(): boolean {
return this.toast;
}
}
and here is the provider for the service
import { Sanitizer } from '#angular/core';
import { AlertService } from './alert.service';
export function alertServiceProvider(toast?: boolean) {
// set below to true to make alerts look like toast
let isToast = toast ? toast : false;
return {
provide: AlertService,
useFactory: (sanitizer: Sanitizer) => new AlertService(sanitizer, isToast),
deps: [Sanitizer]
}
}
Now you need to call the alertServiceProvider method in the provider declaration of your module.
#NgModule({
imports: [
...
],
declarations: [
...
],
providers: [
...
alertServiceProvider()
],
exports: [
...
]
})
export class SharedCommonModule {}
The code is part of the JHipster project and you can browse actual templates here

Categories

Resources