I have a problem where when I type manually localhost:4200/create it goes on the page where i want it to go, but when I click on a link to lead me there, I get an error saying:
TypeError: Cannot read property 'unsubscribe' of undefined
at PostListComponent.ngOnDestroy
Here's my code:
header.component.html
<mat-toolbar color="primary">
<span><a routerLink="/">My Messages</a></span>
<ul>
<li><a routerLink="/create">New Post</a></li>
</ul>
</mat-toolbar>
app-routing.module.ts:
import { NgModule } from '#angular/core';
import { RouterModule, Routes } from '#angular/router';
import { PostCreateComponent } from './posts/post-create/post-create.component';
import { PostListComponent } from './posts/post-list/post-list.component';
const routes: Routes = [
{path: '', component: PostListComponent},
{path: 'create', component: PostCreateComponent},
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
postlistcomponent.ts
import { Component, OnDestroy, OnInit } from '#angular/core';
import { Subscription } from 'rxjs';
import { Post } from '../posts';
import { PostsService } from '../posts.service';
#Component({
selector: 'app-post-list',
templateUrl: './post-list.component.html',
styleUrls: ['./post-list.component.css'],
})
export class PostListComponent implements OnInit, OnDestroy {
posts: Post[] = [];
private postsSub: Subscription;
constructor(public postsService: PostsService) {}
ngOnInit(): void {
this.postsService.getPosts();
this.postsService.getPostUpdateListener().subscribe((posts: Post[]) => {
this.posts = posts;
});
}
onDelete(postId: string) {
this.postsService.deletePost(postId);
}
ngOnDestroy() {
this.postsSub.unsubscribe();
}
}
Like the error says, you're calling unsubscribe on an object that doesn't exist in PostListComponent (postlist.component.ts?)
In that file, find the ngOnDestroy function and for any this.object$.unsubscribe() functions, test for the object first -
if (this.object$ && !this.object$.closed) {
this.object$.unsubscribe()
}
I'm using this.object$ as an example - your variable will be called something different
When you navigate from / to /create your ngOnDestroy in the PostListComponent is throwing an error.
This is why it is happening on the link and not when you put in the url.
As you can see in your ngInit you are not passing any value to your variable (postsSub). That is why you cannot destroy it.
Change this :
ngOnInit(): void {
this.postsService.getPosts();
this.postsService.getPostUpdateListener().subscribe((posts: Post[]) => {
this.posts = posts;
});
}
For this:
ngOnInit(): void {
this.postsService.getPosts();
this.postsSub = this.postsService.getPostUpdateListener().subscribe((posts: Post[]) => {
this.posts = posts;
});
}
That should work.
Regards
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.
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 { }
I'm trying to pass a variable that is set on a component, to the parent component via a getter/setter in a service. The setter is applied correctly, but the getter returns undefined.
The below code was pulled out of another project I work on that does just fine with this code so I'm not sure why it isn't working here.
I simply just need to pass the pageTitle that is set on the child component, pass it to the parent component to display in its HTML.
Parent Component
TS: styleguide.component.ts
import { Component } from '#angular/core';
import { Router, ActivatedRoute } from '#angular/router';
import { StyleguideService } from './styleguide.service';
#Component({
selector: 'styleguide',
templateUrl: './styleguide.component.html',
host: {'class': 'route'},
})
export class StyleguideComponent {
constructor( private ss: StyleguideService ) {}
}
Relevant HTML: styleguide.component.html
<a [routerLink]="[]" aria-current="page" class="crumbs__link crumbs__link--active" [title]="ss.pageTitle">{{ss.pageTitle}}</a>
Parent Module: styleguide.module.ts
import { NgModule } from '#angular/core';
import { CommonModule } from '#angular/common';
import { FormsModule } from '#angular/forms';
import { StyleguideService } from './styleguide.service';
import { StyleguideComponent } from './styleguide.component';
import { TemplatesComponent } from './templates/templates.component';
...
#NgModule({
imports: [
CommonModule,
FormsModule,
...
],
declarations: [
StyleguideComponent,
TemplatesComponent,
...
],
exports: [
...
],
providers: [
StyleguideService
]
})
export class StyleguideModule {}
Service: styleguide.service.ts
import { Injectable } from '#angular/core';
#Injectable()
export class StyleguideService {
pageTitleS: string;
get pageTitle(): string {
console.log('get title: ', this.pageTitleS); // <-- Returns undefined
return this.pageTitleS;
}
set pageTitle(s: string) {
console.log('set title: ', s);
this.pageTitleS= s;
}
}
Child Component: templates.component.ts
import { Component } from '#angular/core';
import { StyleguideService } from '../styleguide.service';
#Component({
selector: 'templates',
templateUrl: './templates.component.html',
host: {'class': 'route__content'}
})
export class TemplatesComponent {
constructor( private ss: StyleguideService ) {
this.ss.pageTitle = "Templates";
}
}
You should implement the Service with Observables. A quick example would be something like this:
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
import {Injectable} from '#angular/core'
#Injectable()
export class Service {
private value: BehaviorSubject<string>;
constructor() {
this.value = <BehaviorSubject<string>>new BehaviorSubject();
}
setValue(value=""){
this.value.next(value);
}
getValue() {
return this.value.asObservable();
}
}
The Parent Component would subscribe to it like so:
import {Component, OnInit} from '#angular/core'
import { Service } from './service';
#Component({
selector: 'parent-component',
template: `
<div>
<h2>Value {{value}}</h2>
<child-component></child-component>
</div>
`,
})
export class ParentComponent implements OnInit {
value:string;
constructor(private service: Service) {
}
ngOnInit(){
this.service.getValue().subscribe((newValue)=>{
this.value = newValue;
})
}
}
And the Child Component would set the value and also subscribe to it like so:
import {Component, OnInit} from '#angular/core'
import { Service } from './service';
#Component({
selector: 'child-component',
template: `
<div>
<h2>Child Value {{value}}</h2>
</div>
`,
})
export class ChildComponent implements OnInit {
value:string;
constructor(private service: Service) {
this.service.setValue('New Value');
}
ngOnInit(){
this.service.getValue().subscribe((newValue)=>{
this.value = newValue;
})
}
}
Your setter is never called. You instantiate the service using StyleguideComponent, not the TemplatesComponent which does call the setter, and the constructor of StyleguideComponent does not call the setter on the service which is why the value remains undefined.
The TemplatesComponent has an element selector templates which I do not see in the styleguide.component.html you have in the question which is why I believe TemplatesComponent is never being created.
You are not calling the setter function in your child.component.ts instead you are setting the value of variable but I think you are accessing it wrongly as you are missing the last S in the variable name. You should be doing
export class TemplatesComponent {
constructor( private ss: StyleguideService ) {
this.ss.pageTitle("Templates");
// Now to get it you should call
this.ss.pageTitle(); // Should console.log the value
}
}
Okay so it was related to my routing setup, I didn't have my child routes setup correctly so this really had nothing to do with the getter/setter after all.
I am trying to extend some components with another class but I got
[Angular] Can't resolve all parameters for MyProjectsComponent in 'pathtomyproject'/src/app/my-projects/my-projects.component.ts: ([object Object], ?).
security.ts
import { Router} from '#angular/router';
export abstract class Security {
constructor(
protected router: Router
) { }
isLogged() {
if (!sessionStorage.getItem('user')) {
this.router.navigate(['/login']);
}
}
}
my-projects.component
import { Component, OnInit } from '#angular/core';
import { RouterModule, Router, ActivatedRoute } from '#angular/router';
import { ProjectService } from './project.service';
import { Security } from '../security';
#Component({
selector: 'app-my-projects',
templateUrl: './my-projects.component.html',
styleUrls: ['./my-projects.component.scss'],
providers: [ProjectService],
})
export class MyProjectsComponent extends Security implements OnInit {
projects: Array<any>;
constructor(
private projectService: ProjectService,
protected router
) {
super(router);
}
ngOnInit() {
this.getProjects();
}
async getProjects() {
const data: any = await this.projectService.getAll();
this.projects = data._embedded.projects;
}
delete(id) {
this.projectService.delete(id);
this.getProjects();
}
edit(id) {
const path = `/edit/${id}`;
this.router.navigate([path]);
}
}
There might be some issue in the constructor of the MyProjectsComponent. You misconfigured the dependency injection of the router. Change it to:
constructor(
private projectService: ProjectService,
protected router: Router // <-- DI for Router
) {
super(router);
}
and then it should work.
I tried to reproduce your issue in this stackblitz:
https://stackblitz.com/edit/angular-aifdew
I am not understanding why same code is not working from app.component which is perfectly worked in auth.guard.
I have written some code to check that user is Logged In to server or not and used canActivate in routing defined auth.guard like below
Routing
{ path: 'dashboard', component: DashboardComponent , canActivate: [AuthGuard] }
AuthGuard
import { Injectable } from '#angular/core';
import { Router, CanActivate } from '#angular/router';
import { AuthService} from './services/auth.service';
#Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService:AuthService, private router:Router) { }
canActivate(next:ActivatedRouteSnapshot, state:RouterStateSnapshot) {
return this.authService.isAuth().map(e => {
if (e) {
return true;
}
}).catch(() => {
this.router.navigate(['/login']);
return Observable.of(false);
});
}
}
It is working fine but not working in AppComponent
AppComponent
import { Component } from '#angular/core';
import { OnInit } from '#angular/core';
import { Router } from '#angular/router';
import { AuthService } from './services/auth.service';
#Component({
moduleId: module.id,
selector: 'my-app',
templateUrl: './views/app.component.html',
styleUrls: [ './styles/app.component.css' ]
})
export class AppComponent implements OnInit{
title = 'Jiffy';
status:boolean;
swap:boolean=true;
constructor(authService:AuthService, private router:Router){
return this.authService.isAuth().map(e => {
if (e) {
return true;
}
}).catch(() => {
this.router.navigate(['/login']);
return Observable.of(false);
});
}
}
I am not understanding why it is not working here?
Error
Error: Uncaught (in promise): TypeError: Cannot read property 'isAuth'
of undefined TypeError: Cannot read property 'isAuth' of undefined at
new AppComponent
AuthService
isAuth(){
return this.http.get("/account/check")
.map(function(res){
return status=res.json().status;
});
}
Express Server with Passport
router.get('/check', function(req, res,next) {
if (req.isAuthenticated()){
return res.send({"status":true});
}
return res.send({"status":false});
});
You need to add public or private to the parameters inside the constructor if you want to get them with this inside your component.
constructor(private authService:AuthService, private router:Router)
The error is saying that authService is undefined. Have you declared AuthService as a provider in your app.module?