Learning typescript & angular2 for the first time. I'm creating a generic service that just does GET and POST so that I can use it in the entire app. I've based my app on Angular's example from Dynamic Forms
My issue is that my "QuestionService" is using a "ServerService" but it is complaining that this.ServerService.getData is not a function isnt a function.
ServerService
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class ServerService {
private apiUrl = 'app/users.json';
constructor (private http: Http) {}
getData (): Observable<any>[] {
return this.http.get(this.apiUrl)
.map(this.extractData)
.catch(this.handleError);
}
QuestionService
import { ServerService } from './server.service';
#Injectable()
export class QuestionService implements OnInit{
errorMessage: string;
mode = 'Observable';
questions: QuestionBase<any>[];
ServerService = ServerService;
ngOnInit() { this.getQuestions(); }
getQuestions(ServerService: ServerService<any>){
console.log('getQuestions');
console.log(this.ServerService.getData());
this.ServerService.getData()
.subscribe(
questions => this.questions = questions,
error => this.errorMessage = <any>error);
}
Here is the url: https://plnkr.co/edit/InWESfa6PPVKE0rXcSFG?p=preview
What you need to do is let Angular inject the ServerService into the QuestionService, just like are doing with the Http inside the ServerService
#Injectable()
export class QuestionService implements OnInit{
constructor(private serverService: ServerService) {}
ngOnInit() { this.getQuestions(); }
getQuestions(){
this.serverService.getData()
.subscribe(
questions => this.questions = questions,
error => this.errorMessage = <any>error);
}
}
Then you need to add both services to the providers array
#NgModule({
imports: [HttpModule],
providers: [ServerService, QuestionService]
})
export class AppModule {}
Related
I have created a Django and Angular application to upload files. It was working without errors until I integrated a login page. I have not been able to upload files since integration. I get 401 - "Unauthorized" error. What could have possibly gone wrong?
Auth-interceptor:
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest,HttpErrorResponse } from "#angular/common/http";
import { Injectable } from "#angular/core";
import { catchError, Observable, throwError } from "rxjs";
import { LoginService } from "src/services/login.service";
#Injectable()
export class AuthInterceptorService implements HttpInterceptor {
constructor(private authService: LoginService) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (this.authService.isLoggedIn()) {
const token = this.authService.getAuthToken();
console.log("intercept",token)
// If we have a token, we set it to the header
request = request.clone({
setHeaders: {Authorization: `Token ${token}`}
});
}
return next.handle(request)
}
}
fileupload.component.ts:
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup } from '#angular/forms';
import { LoginService } from 'src/services/login.service';
import { FileUploader, FileLikeObject } from 'ng2-file-upload';
import { concat, Observable } from 'rxjs';
import { HttpEvent, HttpEventType } from '#angular/common/http';
#Component({
selector: 'app-fileupload',
templateUrl: './fileupload.component.html',
styleUrls: ['./fileupload.component.scss']
})
export class FileuploadComponent {
DJANGO_SERVER = 'http://127.0.0.1:8081'
public uploader: FileUploader = new FileUploader({});
public hasBaseDropZoneOver: boolean = false;
constructor(private formBuilder: FormBuilder, private uploadService: LoginService) { }
fileOverBase(event): void {
this.hasBaseDropZoneOver = event;
}
getFiles(): FileLikeObject[] {
return this.uploader.queue.map((fileItem) => {
return fileItem.file;
});
}
upload() {
let files = this.getFiles();
console.log(files);
let requests= [];
files.forEach((file) => {
let formData = new FormData();
formData.append('file' , file.rawFile, file.name);
requests.push(this.uploadService.upload(formData));
console.log(requests,file)
});
concat(...requests).subscribe(
(res) => {
console.log(res);
},
}
);
}}
console.log(err);
}
);
}}
service:
public upload(formData) {
let token= localStorage.getItem('token');
return this.http.post<any>(`${this.DJANGO_SERVER}/upload/`, formData).pipe(map((res) => {
console.log(res)
})
)
}
Thank you
I resolved the issue. It was because I was usign interceptor and I was using third party API for authentication. So instead of Django token, the third party APIs token was sent in header of POST request.
How I resolved it?
I used Httpbackend to process POST requests to Django DB so that the request is not intercepted and then I added custom header (with Django token to the reuest). I used the code snippet on this website: https://levelup.gitconnected.com/the-correct-way-to-make-api-requests-in-an-angular-application-22a079fe8413
I have created new Cordova Plugin only on my machine. Then I added it to my project. It is working fine when I call that plugin. Now, I tried to make a structured caller for my plugin. I created a Provider for it, but the problem is I don't know how to call my plugin function from my Controller class. Below is my sample code.
Provider: my-service.ts
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import 'rxjs/add/operator/map';
declare let myPlugin: any;
#Injectable()
export class MyService {
constructor(public http: Http) {
console.log('Hello MyService Provider');
}
public myFunction() {
myPlugin.myPluginFunction(
(data) => {
return data;
},
(err) => {
return err;
});
}
}
Pages: my-page.ts
import { Component } from '#angular/core';
import { NavController, ViewController } from 'ionic-angular';
import { MyService } from '../../providers/my-service';
#Component({
selector: 'page-my-page-ionic',
templateUrl: 'hello-ionic.html'
})
export class MyPage {
constructor(private viewCtrl: ViewController, private myService: MyService) {}
ionViewWillEnter() {
//I tried to call like this
this.myService.myFunction().subscribe(
data => {
alert("success");
},
error => {
alert("error");
});
}
}
It returns me this error - Property 'subscribe' does not exist on type 'void'. I don't know how to call that function, since my provider returns me success or error.
I think since your myFunction() does not return any observable you cannot subscribe to it. It just returns data directly.
You can use it like this in this case:
var data = this.myService.myFunction();
console.log("Data from plugin is :", data);
If you want to use it as an Observable, return a new observable like this:
public myFunction() {
return Observable.create(observer => {
myPlugin.myPluginFunction(
(data) => {
observer.next(data);
},
(err) => {
observer.next(data);
});
},
(err) => {
observer.error(err);
});
}
I'm working with a 3rd party module in my ng2 project. I want to be able to mock this module for testing, but the module itself doesn't get injected into my service, it's just required in.
How do I overwrite this Client so that my test isn't using the actual module?
import {Injectable} from '#angular/core';
var Client = require('ssh2').Client;
#Injectable()
export class SshService {
constructor(){
//Should log "hello world"
Client.myFunc();
}
}
import { TestBed, inject } from '#angular/core/testing';
describe('My Service', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
SshService
]
});
});
it('should work as expected',
inject([SshService], (sshService:SshService) => {
sshService.Client = {
myFunc:function(){
console.log('hello world')
}
}
console.log(sshService.Client)
}));
});
You can't directly mock Client module for the test since it's required in the same file. You could wrap the Client in to a separate Angular service and inject that as a dependency:
import { Injectable } from '#angular/core';
import { TestBed, inject } from '#angular/core/testing';
let Ssh2 = require('ssh2');
#Injectable()
export class Ssh2Client {
public client: any = Ssh2.Client;
}
#Injectable()
export class Ssh2ClientMock {
// Mock your client here
public client: any = {
myFunc: () => {
console.log('hello world')
}
};
}
#Injectable()
export class SshService {
constructor(public client: Ssh2Client) {
client.myFunc();
}
}
describe('My Service', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
SshService,
{ provide: Ssh2Client, useClass: Ssh2ClientMock }
]
});
});
it('should work as expected',
inject([SshService], (sshService: SshService) => {
sshService.client.myFunc() // Should print hello world to console
})
);
});
Maybe wrap the 3rd party module in a angular2 service and inject that service in the SshService.
I am using ng2-file-upload. How do I mock its FileUploader class in unit testing?
import { FileUploader } from 'ng2-file-upload/ng2-file-upload';
#Component({
selector: 'my-component',
template: '...',
providers: [ MyService ]
})
export class MyComponent {
public uploader: FileUploader = new FileUploader({url: '/my-app/api/upload',
authToken: 'token'});
constructor() {
this.uploader.onCompleteItem = (item:any, response: any, headers: any) => {
console.log('how to test here');
}
}
I am having a hard time mocking it in my spec. Please help.
I was going to reply to your comment, but I think the following may help to answer your original question on how to mock the FileUploader class taken from their unit test from the file file-drop.directive.spec.ts:
import { Component } from '#angular/core';
import { inject, ComponentFixture, TestBed } from '#angular/core/testing';
import { FileUploader } from './file-uploader.class';
import { FileUploadModule } from './file-upload.module';
#Component({
selector: 'container',
template: `<input type="file" ng2FileSelect [uploader]="uploader" />`
})
export class ContainerComponent {
public uploader:FileUploader = new FileUploader({url: 'localhost:3000'});
}
describe('Directive: FileSelectDirective', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [FileUploadModule],
declarations: [ContainerComponent],
providers: [ContainerComponent]
});
});
it('should be fine', inject([ContainerComponent], (fixture:ComponentFixture<ContainerComponent>) => {
expect(fixture).not.toBeNull();
}));
});
Where import { FileUploader } from './file-uploader.class'; is how you import the FileUploader into your test, ContainerComponent is imported into the test itself.
Further to this, I have created a dummy file to test with on my component, but I am still writing it!
it('should accept a file for upload', () => {
var modifiedDate = new Date();
var file = new File([3555], 'test-file.jpg', {lastModified : modifiedDate, type: 'image/jpeg'});
FileUploadComponent.upload(file);
});
To test if this works, I have a metadata model that I expect to be populated upon the file being selected. I can therefore make two assertions, that both the upload input box will have a file and that the metadata object will be populated.
In the case of ng2-file-upload, I beleive the file list will be populated allowing you to check that the test file has been imported into that.
Good luck!
As Angular team is constantly upgrading/deprecating stuff in Angular 2 RC versions I encountered this problem.
I have a component that has a Dependency Injection (DI), which is actually a service (UserService in this case). This UserService of course has some DIs of its own. After updating to the latest RC4 of Angular 2 I realised that I cannot create similar tests any more.
So as the docs are not mentioning something relative here's my code (simplified for this question).
My component:
import { Component } from '#angular/core';
import { MdButton } from '#angular2-material/button';
import {
MdIcon,
MdIconRegistry
} from '#angular2-material/icon';
import { UserService } from '../../services/index';
#Component({
moduleId: module.id,
selector: 'logout-button',
templateUrl: 'logout-button.component.html',
styleUrls: ['logout-button.component.css'],
providers: [MdIconRegistry, UserService],
directives: [MdButton, MdIcon]
})
export class LogoutButtonComponent {
constructor(public userService: UserService) {}
/**
* Call UserService and logout() method
*/
logout() {
this.userService.logout();
}
}
Component's DI, UserService whic as you can see has some DIs (Router, AuthHttp & Http):
import { Injectable } from '#angular/core';
import {
Http,
Headers
} from '#angular/http';
import {
AuthHttp,
JwtHelper
} from 'angular2-jwt';
import { Router } from '#angular/router';
import { UMS } from '../common/index';
#Injectable()
export class UserService {
constructor(
private router: Router,
private authHttp: AuthHttp,
private http: Http) {
this.router = router;
this.authHttp = authHttp;
this.http = http;
}
/**
* Logs out user
*/
public logout() {
this.authHttp.get(UMS.url + UMS.apis.logout)
.subscribe(
data => this.logoutSuccess(),
err => this.logoutSuccess()
);
}
}
And here's the test for the component:
import { By } from '#angular/platform-browser';
import { DebugElement } from '#angular/core';
import {
beforeEach,
beforeEachProviders,
describe,
expect,
it,
inject,
fakeAsync,
TestComponentBuilder
} from '#angular/core/testing';
import { AuthHttp } from 'angular2-jwt';
import { Router } from '#angular/router';
import { Http } from '#angular/http';
import { LogoutButtonComponent } from './logout-button.component';
import { UserService } from '../../services/index';
describe('Component: LogoutButtonComponent', () => {
beforeEachProviders(() => [
LogoutButtonComponent,
UserService
]);
it('should inject UserService', inject([LogoutButtonComponent],
(component: LogoutButtonComponent) => {
expect(component).toBeTruthy();
}));
});
Don't worry about the (it) for now.
As you can see I;m adding the related providers on the beforeEachProviders.
In this case I'm getting an error when I run the tests:
Error: No provider for Router! (LogoutButtonComponent -> UserService -> Router)
Which is expected let's say.
So in order to don't get those errors I'm adding the service's DIs in the providers also:
beforeEachProviders(() => [
LogoutButtonComponent,
Router,
AuthHttp,
Http,
UserService
]);
But now I'm, getting this error:
Error: Cannot resolve all parameters for 'Router'(?, ?, ?, ?, ?, ?, ?). Make sure that all the parameters are decorated with Inject or have valid type annotations and that 'Router' is decorated with Injectable.
I'm really trying to figure out what's happening so I found some related answers here but ALL are outdated and covers the old router-deprecated or angular2/router but both are deprecated and are not covering this case.
Would love some help on this and maybe some resources as I cannot find anything related to the latest version of Router: "#angular/router": "3.0.0-beta.2", and RC4.
Thanks
UPDATE!
I manage to bypass the two errors above and now I can access the component. Here's the description code:
describe('Component: LogoutButtonComponent', () => {
let component: LogoutButtonComponent;
let router: any = Router;
let authHttp: any = AuthHttp;
let http: any = Http;
let service: any = new UserService(router, authHttp, http);
beforeEachProviders(() => [
LogoutButtonComponent
]);
beforeEach(() => {
component = new LogoutButtonComponent(service);
});
it('should inject UserService', () => {
expect(component.userService).toBeTruthy();
});
it('should logout user', () => {
localStorage.setItem('token', 'FOO');
component.logout();
expect(localStorage.getItem('token')).toBeUndefined();
});
});
But it seems that even that the DI service is injected and accessible the DIs of the service are not. So now I get this error:
TypeError: this.authHttp.get is not a function
Any ideas?
It looks like you were experiencing a dependencies loop problem, because your UserSerivce also need inject AuthHttp, Http, etc... it really will be disturb once if you need test your component.
My way is just create a mock UserSerivce and return the expect mocked value through UserService.logout() method, because you don't have to know what really happened in UserService, all you need is just a return value:
let MockUserService = {
logout() {
// return some value you need
}
}
Then, in test suite:
import { provide } from '#angular/core'
beforeEachProviders(() => [
provide(UserService, {useClass: MockUserService})
])
... detail test code here
I hope this works for you.
And here is a post that helps me a lot:
https://developers.livechatinc.com/blog/testing-angular-2-apps-dependency-injection-and-components/
With RC4, some workarounds are needed to use http in a test. See also this issue (should be fixed in RC5):
https://github.com/angular/angular/issues/9294
Adding this to my unit-tests.html fixed it for me:
System.import('#angular/platform-browser/src/browser/browser_adapter').then(function(browser_adapter) {
browser_adapter.BrowserDomAdapter.makeCurrent();
})