This question already has answers here:
Angular2 Beta dependency injection
(3 answers)
Closed 7 years ago.
I am using angular2 Beta. and getting error when using the #Inject annotation to DI my one service to another, not able to figure out where I am wrong. Everything seem to be as per Angular2 documentation.
I am using a cloud based data-services - CloudDB - for my application's data needs.
CloudDB gives me a javascript based client library that I can include in my js app and use to do CRUD operations in my cloudDB database or call other custom API I have stored in my CloudDB account, like UserAuth API (API to authenticate user's credentials).
Before using cloudDB js client lib API , I need to supply my cloudDB account's URL and authKey by calling CloudDB js object's getClient method.
In my angualar2 app, I created a injectable service class - CloudDBProvider - the would store my CloudDB account URL and authKey and call CloudDB.getClient to set the provider's js client object for my CloudDB account.
import {Injectable} from 'angular2/angular2';
///<reference path="../typeDefs/CloudDB.d.ts" /> //typedef of CloudDB js library
#Injectable()
export class CloudDBProvider {
private cloudDBClient: CloudDB.JSClient;
public get cloudDBClient(): CloudDB.JSClient {
return this.cloudDBClient;
}
constructor() {
this.cloudDBClient = new CloudDB.getClient(
"https://myaccount.CloudDB.com/",
"AcfdsfmyDdCMHeadfsdsdfHdsf68" // account authKey
);
}
}
Now, I want to create a UserUtils service in this angular2 app, to which I want to inject above class to get cloudDBClient object. I coded UserUtils service class like below, as learnt from your tutorial
import {Injectable, Inject} from 'angular2/angular2';
import {CloudDBProvider} from './CloudDBProvider';
#Injectable()
export class UserUtils {
private _userDetails: Object = {};
private _cloudDBProvider: CloudDBProvider;
private _cloudDBClient: Microsoft.WindowsAzure.MobileServiceClient;;
constructor( #Inject(CloudDBProvider) cloudDBPrvdr: CloudDBProvider) {
this._cloudDBProvider = cloudDBPrvdr;
this._cloudDBClient = this._cloudDBProvider.cloudDBClient; //the public getter property in the class CloudDBProvider
}
public authenicateUser(p_strUserName: string, p_strUserPassword: string) {
var p: Promise<any> = new Promise(
(resolve: (result: any) => void, reject: (err: any) => void) =>
this._cloudDBClient.userlogin(p_strUserName, p_strUserPassword).done( //using API 'userlogin' of cloudDB to authenticate user against my cloudDB's users table.
(loginResult) => {
alert("from Userutils - You are now logged in as: " + loginResult.user.basicProfile.firstName);
resolve(loginResult);
},
(loginErr: any) => {
alert("Error: " + loginErr.request.responseText);
reject(loginErr);
}
)
);
return p;
}
}
then I am trying to use UserUtils in my LoginPage component like below:
import {Component} from 'angular2/core';
import {WelcomePage} from "../views/welcome/welcome";
import {UserUtils} from "../services/UserUtils";
#Component({
templateUrl: 'app/login/login.html',
providers: [UserUtils]
})
export class LoginPage {
private _userUtils: UserUtils;
constructor( userUtils: UserUtils) {
this._userUtils = userUtils;
}
public loginButtonClicked(event, userName, password) { //called when Login Button is clicked by user
//...
//... to-do field value verification
//...
this._userUtils.authenicateUser(userName, password).then(
(result) => {
//navigate to WelcomePage
},
(err) => { alert(err); }
);
}
}
the component LoginPage doesn't work when I use UserUtils. The browser console throws error - No provider for CloudDBProvider! (LoginPage -> UserUtils -> CloudDBProvider)
Note that, if I move the 'authenicateUser' method from UserUtils to CloudDBProvider directly and use CloudDBProvider in LoginPage component for user authentication, then everything works just fine, user gets authenticated and navigated to welcome page after login. Also, no error is thrown and app working if I remove #Inject(CloudDBProvider) cloudDBPrvdr from UserUtils's constructor obviously I cannot use CloudDBProvider then in UserUtils, but point is app doesn't throw any error, which means something is wrong with #Inject.
any clue where I am going wrong?
Upto my Understanding your mistake is in the imports change the import of Injectablewith this
import {Component, Inject, Injectable} from 'angular2/core';
also accoriding to me when we have used #injectable annotation no need to use #inject in the constructor you simply put your service with the public identifier and can use that service into any another method of the same class.
Perhaps you could add the CloudDBProvider provider in the list of providers of your component:
#Component({
templateUrl: 'app/login/login.html',
providers: [UserUtils, CloudDBProvider]
})
export class LoginPage {
(...)
}
Or at application level within the second parameter of the bootstrap function:
bootstrap(MainComponent, [CloudDBProvider]);
This answer could give you some additional hints: Angular2 Beta dependency injection.
Hope it helps you,
Thierry
Related
I'm trying to practice using ActionCable in Angular. I created a quick Rails application that I put up on Heroku and then created an Angular application with the actioncable npm module as a dependency.
I configured my Rails appplication to allow http://localhost:4200 as an origin while I play around with my Angular app in development. I also didn't make this an API application because I wanted to have a working UI from the get-go. So I can log into the Rails application, send a message, and my separate Angular application is subscribed to that channel as well. I'm successfully receiving those notifications/messages.
Now I'd like to render something in Angular based on that message. I think I'm missing something pretty silly here, but I cannot refer to methods in the component that instantiates the subscription to that channel in the receive callback of the subscription.
import {
ComponentFactoryResolver,
ComponentRef,
OnInit,
ViewContainerRef,
Component,
ViewChild,
Output
} from '#angular/core';
import * as ActionCable from 'actioncable';
import { MessageComponent } from 'app/message/message.component';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
messageRef: ComponentRef<MessageComponent>;
#ViewChild('message', { read: ViewContainerRef }) message: ViewContainerRef;
title = 'app works!';
private cable: ActionCable.Cable;
private subscription: ActionCable.Channel;
constructor(
private componentFactoryResolver: ComponentFactoryResolver,
private viewContainerRef: ViewContainerRef) {
}
public ngOnInit(): void {
this.cable = ActionCable.createConsumer('wss://<my-heroku-app>.herokuapp.com/cable');
this.subscription = this.cable.subscriptions.create(
'RoomChannel',
{
connected: this.connected,
disconnected: this.disconnected,
received: this.received,
});
}
private showMessage(messageString) {
if (!this.messageRef) {
const messageComponent = this.componentFactoryResolver.resolveComponentFactory(MessageComponent);
this.messageRef = this.message.createComponent(messageComponent);
}
this.messageRef.instance.message = messageString;
this.messageRef.changeDetectorRef.detectChanges();
}
private connected() {
console.log('connected!');
}
private disconnected() {
console.log('disconnected!');
}
private received(data: any) {
console.log('received');
// What do I put here? `this` is of type Subscription,
// and thus, I can't call `this.showMessage(data.message)`
}
}
I want to use some sort of predicate or inject something into that context (sorry if I'm not using the right terminology), but I am just not sure how to do this. I plan on cleaning things up instead of having this all in the AppComponent class, but for now I'm just trying to learn.
Any ideas? Thanks!
The answer seems to be this:
this.cable = ActionCable.createConsumer(this.url)
this.subscription = this.cable.subscriptions.create(
'RoomChannel',
{
connected: this.connected,
disconnected: this.disconnected,
// The key here, apparently, is to use a fat-arrow
// function so that the `this` I care about is
// lexicographically scoped. I still need to better
// understand what exactly that means, but I have a
// general idea.
received: (data) => this.received(data)
});
I was using JQuery for http requests to .NET controllers (I'm using .NET MVC 4.5.2), but am now starting to use Angular 2, so I want to handle those JQuery AJAX calls with Angular 2 instead. Here is the JQuery I was using before, which worked just like I wanted:
$.get('/Plan/Variety/ListVarietiesInMenuForSelling')
.done(function(data){
console.log(data)
});
How do I set up my Angular 2 service to accomplish the same thing? I've tried the code below:
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import 'rxjs/add/operator/toPromise';
#Injectable()
export class SellingMenuVarietiesService {
private url = '/Plan/Variety/ListVarietiesInMenuForSelling'; // URL to web api
constructor(private http: Http) { }
getVarieties() {
return this.http.get(this.url);
}
}
Unfortunately this doesn't work. Instead, I get this error in the console:
EXCEPTION: Uncaught (in promise): TypeError: Cannot read property '0' of undefined core.umd.js:3462
The only examples I've been able to find handle JSON data and then use Angular to parse the data and format it into HTML. My controller already returns the HTML I need, so I don't need Angular to parse JSON. How do I get the http request to work like it did when I was using JQuery?
Just to be clear, for testing purposes I changed return this.http.get(this.url); to return '<h1>test data</h1>'; and was able to get that to display correctly, so I know that the only issue is with the http request.
EDIT:
Here is the code where I call getVarieties():
export class SellingMenuVarietiesComponent implements OnInit {
varietyListSelling: any;
constructor(
private router: Router,
private sellingMenuVarietiesService: SellingMenuVarietiesService) { }
ngOnInit(): void {
this.varietyListSelling = this.sellingMenuVarietiesService.getVarieties();
console.log('initializing SellingMenuVarietiesComponent');
}
}
And here is how I bind it to the HTML:
<div [innerHtml]="varietyListSelling"></div>
I used that instead of {{varietyListSelling}} because I need the string to be converted to HTML.
UPDATE
I upgraded TypeScript and removed my InMemoryWebApiModule and InMemoryDataService imports from app.module.ts, which is resulting in a new error. The error now says: EXCEPTION: Unexpected token < in JSON at position 4
Is this because we are turning my HTML data into JSON? How can I just return the HTML like my JQuery was doing?
You could do something like this:
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import 'rxjs/add/operator/toPromise';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class SellingMenuVarietiesService {
private url = '/Plan/Variety/ListVarietiesInMenuForSelling'; // URL to web api
constructor(private http: Http) { }
public getVarieties(): Observable<any> {
return this.http.get(this.url).map(response=>{return response});
}
}
then to consume the service:
this.sellingMenuVarietiesService.getVarieties().subscribe(res => {
console.log(res);
});
Update:
The html you are trying to render is not rendering because the request has not completed when the template is being rendered. A possible solution for this is to add an ngIf to the div tag.
div *ngIf="varietyListSelling" [innerHtml]="varietyListSelling"></div>
You must subscribe to really send the request and read the response.
getVarieties() {
return this.http.get(this.url).map((response: Response) => {
return response.json();
},(error) => {
console.log(error);
//your want to implement your own error handling here.
});
}
And your component would look like
ngOnInit(): void {
this.sellingMenuVarietiesService.getVarieties().subscribe((res) => {
this.varietyListSelling = res;
});
console.log('initializing SellingMenuVarietiesComponent');
}
"Cannot find name 'Response'" and "Property 'map' does not exist on type 'Observable' I have same issue but it's not a problem , It show error .map but it's work.
I want to make navigation from child components that render inside router-outlet.
My parent component have a router config and I want to navigate manually on some event. But I don't know how I can pass from child to parent some data (for navigation) without output. Because this construction is non working
<router-outlet (navigateTo)="navigateToMessagePart($event)"></router-outlet>
How I can do it in right way? Maybe navigate it from child? But how I can get parent methods from child.
Many thanks for any help!
<router-outlet></router-outlet> can't be used to emit an event from the child component. One way to communicate between two components is to use a common service.
Create a service
shared-service.ts
import { Observable } from "rxjs/Observable";
import { Injectable } from "#angular/core";
import { Subject } from "rxjs/Subject";
#Injectable()
export class SharedService {
// Observable string sources
private emitChangeSource = new Subject<any>();
// Observable string streams
changeEmitted$ = this.emitChangeSource.asObservable();
// Service message commands
emitChange(change: any) {
this.emitChangeSource.next(change);
}
}
Now inject the instance of the above service in the constructor of both the parent and child component.
The child component will be emitting a change every time the onClick() method is called
child.component.ts
import { Component } from "#angular/core";
#Component({
templateUrl: "child.html",
styleUrls: ["child.scss"]
})
export class ChildComponent {
constructor(private _sharedService: SharedService) {}
onClick() {
this._sharedService.emitChange("Data from child");
}
}
The parent component shall receive that change. To do so,capture the subscription inside the parent's constructor.
parent.component.ts
import { Component } from "#angular/core";
#Component({
templateUrl: "parent.html",
styleUrls: ["parent.scss"]
})
export class ParentComponent {
constructor(private _sharedService: SharedService) {
_sharedService.changeEmitted$.subscribe(text => {
console.log(text);
});
}
}
<router-outlet></router-outlet> is just a placeholder for adding routed components. There is no support for any kind of binding.
You can create a custom <router-outlet> that allows you to do that or more common, use a shared service to communicate between parent component and routed component.
For more details see https://angular.io/docs/ts/latest/cookbook/component-communication.html
update
There is now an event that allows to get the added component
<router-outlet (activate)="componentAdded($event)" (deactivate)="componentRemoved($event)"></router-outlet>
which allows to communicate (call getters, setters, and methods) with the component in componentAdded()
A shared service is the preferred way though.
The answer given above is correct and complete. I just want to add for those who the solution didn't work for them that they should add the service to providers only in the parent component and not the child to ensure that you get a singleton of the service, otherwise two service instances will be created.
This response is inspired by the comment of #HeisenBerg in the previous response.
I changed a little from Antara Datta's answer.
I created a Subscriber service
import {Injectable} from '#angular/core';
import {Subject} from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class Subscriber<T>
{
protected observable = new Subject<T>();
public next(item: T)
{
this.observable.next(item);
}
public subscribe(callback: (item:T)=>void) {
this.observable.subscribe(callback);
}
}
Whenever I need two components to share some information, I inject this service in the constructor which subscribe to it:
constructor(protected layoutOptions: Subscriber<Partial<LayoutOptions>>)
{
layoutOptions.subscribe(options => this.options = Object.assign({}, this.options, options));
}
and the one which updates it
constructor(protected router: Router, protected apiService: ApiService, protected layoutOptions: Subscriber<Partial<LayoutOptions>>)
{
this.layoutOptions.next({showNavBar: false});
}
It escapes my understanding why the router does not forward the "#Outputs".
I ended up dispatching barebones DOM events
// dom node needs to be a reference to a DOM node in your component instance
// preferably the root
dom.dispatchEvent(
new CustomEvent('event', {
detail: payload, // <- your payload here
bubbles: true,
composed: true,
})
);
You can catch it anywhere up the DOM tree like any other DOM event
Note: you need to unpack the payload from { detail: payload } on the receiving end..
Using Angular2 RC1
Suppose I have two custom services: for example a ConfigService and an AuthService.
And also assume that AuthService needs data from the ConfigService.
ConfigService is like that
import { Injectable } from '#angular/core';
#Injectable()
export class ConfigService {
public baseURL: string;
constructor() {
this.baseURL = 'http://localhost:8080';
}
}
My ConfigService has to be a singleton, so I've declare it in my bootstrap provider array:
bootstrap(MyWayAppComponent, [
HTTP_PROVIDERS,
...
ConfigService
]);
No problem when I try to inject my ConfigService in any Component, I always fetch the same instance.
But now I want to inject this ConfigService in my AuthService (AuthService is not a singleton and is provided in my auth component)
I inject the ConfigService in my AuthService as followed :
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import { ConfigService } from '../services/ConfigService';
#Injectable()
export class AuthService {
constructor(private http: Http, private configService: ConfigService) {
}
}
And response is :
EXCEPTION: Error: Uncaught (in promise): EXCEPTION: Error in :0:0
ORIGINAL EXCEPTION: No provider for ConfigService!
ORIGINAL STACKTRACE:
Error: DI Exception
at NoProviderError.BaseException [as constructor] (http://localhost:4200/vendor/#angular/core/src/facade/exceptions.js:17:23)
at NoProviderError.AbstractProviderError [as constructor] (http://localhost:4200/vendor/#angular/core/src/di/reflective_exceptions.js:39:16)
at new NoProviderError (http://localhost:4200/vendor/#angular/core/src/di/reflective_exceptions.js:75:16)
at ReflectiveInjector_._throwOrNull (http://localhost:4200/vendor/#angular/core/src/di/reflective_injector.js:776:19)
at ReflectiveInjector_._getByKeyDefault (http://localhost:4200/vendor/#angular/core/src/di/reflective_injector.js:804:25)
at ReflectiveInjector_._getByKey (http://localhost:4200/vendor/#angular/core/src/di/reflective_injector.js:767:25)
at ReflectiveInjector_.get (http://localhost:4200/vendor/#angular/core/src/di/reflective_injector.js:576:21)
at ElementInjector.get (http://localhost:4200/vendor/#angular/core/src/linker/element_injector.js:23:48)
at ElementInjector.get (http://localhost:4200/vendor/#angular/core/src/linker/element_injector.js:23:48)
at ReflectiveInjector_._getByKeyDefault (http://localhost:4200/vendor/#angular/core/src/di/reflective_injector.js:801:24)
ERROR CONTEXT:
[object Object]
I've carefully read the excellent Pascal Precht article but I don' see where I'm wrong... http://blog.thoughtram.io/angular/2015/09/17/resolve-service-dependencies-in-angular-2.html
Note : If I my AuthService I try to inject a "regular" service (like http) I have no pb... So I should forget something in my service declaration but what ???
Providers can only be defined when bootstrapping your application or at the level of components.
So you can define the ConfigService at this level.
See this question for more details:
What's the best way to inject one service into another in angular 2 (Beta)?
Regarding the AuthService, I don't know how you defined its providers but if you leverage useFactory for this, you need to explicitly define its dependencies (included the ConfigService). Here is à sample:
provide(AuthService, {
useFactory: (config) => {
return new...
},
depend: [ ConfigService ]
});
I am having a component that has canactivate
import {isLoggedIn} from '../login/isLoginedIn';
#CanActivate((next, previous) => {
isLoggedIn()
})
My "isLoggedIn" is as below
import {Http, Headers} from 'angular2/http';
class Auth {
constructor( #Inject(Http) private _http: Api) { }
check() {
this._http.get('/Users/LoggedInUser')
}
}
export const isLoggedIn = () => {
let injector = Injector.resolveAndCreate([Auth, Http]);
let auth = injector.get(Auth);
return auth.check();
};
I can't inject a service which has http as dependancy. Can this be done like this or is there a better way to do it?
Since the CanActivate is a decorator instead of a method as with OnActivate or CanDeactivate then you are correct in assuming that constructor dependency injection of the component that you are attempting to authorize is not an option.
The method which you are using will work, but there is a missed #Injectable() on your Auth class.
import {Injectable} from 'angular2/core';
import {Http, Headers} from 'angular2/http';
#Injectable()
class Auth {
constructor( #Inject(Http) private _http: Api) { }
check() {
this._http.get('/Users/LoggedInUser')
}
}
This approach is sound and I don't think that besides some syntactic sugar or minor refactoring that there would be much to improve this and still achieve the same amount of readability / maintainability for this approach.
One other addition that could be made to improve the flow and prevent a potential bug would be to return the observable in CanActivate so that the navigation will wait for the Http request to complete before deciding to continue or cancel.
#CanActivate((next, previous) => {
return isLoggedIn()
})
or for short
#CanActivate(() => isLoggedIn())
(Single statement arrow functions are auto-returning)