Angular 2: BehaviorSubject/Subject does not work outside constructor - ionic2

Hi I need pass the data from one component to another, for this I am using the class BehavorSubject(I tried too with Subject class, but doesnt works for me). This is my code:
Home page has a filter, and when is selected a filter, it called the service and it service should change a variable of homePage
HomePage.ts
#Component({
providers: [PatientService],
})
export class HomePage {
subscription: Subscription;
constructor( public auth: AuthService,
public patientService: PatientService) {
this.subscription = this.patientService.nameGiven.subscribe(
nameGiven => {
this.patientsByElement = nameGiven.toString();
});
------ more code---
}
Filtro.ts
export class FiltroPage {
showFilter(filter : FiltroT): void{
... code ...
clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
this.PatientService.getPatientsByTags(this.token,this.filterSelected);
} , 1000);
}
}
patient-service.ts
import { Subject } from 'rxjs/Subject';
import { Observable ,BehaviorSubject } from 'rxjs/Rx';
#Injectable()
export class PatientService {
nameSource = new BehaviorSubject("asd");
nameGiven = this.nameSource.asObservable();
this.nameSource.next('hi!!!'); //**it works but only in the constructor**
this.nameGiven.subscribe(()=>{
console.log("it changed");
});
getPatientsByTags(token: String, tags: Array<string>){
return new Promise(resolve => {
this.http.get(ConnectionParams.DevEnv + ProceduresNames.TagsByPatient + ProceduresNames.TagIdEtiqueta + tags, options)
.map(res => res.json())
.subscribe(data => {
if(data.data.poAnicuRegistros){
console.log("here")
this.nameSource.next('hi TON :/'); // <-- ***here is the problem. It doesnt work***
}
else
console.log("XX");
resolve( this.data);
});
});
}
}

Finally i didn't use the BehaviorSubject/Subject, i pass the data from the filter to Homepage of this way :
HomePage.ts
public popoverCtrl: PopoverController
//code ...
showFilter(myEvent) {
let popover = this.popoverCtrl.create(FiltroPage, {
showConfirm: (x) => {
//do something with the data received from the filter
}
});
popover.present({
ev: myEvent
});
}
Filter.ts
//code...
params: NavParams;
showConfirm() {// function that return the data to homePage
this.params.get('showConfirm')(this.patientsBytag);
}

Related

Ionic 2: Error testing with Karma/Jasmine/TestBed

I'm new with Ionic2 and I was following this tutorial and a simple test like
describe('Dummy test', () => {
it('should do nothing', () => {
expect(true).toBeTruthy();
expect(1 + 1).toBe(2);
});
});
works fine, but for some reason I keep getting this error when I try to follow the rest of the tutorial.
Component: Root Component
✖ initialises with a root page of LoginPage
Firefox 45.0.0 (Linux 0.0.0)
TypeError: win is undefined in src/test.ts (line 937)
My src/test.ts is the same as the tutorial and it doesn't have any win in it. My app.spec.ts is this
import { TestBed, ComponentFixture, async } from '#angular/core/testing';
import { IonicModule } from 'ionic-angular';
import { StatusBar } from '#ionic-native/status-bar';
import { SplashScreen } from '#ionic-native/splash-screen';
import { UserData } from '../providers/user-data';
import { LoginPage } from '../pages/login/login';
import { Platform } from 'ionic-angular';
import { MyApp } from './app.component';
import { LoginPage } from '../pages/login/login';
let comp: MyApp;
let fixture: ComponentFixture<MyApp>;
describe('Component: Root Component', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [MyApp],
providers: [
StatusBar,
SplashScreen,
UserData,
Platform
],
imports: [
IonicModule.forRoot(MyApp)
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MyApp);
comp = fixture.componentInstance;
});
afterEach(() => {
fixture.destroy();
comp = null;
});
it('initialises with a root page of LoginPage', () => {
expect(comp['rootPage']).toBe(LoginPage);
});
});
And my app.component.ts is this
import { Component } from '#angular/core';
import { Platform } from 'ionic-angular';
import { StatusBar } from '#ionic-native/status-bar';
import { SplashScreen } from '#ionic-native/splash-screen';
import { MenuSidePage } from '../pages/menu-side/menu-side';
import { LoginPage } from '../pages/login/login';
import { UserData } from '../providers/user-data';
#Component({
template: `<ion-nav #nav [root]="rootPage"></ion-nav>`
})
export class MyApp {
rootPage: any;
constructor(
public platform: Platform,
public statusBar: StatusBar,
public splashScreen: SplashScreen,
private userData: UserData,
) {
platform
.ready()
.then(() => {
//First - check if user is logged
if(this.userData.currentUser) {
this.rootPage = MenuSidePage;
} else {
this.rootPage = LoginPage;
}
statusBar.styleDefault();
splashScreen.hide();
});
}
}
I don't have yet the solution, but you shouldn't use compileComponents() 'cause you are using a template and not a templateUrl like said in this tutorial :
"We need to use compileComponents when we need to asynchronously compile a component, such as one that has an external template (one that is loaded through templateUrl and isn’t inlined with template). This is why the beforeEach block that this code runs in uses an async parameter – it sets up an asynchronous test zone for the compileComponents to run inside."
Hope it's a kind of helping :)
The win() function come from the Plaftorm, you have to mock it as follow :
export class PlatformMock {
public ready(): Promise<string> {
return new Promise((resolve) => {
resolve('READY');
});
}
public getQueryParam() {
return true;
}
public registerBackButtonAction(fn: Function, priority?: number): Function {
return (() => true);
}
public hasFocus(ele: HTMLElement): boolean {
return true;
}
public doc(): HTMLDocument {
return document;
}
public is(): boolean {
return true;
}
public getElementComputedStyle(container: any): any {
return {
paddingLeft: '10',
paddingTop: '10',
paddingRight: '10',
paddingBottom: '10',
};
}
public onResize(callback: any) {
return callback;
}
public registerListener(ele: any, eventName: string, callback: any): Function {
return (() => true);
}
public win(): Window {
return window;
}
public raf(callback: any): number {
return 1;
}
public timeout(callback: any, timer: number): any {
return setTimeout(callback, timer);
}
public cancelTimeout(id: any) {
// do nothing
}
public getActiveElement(): any {
return document['activeElement'];
}
}
Here is the link to see a project for real integration of this mock class.
Hope it helps :)

How to pass Unit test after implementing ngx-bootstrap/datepicker?

I created independent component with ngx-bootstrap/datepicker on angular-cli project.
Everything works fine but Unit test is failing.
But I am new on Unit Testing, and I tried to test but it saying fails.
Here is Travic-CI build log.
https://travis-ci.org/webcat12345/webcat-black-page/builds/221961698
Here is my project version and code.
#angular/cli: 1.0.0
node: 7.6.0
os: linux x64
#angular/cli: 1.0.0
#angular/common: 4.0.2
#angular/compiler: 4.0.2
#angular/compiler-cli: 4.0.2
#angular/core: 4.0.2
#angular/forms: 4.0.2
#angular/http: 4.0.2
#angular/platform-browser: 4.0.2
#angular/platform-browser-dynamic: 4.0.2
#angular/router: 4.0.2
Template
<datepicker class="well well-sm main-calendar" [(ngModel)]="dt" [minDate]="minDate" [showWeeks]="false" [dateDisabled]="dateDisabled"></datepicker>
Component (sorry for posting full code. it is just sample code from ngx-bootstrap demo)
import { Component, OnInit } from '#angular/core';
import * as moment from 'moment';
#Component({
selector: 'app-sidebar-datepicker',
templateUrl: './sidebar-datepicker.component.html',
styleUrls: ['./sidebar-datepicker.component.scss']
})
export class SidebarDatepickerComponent implements OnInit {
public dt: Date = new Date();
public minDate: Date = void 0;
public events: any[];
public tomorrow: Date;
public afterTomorrow: Date;
public dateDisabled: {date: Date, mode: string}[];
public formats: string[] = ['DD-MM-YYYY', 'YYYY/MM/DD', 'DD.MM.YYYY',
'shortDate'];
public format: string = this.formats[0];
public dateOptions: any = {
formatYear: 'YY',
startingDay: 1
};
private opened: boolean = false;
constructor() {
(this.tomorrow = new Date()).setDate(this.tomorrow.getDate() + 1);
(this.afterTomorrow = new Date()).setDate(this.tomorrow.getDate() + 2);
(this.minDate = new Date()).setDate(this.minDate.getDate() - 1000);
(this.dateDisabled = []);
this.events = [
{date: this.tomorrow, status: 'full'},
{date: this.afterTomorrow, status: 'partially'}
];
}
ngOnInit() {
}
public getDate(): number {
return this.dt && this.dt.getTime() || new Date().getTime();
}
public today(): void {
this.dt = new Date();
}
public d20090824(): void {
this.dt = moment('2009-08-24', 'YYYY-MM-DD')
.toDate();
}
public disableTomorrow(): void {
this.dateDisabled = [{date: this.tomorrow, mode: 'day'}];
}
// todo: implement custom class cases
public getDayClass(date: any, mode: string): string {
if (mode === 'day') {
let dayToCheck = new Date(date).setHours(0, 0, 0, 0);
for (let event of this.events) {
let currentDay = new Date(event.date).setHours(0, 0, 0, 0);
if (dayToCheck === currentDay) {
return event.status;
}
}
}
return '';
}
public disabled(date: Date, mode: string): boolean {
return ( mode === 'day' && ( date.getDay() === 0 || date.getDay() === 6 ) );
}
public open(): void {
this.opened = !this.opened;
}
public clear(): void {
this.dt = void 0;
this.dateDisabled = undefined;
}
public toggleMin(): void {
this.dt = new Date(this.minDate.valueOf());
}
}
Test Code
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { CUSTOM_ELEMENTS_SCHEMA } from '#angular/core';
import { DatepickerModule } from 'ngx-bootstrap/datepicker';
import { SidebarDatepickerComponent } from './sidebar-datepicker.component';
describe('SidebarDatepickerComponent', () => {
let component: SidebarDatepickerComponent;
let fixture: ComponentFixture<SidebarDatepickerComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SidebarDatepickerComponent ],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
imports: [DatepickerModule.forRoot()]
})
.compileComponents();
}));
beforeEach(async() => {
fixture = TestBed.createComponent(SidebarDatepickerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
Please help me to figure out this issue.
Thank you!
1) Use NO_ERRORS_SCHEMA instead of CUSTOM_ELEMENTS_SCHEMA because:
CUSTOM_ELEMENTS_SCHEMA will allow:
any non-Angular elements with a - in their name,
any properties on elements with a - in their name which is the common rule for custom
but your component without -(datepicker)
NO_ERRORS_SCHEMA will allow any property on any element
TestBed.configureTestingModule({
declarations: [ SidebarDatepickerComponent ],
schemas: [NO_ERRORS_SCHEMA],
imports: [DatepickerModule.forRoot()]
})
2) Another option is importing FormsModule
TestBed.configureTestingModule({
declarations: [ SidebarDatepickerComponent ],
imports: [DatepickerModule.forRoot(), FormsModule]
})

Ionic 2: How to call Provider function in Controller class

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);
});
}

Mocking RouterStateSnapshot in Jasmine testing

Although I have been writing Angular 2 for a while now, I am only just writing my first Jasmine tests and have run into a little difficulty. I am trying to test that the CanActivate method of service implementing CanActivate is behaving itself, and is returning true or false as expected.
My method looks like this:
canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable<boolean> {
return this.store$
.map( ( store: StoreState ) => store.currentUser )
.first()
.map( ( user ) => {
if ( user.isAuthenticated ) {
return true;
}
// TODO: This needs refactoring. Need to provide RouterStateSnapshot in test,
// rather than ignoring it!
this.redirectUrl = state ? state.url : '';
this.injector.get( Router ).navigate( ['/login'] );
return false;
} );
}
An extract of my test looks like this:
service = TestBed.get( AuthGuardService );
it( 'should prevent navigation', () => {
service.canActivate(null, null).subscribe((res) => expect( res ).toBeTruthy() );
} );
How do I mock/stub/whatever the second parameter of my call to service.canActivate, rather than simply passing in null?
describe('AuthGuard', () => {
let mockSnapshot: RouterStateSnapshot;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
// so we can get the Router injected
RouterTestingModule,
// other imports as needed
],
// usual config here
});
// create a jasmine spy object, of the required type
// toString is because we have to mock at least one method
mockSnapshot = createSpyObj<RouterStateSnapshot>('RouterStateSnapshot', ['toString']);
});
it('should prevent non-authenticated access',
async(inject([AuthGuard, AuthService, Router], (guard: AuthGuard, auth: AuthService, router: Router) => {
// ensure we're logged out
auth.logout();
// set the url on our mock snapshot
mockSnapshot.url = '/protected';
// so we can spy on what's been called on the router object navigate method
spyOn(router, 'navigate');
expect(guard.canActivate(null, mockSnapshot)).toBeFalsy();
// check that our guard re-directed the user to another url
expect(router.navigate).toHaveBeenCalled();
})));
});
})
Here is my solution which I used for unit testing of Custom Router State Serializer
custom-serializer.ts
import { RouterStateSerializer } from '#ngrx/router-store';
import { RouterStateSnapshot, Params } from '#angular/router';
/**
* The RouterStateSerializer takes the current RouterStateSnapshot
* and returns any pertinent information needed. The snapshot contains
* all information about the state of the router at the given point in time.
* The entire snapshot is complex and not always needed. In this case, you only
* need the URL and query parameters from the snapshot in the store. Other items could be
* returned such as route parameters and static route data.
*/
export interface RouterStateUrl {
url: string;
params: Params;
queryParams: Params;
}
export class CustomRouterStateSerializer
implements RouterStateSerializer<RouterStateUrl> {
serialize(routerState: RouterStateSnapshot): RouterStateUrl {
let route = routerState.root;
while (route.firstChild) {
route = route.firstChild;
}
const { url, root: { queryParams } } = routerState;
const { params } = route;
// Only return an object including the URL, params and query params
// instead of the entire snapshot
return { url, params, queryParams };
}
}
custom-serializer.spec.ts
import { CustomRouterStateSerializer } from './utils';
import { RouterStateSnapshot } from '#angular/router';
describe('Utils CustomRouterStateSerializer', () => {
let mockSnapshot: RouterStateSnapshot;
let serializer: CustomRouterStateSerializer;
let mockSnapshotProxy;
beforeEach(() => {
mockSnapshot = jasmine.createSpyObj<RouterStateSnapshot>('RouterStateSnapshot', ['toString']);
serializer = new CustomRouterStateSerializer();
});
it('should serialize RouterStateSnapshot to subset of params', () => {
mockSnapshotProxy = new Proxy(mockSnapshot, {
get(target, prop) {
if (prop === 'root') {
return {
params: {
id: 100
},
queryParams: {
name: 'John'
}
};
} else if (prop === 'url') {
return '/orders';
}
},
});
const result = serializer.serialize(mockSnapshotProxy);
expect(result.url).toBe('/orders');
expect(result.params.id).toBe(100);
expect(result.queryParams.name).toBe('John');
});
});
I used jasmine.createSpyObj to create object with proper type and Proxy to pass in required properties

Angular 2 RC how to unit test an observable

I am writing some tests for an angular 2 RC application and I'm having some issues with the testing of observables. I mocked up the method setting it's type as observable but when the unit being tested tries to subscribe to the mocked observable I get an error 'Cannot read property 'subscribe' of undefined'
I'm testing my DashboardComponent, which injects a model3DService and calls model3DService.get3DModels() which is an observable that does an http request and returns an array of 3D model objects.
Here's some sample code:
Dashboard Component
import { Model3DService } from '../../services/model3D/model3D.service';
import { ProjectService } from '../../services/project/project.service';
#Component({
selector: 'cmg-dashboard',
styles: [require('./css/dashboard.scss')],
template: require('./dashboard.html')
})
export class DashboardComponent implements OnInit {
constructor(
private projectService: ProjectService,
private model3DService: Model3DService
) { }
ngOnInit (): void {
this.model3DService.get3DModels().subscribe((res: any[]) => {
this.findProjects(res);
this.models = res;
this.projectService.isProjectSelected = true;
this.createProject(res[0]);
});
}
}
Model3DService
#Injectable()
export class Model3DService {
private models: any[] = [];
public get3DModels (): Observable<any> {
return this.http.get('../../../json/3DModel.json')
.map(( res: Response ) => {
this.models = res.json();
return this.models;
});
}
}
Okay now that we have the under test heres the test I'm writing.
Dashboard Component Spec
class MockModel3DService {
public get3DModels(): Observable<any> {
return;
}
}
describe('Dashboard Component', () => {
beforeEachProviders(() => {
return [
DashboardComponent,
provide(ProjectService, {
useClass: MockProjectService
}),
provide(Model3DService, {
useClass: MockModel3DService
})
];
});
describe('ngOnInit', () => {
it('should call model3DService.get3DModels on init', (inject([DashboardComponent], (dashboardComponent: DashboardComponent, model3DService: MockModel3DService) => {
dashboardComponent.ngOnInit();
expect(model3DService.get3DModels).toHaveBeenCalled();
})));
});
});
The concept is similar to testing AngularJS $q promise. Stubbed method returns an observable mock. The method can return a subject instead which inherits Observable but also has properties of both observables and observers.
A fresh subject can be provided with mocked value in-place, a mocked promise would be required to be defined beforehand (subjects share this property with deferreds, see the relevant question).
RxJS 4 subjects have hasObservers method which obviates subscribe spy. RxJS 5 subjects miss the method, yet they expose observers property.
Most likely it should be something like that
let subject: Subject;
class MockModel3DService {
public get3DModels(): Observable<any> {
return subject;
}
}
...
// beforeEach(...)
subject = new Subject;
...
// it(...)
const models = ['mocked'];
dashboardComponent.ngOnInit();
expect(subject.observers.length).toBe(1);
subject.next(models);
expect(model3DService.get3DModels).toHaveBeenCalled();
expect(dashboardComponent.models).toBe(models);
...
Your MockModel3DServic.get3DModels does not return a observable.
import { of } from 'rxjs';
class MockModel3DService {
public get3DModels(): Observable<any> {
return of(yourResponse)
}
}