I have a global Errorhandler in which I process Client- and Server-Errors.
To provide a feedback for the user I want to open a modal which returns the error-message.
Therefore I've implemented a modal:
import {Component} from '#angular/core';
import {BsModalRef, BsModalService} from 'ngx-bootstrap';
import {Button} from '../../layout-models/button.model';
#Component({
selector: 'error-modal',
templateUrl: './error-modal.component.html',
styleUrls: ['./error-modal.component.scss']
})
export class ErrorModalComponent {
title: string;
buttonTitle = 'OK';
type: 'error';
button: Button;
protected modalRef: BsModalRef;
constructor(protected modalService: BsModalService) {}
public show(title: string, message: string) {
this.title = title;
this.modalRef = this.modalService.show(
message,
Object.assign({}, { class: `modal-banner ${this.type}`})
);
}
hide() {
if (this.modalRef) {
this.modalRef.hide();
}
}
}
In my Notification-Service:
import {Injectable, NgZone} from '#angular/core';
import { ErrorModalComponent } from '../error-modal.component';
#Injectable({
providedIn: 'root'
})
export class NotificationService {
public errorModalComponent: ErrorModalComponent;
showError(title: string, message: string): void {
this.errorModalComponent.show(title, message);
}
}
Which leads to
Uncaught TypeError: Cannot read property 'show' of undefined
I feel like I am doing some fundamental mistake - the main purpose of this is to have a centralized modal. Is this possible or do I need to use the ModalComponent in every Component in which I want to show the error-handling-modal?
I wouldn't use ngx-modal I would use NgbModal
What yazantahhan means is something like this:
import {Injectable} from "#angular/core";
import {NgbModal, NgbModalRef} from "#ng-bootstrap/ng-bootstrap";
#Injectable()
export class ErrorModalService {
title: string;
buttonTitle = "OK";
type: "error";
protected modalRef: NgbModalRef;
constructor(protected modalService: NgbModal) {}
public show(title: string, message: string) {
this.title = title;
this.modalRef = this.modalService.open(
message
);
}
hide() {
if (this.modalRef) {
this.modalRef.close();
}
}
}
Then inject and use it like this:
import { Component } from "#angular/core";
import {ErrorModalService} from "./ErrorModalService";
#Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.scss"]
})
export class AppComponent {
title = "testAngular";
constructor(
private errorModalService: ErrorModalService,
) {}
showError() {
this.errorModalService.show("title", "message");
}
}
Don't forget to provide the service in your module
import { BrowserModule } from "#angular/platform-browser";
import { NgModule } from "#angular/core";
import { AppRoutingModule } from "./app-routing.module";
import { AppComponent } from "./app.component";
import {ErrorModalService} from "./ErrorModalService";
import {BsModalService} from "ngx-bootstrap/modal";
import {NgbModule} from "#ng-bootstrap/ng-bootstrap";
#NgModule({
declarations: [
AppComponent,
],
imports: [
BrowserModule,
AppRoutingModule,
NgbModule,
],
providers: [
ErrorModalService,
],
bootstrap: [AppComponent],
})
export class AppModule { }
Related
I am working on an e-commerce app who's front-end is made in Angular 13.
I use a service to handle the products coming from an API. I run into a problem while ring to display the product details.
See Stackblitz demo HERE.
In app\services\product.service.ts I have:
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs';
import { HttpClient } from '#angular/common/http';
import { Product, ProductResponse } from '../models/product';
#Injectable({
providedIn: 'root'
})
export class ProductService {
products: Product[] = [];
apiURL: string = 'https://dummyjson.com';
constructor(private http: HttpClient) {}
// Product List
public getProducts(): Observable<ProductResponse>{
return this.http.get<ProductResponse>(`${this.apiURL}/products`);
}
// Product Details (single product)
public getProductDetails(id: Number): Observable<ProductResponse>{
return this.http.get<ProductResponse>(`${this.apiURL}/products/${id}`);
}
}
In app\app.module.ts:
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { CommonModule } from '#angular/common';
import { HttpClientModule } from '#angular/common/http';
import { AppComponent } from './app.component';
import { Routes, RouterModule } from '#angular/router';
import { NavbarComponent } from './components/navbar/navbar.component';
import { FooterComponent } from './components/footer/footer.component';
import { SidebarComponent } from './components/sidebar/sidebar.component';
import { HomeComponent } from './components/home/home.component';
import { ProductItemComponent } from './components/product-item/product-item.component';
import { ProductsListComponent } from './components/products-list/products-list.component';
import { ProductsDetailsComponent } from './components/products-details/products-details.component';
const routes: Routes = [
{ path: '', component: HomeComponent},
{ path: 'products', component: ProductsListComponent},
{ path: 'products/show/:id', component: ProductsDetailsComponent},
];
#NgModule({
declarations: [
AppComponent,
NavbarComponent,
FooterComponent,
SidebarComponent,
ProductsListComponent,
ProductItemComponent ,
ProductsDetailsComponent
],
imports: [
CommonModule,
BrowserModule,
HttpClientModule,
RouterModule.forRoot(routes),
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
In app\models\product.ts:
export class Product {
id?: number;
title?: string;
description?: string;
price?: number;
discountPercentage?: number;
rating?: number;
stock?: number;
brand?: string;
category?: string;
thumbnail?: string;
}
export interface ProductResponse {
products: Product[];
total: number;
skip: number;
limit: number;
}
In app\components\products-details\products-details.component.ts I have:
import { Component, OnInit, InputDecorator, Input } from '#angular/core';
import { Router, ActivatedRoute } from '#angular/router';
import { Product, ProductResponse } from '../../models/product';
import { ProductService } from '../../services/product.service';
#Component({
selector: 'app-products-details',
templateUrl: './products-details.component.html',
styleUrls: ['./products-details.component.css']
})
export class ProductsDetailsComponent implements OnInit {
#Input() product!: Product;
productResponse: any;
constructor(private ProductService: ProductService, private Router: Router, private ActivatedRoute:ActivatedRoute) { }
ngOnInit(): void {
const id = Number(this.ActivatedRoute.snapshot.paramMap.get('id'));
this.ProductService.getProductDetails(id).subscribe((response) => (this.productResponse = response));
}
}
In app\components\products-details\products-details.component.html I have:
<h1>{{ product.title }}</h1>
The problem
When I access a product details route (for instance, http://localhost:4200/products/show/1), the page displays an empty <h1> tag and the Chrome console shows Cannot read properties of undefined (reading 'title').
Where is my mistake?
Unlike the https://dummyjson.com/products endpoint, the https://dummyjson.com/products/{id} returns a plain Product object, so:
// product.service.ts
public getProductDetails(id: Number): Observable<Product>{
return this.http.get<Product>(`${this.apiURL}/products/${id}`);
}
// products-details.component.ts
// the #Input decorator is wrong — the data is not passed to the component from outside
// but instead fetched inside of the component
#Input() product!: Product;
// productResponse: any — this field is unused and should be removed
ngOnInit(): void {
...
this.ProductService.getProductDetails(id).subscribe((product) => (this.product = product));
}
The #Input() property only works for external assignment of values.
The following will probably do what you want:
product: Product | undefined;
this.ProductService.getProductDetails(id).subscribe((response) => (this.product = response));
<h1>{{ product?.title }}</h1>
Furthermore I believe handling raw responses should happen in the service instead of the component, but that may be a matter of personal preference.
My login component logs the user in successfully, but then I'd like to create a document for this user in FireStore.
I keep getting an issue loading the AngularFirestore afs in this case.
zone-evergreen.js:659 Unhandled Promise rejection: Cannot read property 'afs' of undefined ; Zone: <root> ; Task: Promise.then ; Value: TypeError: Cannot read property 'afs' of undefined
at onLoginFulfiled (login.component.ts:32)
at ZoneDelegate.invoke (zone-evergreen.js:365)
at Zone.run (zone-evergreen.js:124)
at zone-evergreen.js:851
at ZoneDelegate.invokeTask (zone-evergreen.js:400)
Here is my login component code.
import { Component, OnInit } from '#angular/core';
import { LoginRequest } from './loginRequest';
import { AngularFireAuth } from '#angular/fire/auth';
import { AngularFirestore, AngularFirestoreDocument } from '#angular/fire/firestore';
import { auth } from 'firebase/app';
import 'firebase/firestore';
export interface Item { uid: string; }
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
loginRequest: LoginRequest = { password: '', login: ''};
constructor(public fireAuth: AngularFireAuth, public afs: AngularFirestore) { }
ngOnInit(): void {
}
login(): void {
console.log(this.loginRequest);
this.fireAuth.signInWithEmailAndPassword(this.loginRequest.login, this.loginRequest.password)
.then(this.onLoginFulfiled, this.onLoginRejected);
}
onLoginFulfiled(credential: auth.UserCredential): void {
console.log('Fulfilled: ' + credential);
const uid = credential.user.uid;
const userDocument = this.afs.doc<Item>(`system-users/${uid}`);
userDocument.update({uid});
}
onLoginRejected(reason: any): void {
console.log('Rejected: ' + reason);
}
}
Also my app.module.ts
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { FormsModule } from '#angular/forms';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
import { AngularFireModule } from '#angular/fire';
import { AngularFireAnalyticsModule } from '#angular/fire/analytics';
import { AngularFirestoreModule } from '#angular/fire/firestore';
import { environment } from '../environments/environment';
import { LoginComponent } from './login/login.component';
#NgModule({
declarations: [
AppComponent,
PageNotFoundComponent,
LoginComponent
],
imports: [
BrowserModule,
FormsModule,
AngularFireModule.initializeApp(environment.firebase),
AngularFireAnalyticsModule,
AngularFirestoreModule,
AppRoutingModule,
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Try replacing
.then(this.onLoginFulfiled, this.onLoginRejected);
with
.then(() => this.onLoginFulfiled(), () => this.onLoginRejected());
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
}
}
I am working on my first Angular app and dealing with the HttpClientModule in my component and getting errors.
following the docs Angular -HttpClient I installed the HttpClientModule in the app.module.ts as instructed, then in my emails.component.ts I have the following:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-emails',
templateUrl: './emails.component.html',
styleUrls: ['./emails.component.scss'],
results: string[]
})
export class EmailsComponent implements OnInit {
constructor(private http: HttpClient) { }
ngOnInit() {
this.http.get('/api/email_list.json').subscribe(data => {
this.results = data['results'];
});
}
}
which is giving me the following error in my console:
ERROR in src/app/components/emails/emails.component.ts(7,3): error TS2345: Argument of type '{ selector: string; templateUrl: string; styleUrls: string[]; results: any; }' is not assignable to parameter of type 'Component'.
Object literal may only specify known properties, and 'results' does not exist in type 'Component'.
src/app/components/emails/emails.component.ts(7,12): error TS2693: 'string' only refers to a type, but is being used as a value here.
src/app/components/emails/emails.component.ts(7,19): error TS1109: Expression expected.
src/app/components/emails/emails.component.ts(12,29): error TS2304: Cannot find name 'HttpClient'.
src/app/components/emails/emails.component.ts(16,12): error TS2339: Property 'results' does not exist on type 'EmailsComponent'.
app.module.ts:
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { HttpClientModule } from '#angular/common/http';
import { AppComponent } from './app.component';
import { EmailsComponent } from './components/emails/emails.component';
#NgModule({
declarations: [
AppComponent,
EmailsComponent
],
imports: [
BrowserModule,
HttpClientModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
you're component should be:
first you need to import HttpClient
second results: string[] should be inside the class not at decoration component exactly as I did here :
import { Component, OnInit } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Component({
selector: 'app-emails',
templateUrl: './emails.component.html',
styleUrls: ['./emails.component.scss']
})
export class EmailsComponent implements OnInit {
results: string[];
constructor(private http: HttpClient) { }
ngOnInit() {
this.http.get('/api/email_list.json').subscribe(data => {
this.results = data['results'];
});
}
}
Please read the description carefully, they explaining you need to load HttpClient.
In your app module you forgot to provide.
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { HttpClientModule } from '#angular/common/http';
import { AppComponent } from './app.component';
import { EmailsComponent } from './components/emails/emails.component';
#NgModule({
declarations: [
AppComponent,
EmailsComponent
],
imports: [
BrowserModule,
HttpClientModule
],
providers: [HttpClient],
bootstrap: [AppComponent]
})
export class AppModule { }
Then in your component:
import { Component, OnInit } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Component({
selector: 'app-emails',
templateUrl: './emails.component.html',
styleUrls: ['./emails.component.scss']
})
export class EmailsComponent implements OnInit {
results: string[]
constructor(private http: HttpClient) { }
ngOnInit() {
this.http.get('/api/email_list.json').subscribe(data => {
this.results = data['results'];
});
}
}
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