I am trying to create a reusable component that serves as a processing overlay when making asynchronous calls across my site. I have a service in place but the OverlayComponent doesn't seem to get invoked when showOverlay is invoked:
app.module.ts
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { HashLocationStrategy, LocationStrategy } from '#angular/common';
import { HttpModule } from '#angular/http';
import { AppRoutingModule } from './app-routing.module';
import { MainComponent } from './app.mysite.component';
import { OverlayComponent } from './app.mysite.overlay.component';
import { TrackerComponent } from './pages/tracker/mysite.tracker.component';
import { OverlayService } from "./overlay.service";
#NgModule({
imports: [ BrowserModule, AppRoutingModule, HttpModule ],
declarations: [
MainComponent,
OverlayComponent,
NavbarComponent,
TrackerComponent,
],
providers: [{provide: LocationStrategy, useClass: HashLocationStrategy}, OverlayService],
bootstrap: [ MainComponent ]
})
export class AppModule { }
TrackerComponent.ts
import { Component, OnInit } from '#angular/core';
import { OverlayService } from '../../overlay.service.js';
#Component({
moduleId: module.id,
selector: 'tracker-component',
templateUrl: '/public/app/templates/pages/tracker/mysite.tracker.component.html',
providers: [ OverlayService]
})
export class TrackerComponent implements OnInit{
constructor(private http: Http, private overlayService: OverlayService) {
}
ngOnInit(): void {
this.overlayService.showOverlay('Processing...'); //This kicks everything off but doesn't show the alert or overlay
this.overlayService.test(); //does exactly what i'd expect
}
}
overlay.service.ts
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
#Injectable()
export class OverlayService {
private message: string;
private subject: Subject<any> = new Subject<any>();
showOverlay(msg: string) : void { //When this gets invoked, shouldn't it be invoking a change to this.subject and therefore invoking getMessage()
this.message = msg;
this.subject.next(msg);
}
getMessage(): Observable<any> {
return this.subject.asObservable();
}
test() {
return 'test good'; //if I call this function, it works
}
}
app.mysite.overlay.component
import { Component, OnInit } from '#angular/core';
import { OverlayService } from './overlay.service';
#Component({
selector: 'overlay-component',
templateUrl: '/public/app/templates/mysite.overlay.component.html',
styleUrls: ['public/app/scss/overlay.css'],
providers: [OverlayService]
})
export class OverlayComponent implements OnInit {
private processingMessage: string;
constructor(private overlayService: OverlayService) {}
ngOnInit() {
this.overlayService.getMessage().subscribe((message: string) => { //since i'm subscribed to this, i'm expecting this to get called. It doesn't
this.processingMessage = message;
alert(this.processingMessage); //never gets hit
$('.overlay-component-container').show(); // never gets hit
},
error => {
alert('error');
})
}
}
Specifying providers in the Component metadata actually creates a new injectable, scoped to that component tree.
If you want to share the overlay service across the app, you'll need to declare the overlay provider in the NgModule, and not in the components. Alternatively, you can declare it only as a provider on the top-level entry component (eg. AppComponent), though it may cause confusion when used in other entry components/lazy-loaded modules.
See https://angular.io/docs/ts/latest/guide/hierarchical-dependency-injection.html for a better explanation
Related
I am trying to use angular routing to route to a new url when a button is clicked. I also need to use the module HttpClient to call some calls to the backend. However, whenever I create a HttpClient object, the routing doesn't work and it routes to a blank page with no url extension. When I delete the object, the routing works again. Anyone know how to overcome this? Here are some of my code snippets.
agent-page-component.ts (I create a Agent Service in the constructor)
import { Router} from "#angular/router";
import { Agent } from '../../models/agent.model'
import { AgentService } from '../../services/agent.service';
import { Subject, Subscription } from 'rxjs';
#Component({
selector: 'app-agent-page',
templateUrl: './agent-page.component.html',
styleUrls: ['./agent-page.component.css']
})
export class AgentPageComponent implements OnInit {
agents: Agent[] = [];
private agentSub: Subscription;
constructor(private agentService: AgentService){}
ngOnInit() {
this.agentService.getAgents();
this.agentSub = this.agentService.getAgentUpdateListener().subscribe((agents: Agent[]) => {
this.agents = agents;
});
}
ngOnDestroy() {
this.agentSub.unsubscribe();
}
}
agent.service.ts (this is where I import an HttpClient)
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs';
import { HttpClient, HttpClientModule } from '#angular/common/http';
#Injectable({providedIn: 'root'})
export class AgentService {
private agents: Agent[] = [];
private agentsUpdated = new Subject<Agent[]>();
constructor(private http: HttpClient){}
getAgentUpdateListener() {
return this.agentsUpdated.asObservable();
}
getAgents(){
this.http.get<{message: string, agents: Agent[]}>('http://localhost:3000/agents/Breach').subscribe((agentList) => {
this.agents = agentList.agents;
})
}
}
app-routing.module.ts
import { Routes, RouterModule } from '#angular/router';
import { AgentPageComponent } from './components/agent-page/agent-page.component';
import { HomePageComponent } from './components/home-page/home-page.component';
const routes: Routes = [
{path: 'agents', component: AgentPageComponent},
{path: '', component: HomePageComponent}
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
This is my Angular App. My app will get data from API (temporarily in JSON file) and show in many another sibling component. So I decide to create a category.service.ts that I get and store data in. I using APP_INITIALIZER to run this service first when my app started. But there is a problem that: This service is running first, AppComponent runs before service get data done. So my view have empty of data.
If I click button routing to this component, everything run perfect. But when I go to this component by url path or F5(refresh page), nothing is shown
category.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class CategoryService {
DATA_CATEGORIES = 'assets/categories.json';
private _categories = [];
constructor(private http: HttpClient) {
}
get categories() {
return this._categories;
}
Init(): Promise<any> {
return new Promise<void>(resolve => {
this.http.get(this.DATA_CATEGORIES).subscribe(data => {
this._categories = Array.from(Object.keys(data), k => data[k]);
console.log("load data...");
});
resolve();
});
}
}
app.module.ts
export function initializeCategoryService(catService: CategoryService) {
return (): Promise<any> => {
return catService.Init();
}
}
#NgModule({
declarations: [
AppComponent,
HomeComponent,
StoriesFilterPipe,
ViewStoryComponent,
ViewCatComponent,
FrontEndComponent,
SearchComponent,
BackEndComponent,
CrudStoryFormComponent,
CrudStoryComponent,
JwPaginationComponent,
CrudCatComponent,
CrudCatFormComponent,
CrudCatSearchResultComponent,
CatListComponent
],
imports: [
BrowserModule,
FormsModule,
AppRoutingModule,
HttpClientModule,
],
providers: [
StoryService,
CategoryService,
{
provide: APP_INITIALIZER, useFactory: initializeCategoryService, deps: [CategoryService], multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule { }
What I will suggest is to use Observable
like in your category service
import { Observable, Subject } from 'rxjs';
export class CategoryService {
private loadDataSub = new Subject<any>();
loadDataObservable$ = this.loadDataSub.asObservable();
emitLoadDataSuccess() {
this.loadDataSub.next();
}
Init(): Promise<any> {
return new Promise<void>(resolve => {
this.http.get(this.DATA_CATEGORIES).subscribe(data => {
this._categories = Array.from(Object.keys(data), k => data[k]);
console.log("load data...");
this.emitLoadDataSuccess(); // here we are emitting event
});
resolve();
});
}
}
And In your component
export class AppComponent implements OnInit {
constructor(private categoryService: CategoryService) {
this.categoryService.loadDataObservable$.subscribe(() => {
// here you can get data, this will only trigger when data is loaded from API
});
}
}
This is common case - i.e. you shows page while data is not avaliable yet - at slow and bad connections for instance, and it can do even more - connection was broken and data was nto recieved.
So, your page should be able to show not only data recieved, but also two another states: loading and error.
(So the advise is "add loader").
// data.service.ts
import { Injectable } from "#angular/core";
import { HttpClient, HttpClientModule } from "#angular/common/http";
#Injectable()
export class DataService {
private _categories = [];
constructor(private http: HttpClient) {}
get categories() {
return this._categories;
}
getData(): Promise<any[]> {
return new Promise<any[]>(resolve => {
this.http.get('https://api.myjson.com/bins/18qku4').subscribe(data => {
this._categories = Array.from(Object.keys(data), k => data[k]);
console.log("load data...");
resolve(this._categories);
});
});
}
}
// app.module.ts
import { NgModule, APP_INITIALIZER } from "#angular/core";
import { BrowserModule } from "#angular/platform-browser";
import { FormsModule } from "#angular/forms";
import { RouterModule } from "#angular/router";
import { ListDataComponent } from "./list-data/list-data.component";
import { AppComponent } from "./app.component";
import { DataService } from "./data.service";
import { HttpClientModule } from "#angular/common/http";
import {DetailComponent} from './detail/detail.component'
#NgModule({
imports: [
BrowserModule,
FormsModule,
HttpClientModule,
RouterModule.forRoot([
{ path: "", component: ListDataComponent },
{ path: "detail", component: DetailComponent }
])
],
declarations: [AppComponent, ListDataComponent,DetailComponent],
bootstrap: [AppComponent]
})
export class AppModule {}
//list-data.component.ts
import { Component, OnInit } from "#angular/core";
import { DataService } from "../data.service";
#Component({
selector: "app-list-data",
templateUrl: "./list-data.component.html",
styleUrls: ["./list-data.component.css"],
providers: [DataService],
})
export class ListDataComponent implements OnInit {
categories = [];
constructor(service: DataService) {
service.getData().then(data => {
debugger;
this.categories = data;
});
}
ngOnInit() {}
}
There are alternatives to resolve this issue:
One is you can use a loader which you can display until the service call finishes.
Second is you can use *ngIf="categories?.length" which will keep your component hides until your service call finishes.
I hope it will resolve your issue.
I have a ChatService A that depends upon an interface.
import { Injectable, Inject } from '#angular/core';
import { NGXLogger } from 'ngx-logger';
import { TokenHttpService } from '#lib/interfaces';
#Injectable({
providedIn: 'root'
})
export class ChatService {
constructor(
#Inject('TokenHttpService') private tokenFetchService: TokenHttpService,
private logger: NGXLogger
) {
this.logger.debug('Confirmed ctor for ChatService called');
}
}
I have a HttpService B that implements the TokenHttpService interface.
import { Injectable } from '#angular/core';
import { CoreDataService } from '#app/core/async-services/http/core.data';
import { TokenHttpService } from 'he-common';
import { NGXLogger } from 'ngx-logger';
#Injectable()
export class HttpService implements TokenHttpService {
constructor(
private _coreDataService: CoreDataService,
private logger: NGXLogger
) {
this.logger.debug("Confirmed ctor for HttpService called");
}
}
Then I try to combine them both in MessagingModule C
import { NgModule } from '#angular/core';
import { CommonModule } from '#angular/common';
import { FormsModule } from '#angular/forms';
import { Routes, RouterModule } from '#angular/router';
import { IonicModule } from '#ionic/angular';
import { MessagingPage } from './messaging.page';
import { TranslateModule } from '#ngx-translate/core';
import { ChatService } from 'my-common-lib';
import { SharedModule } from '#app/shared';
import { HttpService } from '#app/core/async-services/http/versioned/http';
const routes: Routes = [
{
path: '',
component: MessagingPage
}
];
#NgModule({
imports: [
CommonModule,
FormsModule,
IonicModule,
TranslateModule.forChild(),
RouterModule.forChild(routes),
SharedModule
],
declarations: [MessagingPage],
providers: [
{ provide: 'TokenHttpService', useValue: HttpService },
{ provide: ChatService, deps: ['TokenHttpService'] }
]
})
export class MessagingPageModule {}
If I'm not mistaken this code should work like this: C attempts to construct A and is told it needs B, B is provided to the module as well so it should provide B to A so that it can be constructed and used in component D.
import { Component } from '#angular/core';
import { ChatService } from 'he-common';
import { NGXLogger } from 'ngx-logger';
#Component({
selector: 'app-messaging',
templateUrl: './messaging.page.html',
styleUrls: ['./messaging.page.scss']
})
export class MessagingPage {
constructor(
private ChatService: ChatService,
private logger: NGXLogger
) {
// This line logs undefined.
this.logger.debug(this.twilioChatService);
}
}
How can I provide the TokenHttpService B to Service A? Can I do it in the same module?
I found out what my problems were:
I needed to use the useClass property instead of the useValue property. useClass expects a constructor (or factory function) while the useValue property expects a simple object.
In the deps section you are basically overriding what values you want to pass into the constructor for the service. So, in my case, I had the 'TokenHttpService' injected but no logger, so my service threw an error when it tried to call this.logger.debug. After adding NGXLogger to the deps array the service worked flawlessly.
Here's my final providers array:
providers: [
{
provide: 'TokenHttpService',
useClass: HttpService
},
{
provide: ChatService,
deps: ['TokenHttpService', NGXLogger],
useClass: ChatService
}
]
I am using JSONPlaceholder to get data in service, but I am unable to get data at all. Please, help me out.
user.component.html
<p (click)="getUsers()">Click Me!</p>
<ul *ngFor="let x of users">
<li>{{x.name}}, {{x.age}}</li>
</ul>
user.component.ts
import { Component, OnInit } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { DataService } from '../../services/data.service';
import { Http } from '#angular/http';
#Component({
selector: 'app-user',
templateUrl: './user.component.html',
styleUrls: ['./user.component.css']
})
export class UserComponent implements OnInit {
constructor(private http:Http) { }
ngOnInit() {
}
getUsers(){
console.log(this.http.get("https://jsonplaceholder.typicode.com/posts"));
}
}
app.module.ts
//Basic File Inclusions
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
//Additional files inclusion
import { AppComponent } from './app.component';
import { UserComponent } from './components/user/user.component';
import { DataService } from './services/data.service';
import { Http } from '#angular/http';
#NgModule({
declarations: [
AppComponent,
UserComponent
],
imports: [
BrowserModule
],
providers: [ Http ],
bootstrap: [AppComponent]
})
export class AppModule { }
Please, can someone help me out making a successfull service call via http and get data on console.
Import
import { HttpModule } from '#angular/http';
in app.module.ts
imports: [HttpModule]
Rest of code will be same as you posted.
calling http like
this.http.get(`https://jsonplaceholder.typicode.com/posts`).subscribe(
data => {
console.log(data)
});
user.component.ts
import { Component, OnInit } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { DataService } from '../../services/data.service';
import { Http } from '#angular/http';
import { HttpClient } from '#angular/common/http';
#Component({
selector: 'app-user',
templateUrl: './user.component.html',
styleUrls: ['./user.component.css']
})
export class UserComponent implements OnInit {
constructor(private http: HttpClient) { }
ngOnInit() {
}
this.http.get(`https://jsonplaceholder.typicode.com/posts`).subscribe(
data => {
console.log(data)
});
}
app.module.ts
//Basic File Inclusions
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
//Additional files inclusion
import { AppComponent } from './app.component';
import { UserComponent } from './components/user/user.component';
import { DataService } from './services/data.service';
import { Http } from '#angular/http';
import { HttpModule } from "#angular/http";
#NgModule({
declarations: [
AppComponent,
UserComponent
],
imports: [
BrowserModule,
HttpModule
],
providers: [ Http ],
bootstrap: [AppComponent]
})
export class AppModule { }
Hope this may help you..
You are just calling http.get(url) and expecting something in return is like calling ajax method without success and error callback methods.
Kindly check Http documentation and usage of get and post methods
Mistake/Wrong Assumptions:
this.http.get(https://jsonplaceholder.typicode.com/posts) will not return http response which are expecting
Reality/Correct Approach:
You can use either pipe(can be used in the service) or subscribe(can be used in Component) method on http's get method whose return type is Observable.
Based on your requirement, you can use either of them
http.get('https://jsonplaceholder.typicode.com/posts')
// Call map on the response observable to get the parsed people object
.pipe(map(res => res.json()))
// Subscribe to the observable to get the parsed people object and attach it to the
// component
.subscribe(posts => this.posts = posts)
Hence your component code becomes:
user.component.ts
import { Component, OnInit } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { DataService } from '../../services/data.service';
import { Http } from '#angular/http';
#Component({
selector: 'app-user',
templateUrl: './user.component.html',
styleUrls: ['./user.component.css']
})
export class UserComponent implements OnInit {
constructor(private http:Http) { }
ngOnInit() {
}
getUsers(){
this.http.get("https://jsonplaceholder.typicode.com/posts")
.subscribe(posts=> console.log(posts))
}
}
In my app, I want to open a modal popup for user to upload a file. So I used below code for it (Used angular material to open the popup):
Actual service call happens after I upload document and if uploaded wrong document then service respond with error message.
What I want to achieve is If user select incorrect document I want to show another popup (Error Modal popup).
However when I import dialog.service.ts in uploaddoc.component.ts gives me below error
Can't resolve all parameters for UploaddocComponent
also throws warning in console saying :
WARNING in Circular dependency detected:
src\app\dialog.service.ts ->
src\app\uploaddoc\uploaddoc.component.ts ->
src\app\dialog-service.service.ts
WARNING in Circular dependency detected:
src\app\uploaddoc\uploaddoc.component.ts ->
src\app\dialog.service.ts ->
src\app\uploaddoc\uploaddoc.component.ts
Note : UploaddocComponent and ErrorModalComponents are both added in entryComponents array in app.module.ts as both are dynamic components.
Below is my code (and reproduced in stackblitz)
Main Component(to open upload popup ):
HTML
<button type="button" (click)="openUpload()">Open Upload Popup</button>
Component.ts
import { Component } from '#angular/core';
import { MatDialog } from '#angular/material';
import { DialogService } from './dialog.service';
import { ErrorModalComponent } from './error-modal/error-modal.component';
import { UploaddocComponent } from './uploaddoc/uploaddoc.component';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Angular 5';
constructor(public dialog: MatDialog,private dialogsService: DialogService){
}
public openUpload(){
this.dialogsService.openUploadDialog(UploaddocComponent);
}
}
My dialog.service.ts
import { Injectable } from '#angular/core';
import { ErrorModalComponent } from './error-modal/error-modal.component';
import { UploaddocComponent } from './uploaddoc/uploaddoc.component';
import { MatDialogRef, MatDialog, MatDialogConfig } from '#angular/material';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class DialogService {
constructor(private dialog: MatDialog) { }
public infoPopup(): Observable<boolean> {
let dialogRef: MatDialogRef<ErrorModalComponent>;
dialogRef = this.dialog.open(ErrorModalComponent);
dialogRef.componentInstance.data = "error";
return dialogRef.afterClosed();
}
public openUploadDialog(data: Object): Observable<boolean> {
let dialogRef: MatDialogRef<UploaddocComponent>;
dialogRef = this.dialog.open(UploaddocComponent);
dialogRef.componentInstance.data = data;
return dialogRef.afterClosed();
}
}
upload.component.ts
import { Component, OnInit } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { delay } from 'rxjs/operators';
import { of } from 'rxjs/observable/of';
import { DialogService } from '../dialog.service';
import { ErrorModalComponent } from '../error-modal/error-modal.component';
#Component({
selector: 'app-uploaddoc',
templateUrl: './uploaddoc.component.html',
styleUrls: ['./uploaddoc.component.css']
})
export class UploaddocComponent implements OnInit {
constructor(public dialogService: DialogService) { }
data: any;
ngOnInit() {
}
public uploadDoc() {
//in this method actual service call happens and check if correct document is uploaded or not.
// Service side sends error if wrong document is uploaded.
// If wrong doc is uploaded then I want to display Error component here
// I will simulate service call here with delay and will open ErrorModal
of(['some data']).pipe(
delay(2000)
).subscribe((res)=>{
console.log(res);
// suppose error occured here then I want to open error modal So I added `dialog.service.ts` here in this component
this.dialogService.infoPopup();
})
}
}
upload.component.html
<p>
Upload popup works
<button type="button" (click)="uploadDoc()">Do upload</button>
</p>
app.module.ts
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { FormsModule } from '#angular/forms';
import { Components } from './materialComponents';
import { AppComponent } from './app.component';
import { HelloComponent } from './hello.component';
import { DialogService } from './dialog.service';
import { UploaddocComponent } from './uploaddoc/uploaddoc.component';
import { ErrorModalComponent } from './error-modal/error-modal.component';
import { BrowserAnimationsModule } from '#angular/platform-browser/animations';
#NgModule({
imports: [BrowserModule, FormsModule, ...Components,BrowserAnimationsModule],
declarations: [AppComponent, HelloComponent, UploaddocComponent, ErrorModalComponent],
bootstrap: [AppComponent],
entryComponents: [UploaddocComponent, ErrorModalComponent],
providers: [DialogService]
})
export class AppModule { }
I am not sure How should I handle circular dependency.
I may not have understood ngModule completely but guessing; Not able to inject service in components added in entryComponents array in app.module.ts.
What I am doing wrong here?
Well I tried below approach:
Modified uploaddoc.component.ts to :
import { Component, OnInit, Injector } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { delay } from 'rxjs/operators';
import { of } from 'rxjs/observable/of';
import { DialogService } from '../dialog.service';
import { ErrorModalComponent } from '../error-modal/error-modal.component';
#Component({
selector: 'app-uploaddoc',
templateUrl: './uploaddoc.component.html',
styleUrls: ['./uploaddoc.component.css']
})
export class UploaddocComponent implements OnInit {
constructor(private injector: Injector) {
this.dialogsService = this.injector.get(DialogsService);
}
data: any;
ngOnInit() {
}
public uploadDoc() {
of(['some data']).pipe(
delay(2000)
).subscribe((res)=>{
console.log(res);
// suppose error occured here then I want to open error modal So I added `dialog.service.ts` here in this component
this.dialogService.infoPopup();
})
}
}
I have used injector from #angular/core to explicitly get the service instance and no more error now.
however I can still see warnings. To remove warning I have added following in .angular-cli.json
"defaults": {
....
"build": {
"showCircularDependencies": false
}
}