an error occur in changing the root page - ionic2

i want to set login page as my root page for this app, so i change the code in the app.component.ts like 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 { HomePage } from '../pages/home/home';
import {LoginPage } from '../pages/login/login';
#Component({
templateUrl: 'app.html'
})
export class MyApp {
rootPage:any = LoginPage;
constructor(platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen) {
platform.ready().then(() => {
// Okay, so the platform is ready and our plugins are available.
// Here you can do any higher level native things you might need.
statusBar.styleDefault();
splashScreen.hide();
});
}
}
Error: No component factory found for LoginPage. Did you add it to #NgModule.entryComponents? Error: No component factory found for LoginPage.

The error message here is key, take a look at your app.module.ts and make sure your LoginPage is included in the appropriate sections.
Take a look at the sample here to see where your Page should be referenced.
Pay particular attention to the section: entryComponents

Related

Adding a custom variable to EmberRouter and then using it in another file

I recently tried extended the EmberRouter to include the following piece of information.
router.js
import EmberRouter from '#ember/routing/router';
const Router = EmberRouter.extend({
lastVisitedURL: null,
init() {
this._super(...arguments);
this.on('routeWillChange', () => {
this._super(...arguments);
this.lastVisitedURL = this.currentURL;
});
}
// Router.map code (not important)
})
I would now like to extract lastVisitedURL from a controller file. However, I'm not sure how to do it. Some of the things I've tried include importing the EmberRouter directly (I.E.):
import Router from '../router';
export default Controller.extend({
someFunction() {
console.log(Router.lastVisitedURL); // returns undefined
}
});
I'm not perfectly sure what the problem is with this approach, but it appears to be returning me some sort of other object or function that doesn't truly contain the state of the router.
So the next approach, that seems to be a little more accepted, was to try to use the RouterService object that I believe is meant to provide an API to the EmberRouter.
import Router from '../router';
export default Controller.extend({
router: service(),
someFunction() {
console.log(this.router.lastVisitedURL) // returns undefined
}
});
The problem I encountered with this solution though is that even though the routerService can store the state of the EmberRouter, it doesn't store my specific new variable. So I now need a way to add this specific pice of data to the RouterService as well as the EmberRouter.
I'm not really sure how to do this or if there is a better approach to the problem I'm trying to solve. Any thoughts appreciated!
I'm a little bit confused about your use case to be honest. The current URL is available on the RouterService, which is shipped with Ember by default. You could access it like this:
import Controller from '#ember/controller';
import { inject as service } from '#ember/service';
export default Controller.extend({
router: service(),
someFunction() {
console.log(this.router.currentURL);
}
});
It seems like you are trying to reinvent that feature.
If you want to go with declaring a property on the EmberRouter instance and use it at other places you need to look up the router on the container. It's available as router:main. You can't import it directly as it's neither a service nor a controller. The code would look like:
import Controller from '#ember/controller';
import { getOwner } from '#ember/application';
export default Controller.extend({
someFunction() {
let owner = getOwner(this);
let router = owner.lookup('router:main');
console.log(router.currentURL);
}
});
I would not recommend such a pattern. I don't think it's officially supported. As far as I'm aware router:main is private API. So it might be broken in a minor release.
This could be way better addressed by a service:
// app/services/location-history.js
import Service from '#ember/service';
import { inject as service } from '#ember/service';
import { action } from '#ember/object';
export default Service.extend({
router: service(),
updateRoute: action(function() {
this.visitedURLs = [...this.visitedRoutes, this.router.currentURL];
}),
init() {
this.router.on('routeDidChange', this.updateRoute);
this.set('visitedURLs', []);
},
willDestroy() {
this.router.off('routeDidChange', this.updateRoute);
}
});
If you find that syntax hard to read I recommend switching to native ECMAScript classes, which is the default syntax since Ember Octance:
// app/services/location-history.js
import Service from '#ember/service';
import { inject as service } from '#ember/service';
import { action } from '#ember/object';
export default class LocationHistoryService extends Service {
#service router;
visitedURLs = [];
#action
updateRoute() {
this.visitedURLs = [...this.visitedRoutes, this.router.currentURL];
}
constructor() {
this.router.on('routeDidChange', this.updateRoute);
},
willDestroy() {
this.router.off('routeDidChange', this.updateRoute);
}
});

Can i access ember service variables in my whole application?

I Created an ember service and i initialized its value now i updated that value in a controller. I want this updated variable in my complete application ? What should i supposed to do. Any help will be appreciated. Thanks
Just dummy example
Service
// header.js
import Service from '#ember/service';
export default Service.extend({
currentRoute: null
});
Route
import Route from '#ember/routing/route';
import { inject as service } from '#ember/service';
import { set } from '#ember/object';
export default Route.extend({
header: service(),
beforeModel() {
set(this, 'header.currentRoute', 'login');
}
});
Any controller
import Controller from '#ember/controller';
import { computed, get } from '#ember/object';
import { inject as service } from '#ember/service';
export default Controller.extend({
header: service(),
currentRoute: computed('header.currentRoute', function() {
return get(this, 'header.currentRoute');
})
});
I hope you understood the idea
Please look at this article from the ember guide https://guides.emberjs.com/release/components/triggering-changes-with-actions/#toc_invoking-actions-directly-on-component-collaborators
You did the correct thing, once you already set an updated value of that variable in your service that value will be the same until you instructed your code to change the value of that variable. To answer your question, Can i access ember service variables in my whole application? Yes you can, you can do the following to access the variable into your application.
Assuming that this is your variable and service name
//services/current-session.js
import Service from '#ember/service';
export default Service.extend({
foo: 'foo bar baz',
});
If you want to access it in your controller please do the following
import Controller from '#ember/controller';
import { computed } from '#ember/object';
import { inject as injectService } from '#ember/service';
export default Controller.extend({
currentSession: injectService(),
bar: computed('currentSession.foo', function() {
return this.currentSession.foo;
}),
});
If you want to access it in your component
import Component from '#ember/component';
import { computed } from '#ember/object';
import { inject as injectService } from '#ember/service';
export default Component.extend({
currentSession: injectService(),
bar: computed('currentSession.foo', function() {
return this.currentSession.foo;
}),
});
The approach above is based on ember-cli 3.4 Here's an ember-twiddle you can play around. https://ember-twiddle.com/b92a7d4fa03a1699a5b8a79aca5d2782?openFiles=controllers.application.js%2C
If you want to change the value of your variable in your service through your controller you can do this
import Controller from '#ember/controller';
import { set } from '#ember/object';
import { inject as injectService } from '#ember/service';
export default Controller.extend({
currentSession: injectService(),
actions: {
change() {
this.currentSession.set('foo', 'foo bar baz please');
}
}
});
working twiddle.
Steps:
1) create a service:
app/services/service-name.js
import Service from '#ember/service';
export default Service.extend({
name: "hello",
changeName(name) {
this.set('name', name);
}
});
2) Create an initializer so you need not to inject the service in every route/controller/component.
app/initializers/inject-service-name.js
export function initialize(application) {
application.inject('controller', 'serviceName', 'service:service-name');
// if needed
// application.inject('route', 'serviceName', 'service:service-name');
// application.inject('component', 'serviceName', 'service:service-name');
}
export default {
initialize
};
once initializer is created kindly restart your ember server.
3) Usage:
app/controller/application.js
import Controller from '#ember/controller';
export default Controller.extend({
init() {
this._super(...arguments);
// print initial value
console.log(this.get('serviceName').name); // hello
// change value of name property in service
this.get('serviceName').changeName("hai");
// print changed value
console.log(this.get('serviceName').name); // hai
}
});

Ionic 2 - Runtime Error No provider for NavController

I understand there are similar questions based on this. I believe my scenario is little different. I am trying to check a value in native storage, if it is true. I am trying to navigate to the HomePage. By now after reading couple of articles,I know i wont be able to use the NavController a dependency injector.
I have also tried using #ViewChild , but for that I assume I would need to define a variable in the template that I will use. However I am using an html template. I am new to Ionic 2 and Angular 2, so please go little easy on me :-).
Any other way in which i can accomplish that? Please elaborate a bit more if you guys have a solution to this.
This is my code from app.components.ts (below). Currently I have just commented the navCtrl code.
import { Component,ViewChild } from '#angular/core';
import { Platform } from 'ionic-angular';
import { StatusBar } from '#ionic-native/status-bar';
import { SplashScreen } from '#ionic-native/splash-screen';
import { LoginPage } from '../pages/login/login';
import { NativeStorage } from '#ionic-native/native-storage';
import { NavController } from 'ionic-angular';
import { HomePage } from '../pages/home/home';
#Component({
templateUrl: 'app.html',
})
export class ClassetteApp {
#ViewChild("appNav") nav: NavController;
rootPage:any = LoginPage;
constructor(platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen, private nativeStorage : NativeStorage) {
platform.ready().then(() => platform.ready().then(() => {
this.nativeStorage.getItem("userId").then(function(data){
this.nav.push(HomePage);
},function(error){
this.nav.push(LoginPage);
})
statusBar.styleDefault();
splashScreen.hide();
})
)}
}
here is my app.html.
<ion-nav #appNav [root]="rootPage"></ion-nav>
check here. You can't inject NavController because any components that are navigation controllers are children of the root component so they aren't available to be injected.
You have the wrong id.
#ViewChild('appNav') nav: NavController
It should be the same as the #appNav id you have given in the html.
Lastly you are using a regular function as a callback. The this points to function object instead of the class. So it cannot find nav. Use Arrow function.
this.nativeStorage.getItem("userId").then((data)=>{
this.nav.push(HomePage);
},(error)=>{
this.nav.push(LoginPage);
})
An update to that answer, I found out we can use this below: Make sure you have the latest Ionic version. 3.3.0. Here you don't have to add a variable to your html template.
import { NavController, Platform } from 'ionic-angular';
import { LoginPage } from '../pages/login/login';
#Component({
templateUrl: 'app.html'
})
export class MyApp {
#ViewChild('appNav') nav: NavController;
loginPage: any = LoginPage;
/* your code */
SomeFunction(){
this.nav.push(loginPage);
}
}

Error: Cannot find name ‘MoreInfo’ while trying to push to MoreinfoPage from HomePage

I'm trying to load MoreinfoPage from HomePage upon button click, I get the following error:
Typescript Error Cannot find name 'MoreinfoPage'.
home.html
<button [navPush]="moreinfoPage">More Info</button>
home.ts
import { Component } from '#angular/core';
import { NavController } from 'ionic-angular';
import { MoreinfoPage } from 'pages/moreinfo/moreinfo'
#Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
moreinfoPage: MoreinfoPage;
constructor(public navCtrl: NavController){}
}
I haven't made any changes to the default folder structure. Why can't it find the MoreinfoPage?
Check NavPush.
You need to set page in constructor.
export class HomePage {
moreinfoPage: MoreinfoPage;
constructor(public navCtrl: NavController){
this.moreinfoPage = MoreinfoPage;//here
}
}
Figured it out. The type should be set to any and the page needs to be assigned to the variable.
moreinfoPage: any = MoreinfoPage;

Unit test for a component with dependency which has dependencies, what's wrong?

I have annoying error that probably by my mistake I cannot resolve.
I have on simple component which is actually nothing else than a top-bar element in my web application.
This component as you can see has only one dependency, the UserService and it uses it quite simply:
import { Component, OnInit } from '#angular/core';
import { MdButton } from '#angular2-material/button';
import { MdIcon , MdIconRegistry} from '#angular2-material/icon';
import { UserService } from '../services/index';
import { RouteConfig, ROUTER_DIRECTIVES, Router, ROUTER_PROVIDERS
} from '#angular/router-deprecated';
#Component({
moduleId: module.id,
selector: 'top-bar',
templateUrl: 'top-bar.component.html',
styleUrls: ['top-bar.component.css'],
providers: [MdIconRegistry, UserService, ROUTER_PROVIDERS],
directives: [MdButton, MdIcon, ROUTER_DIRECTIVES]
})
export class TopBarComponent implements OnInit {
constructor(private userService: UserService) {
this.userService = userService;
}
ngOnInit() {
}
/**
* Call UserService and logout() method
*/
logout() {
this.userService.logout();
}
}
As this service has also some dependencies (router etc) I had to provide them at the beforeEachProviders method as you can see:
import {
beforeEach,
beforeEachProviders,
describe,
expect,
it,
inject,
} from '#angular/core/testing';
import { TopBarComponent } from './top-bar.component';
import {
Router, RootRouter, RouteRegistry, ROUTER_PRIMARY_COMPONENT
} from '#angular/router-deprecated';
import { provide } from '#angular/core';
import { SpyLocation } from '#angular/common/testing';
import { UserService } from '../services/index';
describe('Component: TopBar', () => {
beforeEachProviders(() => [
RouteRegistry,
provide(Location, { useClass: SpyLocation }),
provide(ROUTER_PRIMARY_COMPONENT, { useValue: TopBarComponent }),
provide(Router, { useClass: RootRouter }),
UserService,
TopBarComponent
]);
it('should inject the component', inject([TopBarComponent],
(component: TopBarComponent) => {
expect(component).toBeTruthy();
}));
});
When I run the test though I get this error message:
Chrome 51.0.2704 (Mac OS X 10.11.5) Component: TopBar should inject the component FAILED
Error: No provider for Location! (TopBarComponent -> UserService -> Router -> Location)
Error: DI Exception[......]
First of all as you can see the Location provider is provided.
And secondary, why my test requires to provide (or inject) also the dependencies of the used into the tested component service?
For example if from the above test I remove the Router the even that my component doesn't use Router I'll get an error because the used service does. Then shouldn't I received the same error in the component and not only in the test?
UPDATE - CHANGE OF CODE & ERROR MESSAGE
I have manage to stop getting this error by changing my spec doe to this:
import {
beforeEach,
describe,
expect,
it,
} from '#angular/core/testing';
import { TopBarComponent } from './top-bar.component';
import { UserService } from '../services/index';
import {
Router
} from '#angular/router-deprecated';
import { Http } from '#angular/http';
import { AuthHttp } from 'angular2-jwt';
describe('Component: TopBar', () => {
let router: any = Router;
let authHttp: any = AuthHttp;
let http: any = Http;
let component: TopBarComponent;
let service: UserService = new UserService(router, authHttp, http);
beforeEach(() => {
component = new TopBarComponent(service);
});
it('logout function should work ', () => {
let logout = component.logout;
logout();
expect(localStorage.getItem('token')).toBe(null);
});
});
But know I'm getting this error from my component:
TypeError: Cannot read property 'userService' of undefined
The mentioned error is on this function:
logout() {
this.userService.logout();
}
of my component but this is only on the test. In app it works normally. The function cannot reach constructor's parameter for some reason in my test.
kind of stack here...
By your code I understand that you are trying to test the topbar component.
Top bar component has a dependency on UserService.
So to answer your question, Angular does dependency injection when you run your application because all the providers are configured in the module file. But when you try to test the code in spec file you have to configure the testbed with all providers, components in the beforeEach method that are going to be used and angular leaves all the responsibility of resolving the dependency to the user as testbed acts as environment to run your code.
In your code you can do something like this
let service: UserService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [UserService, any other service on which user service is dependent] });
});
Here you can see TestBed.configureTestingModule method creates a dummy module to aid in running ur test case.
My suggestion will be create a mock UserService which doesn't have any other dependencies like the original one and assign it in the provider
Something like this
export MockUserService {
Put all essential methods stub here.
}
let service: UserService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [provide: UserService, useClass: MockUserService] });
});
Then just test the topBar component's usecases.
Try creating the object of service inside beforeEach using TestBed.get(UserService). This code will automatically resolve the dependencies and create that object for use.
Remove '= new UserService(router, authHttp, http);' from 'let service: UserService = new UserService(router, authHttp, http);'