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);
}
}
Related
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);
}
});
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
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;
Starting out with angular 2 after spending time with angular 1. Not having unit tested this much as it's more of a side project thing, I'm trying at least start out OK... I started with the example from AngularClass if that makes a difference.
Struggling in app.component.ts already, which contains my navigation bits. Relevant bits of the template here:
<nav class="navbar navbar-light bg-faded">
<div class="container">
<div class="nav navbar-nav">
<a class="navbar-brand" [routerLink]=" ['./'] ">Navbar</a>
<loading class="nav-item nav-link pull-xs-right" [visible]="user === null"></loading>
</div>
</div>
</nav>
<div class="container">
<main>
<router-outlet></router-outlet>
</main>
</div>
<footer>
<hr>
<div class="container">
</div>
</footer>
Component itself does not contain much:
import { Component, ViewEncapsulation } from '#angular/core';
import { AuthService } from './_services';
import { User } from './_models';
import { Loading } from './_components';
#Component({
selector: 'app',
encapsulation: ViewEncapsulation.None,
template: require('./app.component.html'),
styles: [
require('./app.style.css')
]
})
export class App {
user: User;
constructor(private auth: AuthService) {
}
ngOnInit() {
this.auth.getUser().subscribe(user => this.user = user);
}
}
All modules, components and routes are bootstrapped through the App module. Can post if required.
The test I'm having to write for it has me hooking up basically everything from the router (so it seems). First, [routerLink] is not a native attribute of 'a'. Ok, I fix it. Then:
Error in ./App class App - inline template:3:6 caused by: No provider for Router!
So, I hook up router, only to find:
Error in ./App class App - inline template:3:6 caused by: No provider for ActivatedRoute!
Which I added, to find out that:
Error in ./App class App - inline template:3:6 caused by: No provider for LocationStrategy!
By now, the test looks like:
import { inject, TestBed, async } from '#angular/core/testing';
import { AuthService } from './_services';
import { Router, RouterModule, ActivatedRoute } from '#angular/router';
import { AppModule } from './app.module';
// Load the implementations that should be tested
import { App } from './app.component';
import { Loading } from './_components';
describe('App', () => {
// provide our implementations or mocks to the dependency injector
beforeEach(() => TestBed.configureTestingModule({
declarations: [App, Loading],
imports: [RouterModule],
providers: [
{
provide: Router,
useClass: class {
navigate = jasmine.createSpy("navigate");
}
}, {
provide: AuthService,
useClass: class {
getAccount = jasmine.createSpy("getAccount");
isLoggedIn = jasmine.createSpy("isLoggedIn");
}
}, {
provide: ActivatedRoute,
useClass: class { }
}
]
}));
it('should exist', async(() => {
TestBed.compileComponents().then(() => {
const fixture = TestBed.createComponent(App);
// Access the dependency injected component instance
const controller = fixture.componentInstance;
expect(!!controller).toBe(true);
});
}));
});
I'm already mocking the inputs, this seems wrong to me. Am I missing something? Is there a smarter way of loading the whole app on a test, instead of bolting in every single dependency, all the time?
For testing, you should use the RouterTestingModule instead of the RouterModule. If you want to add routes you can use withRoutes
imports: [
RouterTestingModule.withRoutes(Routes) // same any normal route config
]
See Also
Angular 2 unit testing components with routerLink
Second half of this post for an idea of mock the ActivatedRoute. Sometimes you don't want the whole routing facility when unit testing. You can just mock the route.
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);'