I can't figure out a way to access a service and properly configure it's dependencies when that service is accessed from Angular's #CanActivate hook.
Before I instantiate a component I need to first check that the user is authenticated. My hook looks like:
import {AuthenticationService} from '../authentication/authentication.service';
.................
#CanActivate((next, prev) => {
var injector = Injector.resolveAndCreate([
AuthenticationService
])
var authService = injector.get(AuthenticationService);
return authService.getUser();
})
export class ShellComponent { }
The error I get is EXCEPTION: No provider for Http! (AuthenticationService -> Http). It should be noted that HTTP_PROVIDERS are injected when the application is bootstrapped.
My authentication service is the following:
import {Injectable} from 'angular2/core';
import {Http} from 'angular2/http';
#Injectable()
export class AuthenticationService {
constructor(private _http:Http) {}
getUser() {
var resolver = function(resolve, reject) {
setTimeout(function doneCounting() {
resolve(true);
}, 5000);
}
return new Promise<Boolean>(resolver);
}
}
Any help or insight would be appreciated.
All you need is to add this dependencies to resolveAndCreate, so your code will will be:
var injector = Injector.resolveAndCreate([HTTP_PROVIDERS, AuthenticationService])
Related
This is a follow-up of my previous post. I've been debugging this issue for quite a while now and even though I haven't fixed it, I made some discoveries so maybe someone will be able to help.
Here's the whole setup:
app-config.json (/src/assets/):
{
"apiUrl": "localhost:8080"
}
app-config.service.ts (/src/app/):
import {Injectable, Injector} from '#angular/core';
import {HttpClient} from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class AppConfigService {
private appConfig: any;
constructor (private injector: Injector) { }
loadAppConfig() {
let http = this.injector.get(HttpClient);
return http.get('/assets/app-config.json')
.toPromise()
.then(data => {
this.appConfig = data;
})
}
get config() {
return this.appConfig;
}
}
app.module.ts (/src/app/):
import {APP_INITIALIZER, NgModule} from '#angular/core';
import {HttpClientModule} from '#angular/common/http';
import {AppConfigService} from './app-config.service';
import {CometdService} from './cometd/cometd.service';
const appInitializerFn = (appConfig: AppConfigService) => {
return () => {
return appConfig.loadAppConfig();
}
};
#NgModule({
...
providers: [HttpClientModule,
AppConfigService,
{
provide: APP_INITIALIZER,
useFactory: appInitializerFn,
multi: true,
deps: [AppConfigService]
}]
})
export class AppModule {
constructor(cometdService: CometdService) {}
}
cometd.service.ts (/src/app/cometd/):
import {Injectable, OnDestroy} from '#angular/core';
import {Store} from '#ngrx/store';
import * as fromRoot from '../reducers';
import {AppConfigService} from '../app-config.service';
export interface CometDExtended extends cometlib.CometD {
websocketEnabled: boolean;
}
#Injectable({
providedIn: 'root'
})
export class CometdService implements OnDestroy {
protected cometd: CometDExtended = new cometlib.CometD() as CometDExtended;
private subscriptions: cometlib.SubscriptionHandle[] = [];
constructor(private environment: AppConfigService, private store: Store<fromRoot.State>) {
let config = environment.config;
let apiUrl = environment.config.apiUrl;
this.cometd.configure('http://localhost:8080/cometd');
this.startConnection();
}
...
}
The issue happens for various services. CometD is only an example.
The data in app-config.service.ts itself is fetched properly, i.e. loadAppConfig() returns { "apiUrl": "localhost:8080" }.
Injected environment (AppConfigService) is defined, i.e. it's of type Object.
environment.config is undefined, so environment.config.apiUrl returns an error: "TypeError: Cannot read properties of undefined (reading 'apiUrl')".
AppConfigService is not needed in providers array because providedIn: 'root' already make it available.
If you provide the service in different ways you may have multiple instances : one will be loaded, others would not.
If it still does not work, put a breakpoint to check if other services are created before the init completion. I recommand to move the calls out of the CometdService constructor, so you can perform the async call in a clean way
Welp, just minutes after posting the question I happened to find a solution. It seems like since loadAppConfig() is asynchronous, the environment.config might've been accessed before the promise was resolved. Changing the constructor to:
this.environment.loadAppConfig().then(() => {
let config = environment.config
...
});
fixed the issue.
Ok, so I am following along the traversy media tutorial on ionic 3, and when i get to the part where you create a provider I get and error that says unreachable code detected in here:
.map((res: Response) => res.json() );
and it also says on typescript
cannot find the name 'map' did you mean 'Map'?
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
import 'rxjs/add/operator/map';
#Injectable()
export class WeatherProvider {
apiKey = "89cca14f4ffcd27d602ad5e587f8e17f";
url;
constructor(public http: HttpClient) {
console.log('Hello WeatherProvider Provider');
this.url = "api.openweathermap.org/data/2.5/weather?q=";
}
getWeather(city, country){
return this.http.get(this.url+city+','+country);
.map((res: Response) => res.json() );
}
}
The return statement in getWeather() is making the .map() unreachable. You should make the return statement the last statement in the function.
I'm working on a NativeScript app by using AngularJS2 & TypeScript. I've created one file config.ts for storing my API URL.
code for config.ts
export class Config {
apiUrl = "https://incandescent-fire-8397.firebaseio.com/";
}
Then from an another file called user.service.ts I'm trying to access apiUrl value. But, don't know how to do it. Tried couple of permutation & combination but till now no luck.
Code for user.service.ts
import {Injectable} from "#angular/core";
import {User} from "./user";
import Config = require("../config");
#Injectable()
export class UserService {
config: any;
constructor() {
this.config = new Config();
}
register(user: User) {
alert("API url going to use is : "+this.config.apiUrl);
}
}
Need some guidance.
Regards
------Issue Fixed-------
Updated the user.service.ts file
import {Injectable} from "#angular/core";
import {User} from "./user";
import {ConfigService} from "../config";
#Injectable()
export class UserService {
apiUrl = "https://incandescent-fire-8397.firebaseio.com/";
register(user: User,config:ConfigService) {
//alert("About to register: " + user.email);
alert("API url going to use is : "+config.apiUrl);
}
}
Then, the module(app.componets.ts) from where I'm accessing the register method updated like this -
import {Component} from "#angular/core";
import {User} from "./shared/user/user";
import {UserService} from "./shared/user/user.service";
import {ConfigService} from "./shared/config";
import {HTTP_PROVIDERS} from "#angular/http";
import firebase = require("nativescript-plugin-firebase");
#Component({
selector: "my-app",
providers: [UserService, HTTP_PROVIDERS,ConfigService],
templateUrl: "pages/login/login.html",
styleUrls: ["pages/login/login-common.css", "pages/login/login.css"]
})
export class AppComponent {
user: User;
isLoggingIn = true;
config:any;
constructor(private _userService: UserService, private _configSerice:ConfigService) {
this.user = new User();
this.config = new ConfigService();
}
submit() {
if (this.isLoggingIn) {
this.login();
} else {
this.signUp();
}
}
login() {
// TODO: Define
console.log('Clicked on Login button');
firebase.init(<any>{
persist: true // Allow disk persistence. Default false.
}).then(
function (instance) {
console.log("firebase.init done");
},
function (error) {
console.log("firebase.init error: " + error);
}
);
}
signUp() {
this._userService.register(this.user,this.config);
}
toggleDisplay() {
this.isLoggingIn = !this.isLoggingIn;
}
}
Don't know how much it's a right way to do. But, it's fixed the problem. Will appriciate a lot if someone know any better way to do this.
I thing you're initiating the Config module instead of the Config class inside it.
You can try adding the 'default' keyword to the "export class Config" line, or try initiating the config instance with "new Config.Config()".
In my Angular2 app I am bootstrapping an auth service LocalStorage that I want shared across my components:
bootstrap(AppComponent, [
ROUTER_PROVIDERS,
LocalStorage
]);
LocalStorage is defined as follows:
import {JwtHelper} from 'angular2-jwt/angular2-jwt';
import { Injectable } from 'angular2/core';
#Injectable()
export class LocalStorage {
key:string = 'jwt';
jwtHelper:JwtHelper = new JwtHelper();
username:string;
constructor() {
let token = localStorage.getItem(this.key);
if (token == null) return;
if (this.jwtHelper.isTokenExpired(token)) {
localStorage.removeItem(this.key);
} else {
this.username = this.jwtHelper.decodeToken(token).username;
}
}
login(jwt:string) {
localStorage.setItem(this.key, jwt);
}
logout() {
localStorage.removeItem(this.key);
}
isLoggedIn():boolean {
return this.username != null;
}
getUsername():string {
return this.username;
}
getToken():string {
return localStorage.getItem(this.key);
}
}
The problem is, however, when I share and update it across components only the component that updates it recognizes the changes. It is injected into components and edited like this:
constructor(private router:Router, private localStorage:LocalStorage) {
...
}
logout(event) {
event.preventDefault();
this.localStorage.logout();
this.router.navigateByUrl(RoutingPaths.home.path);
}
Why is it that it seems multiple instances of this service are being created across components? Thanks.
Edit An example of a component template binding is:
Component:
import {Component} from 'angular2/core';
import {Router, RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router';
import {RoutingPaths} from './routing-paths';
import {LoggedInOutlet} from './logged-in-outlet';
import {LocalStorage} from './local-storage'
#Component({
selector: 'my-app',
templateUrl: 'app/app.template.html',
directives: [LoggedInOutlet, ROUTER_DIRECTIVES]
})
export class AppComponent {
registerName:string;
constructor(private router:Router, private localStorage:LocalStorage) {
this.registerName = RoutingPaths.register.name;
}
logout(event) {
event.preventDefault();
this.localStorage.logout();
this.router.navigateByUrl(RoutingPaths.home.path);
}
}
Template:
<a *ngIf="!localStorage.isLoggedIn()" [routerLink]="[registerName]">Register</a>
Final Edit
Well this is embarrassing, after actually editing the username in the service it now works:
login(jwt:string) {
localStorage.setItem(this.key, jwt);
this.username = this.jwtHelper.decodeToken(jwt).username; // here
}
logout() {
localStorage.removeItem(this.key);
this.username = null; // here
}
Sorry for wasting everyone's time. Thanks again.
It's because you assigned LocalStorage as provider somewhere in your code.
Check if any of your component is containing:
#Component({
providers: [LocalStorage]
})
This gives an Injector instruction to create a new Instance for that component and all children if child one again does not have an provided LocalStorage itself.
The problem is, however, when I share and update it across components only the component that updates it recognizes the changes
This is because of angular 2 component model is a Tree:
So only the component that changes and its sub components are rerendered. For stuff like singletons containing state used across components you need something like redux : https://medium.com/google-developer-experts/angular-2-introduction-to-redux-1cf18af27e6e#.yk11zfcwz
I forgot to actually modify username in the service itself:
login(jwt:string) {
localStorage.setItem(this.key, jwt);
this.username = this.jwtHelper.decodeToken(jwt).username; // here
}
logout() {
localStorage.removeItem(this.key);
this.username = null; // here
}
I have an ionic 2 app that I've been testing using local data. Now, I am attempting to make some Ajax requests to my api to get the data.
In my app.js file, I am defining my component like so:
import {UserProvider} from './providers/user-provider';
...
#App({
templateUrl: 'build/app.html',
providers: [UserProvider]
})
Then, my user-provider.js file is defined like so:
import {Injectable} from 'angular2/core';
import {Http, httpInjectables} from 'angular2/http';
#Injectable()
export class UserProvider {
constructor(http: Http) {
http.get('www.someURL.com').toRx().map(res => res.json())
.subscribe(data => console.log(data));
}
}
Then, lastly, I am initializing my "Sign Up" view with my signup.js file:
import {Page, NavController} from 'ionic/ionic';
import {TabsPage} from '../tabs/tabs';
import {UserProvider} from '../../providers/user-provider';
#Page({
templateUrl: 'build/pages/signup/signup.html'
})
export class SignupPage {
constructor(nav: NavController, userProvider: UserProvider) {
this.userProvider = userProvider;
this.nav = nav;
}
}
My expected result is that on initialization of my signup view, the UserProvider will be injected. As such, it's constructor will run, which will fire off the http.get function within the UserProvider's constructor. I should then see a network call in my browsers network tab.
However, I'm getting this error:
EXCEPTION: Error during instantiation of UserProvider! (SignupPage -> UserProvider)
app.bundle.js:33693 TypeError: http.get(...).toRx is not a function
at new UserProvider (app.bundle.js:60706)
Why is http.get.toRx() causing an error? I initially tried this with promises like so:
http.get('www.someURL.com').then(() => {
console.log('test');
});
but that throws a similar error.
In angular2-beta0 http.get() already returns an Observable, you don't need to call its former .toRx() method, you can just directly call .map() and such.