Cyclic Dependency in Angular 4 while Opening one modal component from another - javascript

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
}
}

Related

Angular Routing not working when creating an HttpModule object

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 { }

NullInjectorError: StaticInjectorError[JobcounterComponent -> FetchJobDataService]

I am getting this error as shown in the screenshot below.
I have studied several similiar questions here. Most common suggestion is 'Add your service to your app module's providers array'. As I am writing web components with Angular elements, I do not actively use the default app component. However, I have added die Injectable property providedIn: root to the services decorator. IMO, this should be equivalent to adding the service to app module's providers array.
Have got no ideas on how to fix this.
Best, Dropbear.
My fetch-job-data.service.ts file:
import { Injectable } from '#angular/core';
import {HttpClient, HttpClientModule} from '#angular/common/http';
import {count} from 'rxjs/operators';
import {Observable} from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class FetchJobDataService {
constructor(private _http: HttpClient) { }
getJobCount(jobCountUrl: string): Observable<{'count': number}> {
return this._http.get<{'count': number}>(jobCountUrl);
}
}
My jobcounter.component.js file:
import {Component, OnDestroy, OnInit} from '#angular/core';
import {FetchJobDataService} from '../../services/fetch-job-data.service';
import {takeUntil} from 'rxjs/operators';
import {Subject} from 'rxjs';
#Component({
templateUrl: './jobcounter.component.html',
styleUrls: ['./jobcounter.component.scss']
})
export class JobcounterComponent implements OnInit, OnDestroy {
public jobCount: {'count': number};
public jobDataUrl = 'assets/data/jobCount.json';
private complete$ = new Subject<void>();
constructor(private _fetchDataService: FetchJobDataService) {
console.log('Job counter initialized...');
}
ngOnInit() {
this._fetchDataService.getJobCount(this.jobDataUrl)
.pipe(
takeUntil(this.complete$)
)
.subscribe(
(jobCount) => {
this.jobCount = jobCount;
console.log('Job count: ' + jobCount);
}
);
}
ngOnDestroy() {
this.complete$.next();
this.complete$.complete();
}
}
Browser Console error message
Import HttpClientModule in app.module
Like this:
import { HttpClientModule } from '#angular/common/http';
#NgModule({
imports: [
...
HttpClientModule,
],
declarations: [
]
})

http service hit not working in Angular

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))
}
}

error "ERROR TypeError: Cannot read property 'createComponent' of undefined" when work with dynamic component in Angular2

I am trying to add the components dynamically in angular4.
I checked with other questions, But i cant find solution.
I got the error
ERROR TypeError: Cannot read property 'createComponent' of undefined
on dynamic components.
adv.component.ts
import { Component, OnInit, AfterContentInit, ViewChild, ViewContainerRef, ComponentFactoryResolver } from '#angular/core';
import { SampleComponent } from '../sample/sample.component';
#Component({
selector: 'app-adv',
templateUrl: './adv.component.html',
styleUrls: ['./adv.component.css']
})
export class AdvComponent implements OnInit, AfterContentInit {
#ViewChild('container', {read:'ViewContainerRef'}) container;
constructor(private resolver : ComponentFactoryResolver) { }
ngOnInit() {
}
ngAfterContentInit(){
const sampleFactory = this.resolver.resolveComponentFactory(SampleComponent);
this.container.createComponent(sampleFactory);
}
}
adv.component.html
<div #container></div>
app.module.ts
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { AppComponent } from './app.component';
import { AdvComponent } from './adv/adv.component';
import { SampleComponent } from './sample/sample.component';
#NgModule({
declarations: [
AppComponent,
AdvComponent,
SampleComponent
],
entryComponents:[
SampleComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
About ngAfterViewInit in docs:
Respond after Angular initializes the component's views and child
views.
while ngAfterContentInit:
Respond after Angular projects external content into the component's
view.
So the child view is not ready in ngAfterContentInit, so move this part
ngAfterContentInit(){
const sampleFactory = this.resolver.resolveComponentFactory(SampleComponent);
this.container.createComponent(sampleFactory);
}
to
ngAfterViewInit() {
const sampleFactory = this.resolver.resolveComponentFactory(SampleComponent);
this.container.createComponent(sampleFactory);
}
Also change:
#ViewChild('container', {read:'ViewContainerRef'}) container;
to
#ViewChild('container', { read: ViewContainerRef }) container: ViewContainerRef

Creating a angular2 service that displays a processing overlay

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

Categories