Angular product-details.component.html Object is possibly 'undefined' - html

I'm following the View Product Details tutorial on the angular website and encountered the error:
Error in src/app/product-details/product-details.component.html (4:16)
Object is possibly 'undefined'.
I can't proceed to the next tutorial because it needs this page to function.
Here is the code:
product-list.component.html
<h2>Products</h2>
<div *ngFor="let product of products">
<h3>
<a [title]="product.name + ' details'" [routerLink]="['/products', product.id]">
{{ product.name }}
</a>
</h3>
</div>
product-details.component.ts
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
import { Product, products } from '../products';
#Component({
selector: 'app-product-details',
templateUrl: './product-details.component.html',
styleUrls: ['./product-details.component.css']
})
export class ProductDetailsComponent implements OnInit {
product: Product|undefined;
constructor(
private route: ActivatedRoute,
) { }
ngOnInit() {
// First get the product id from the current route.
const routeParams = this.route.snapshot.paramMap;
const productIdFromRoute = Number(routeParams.get('productId'));
// Find the product that correspond with the id provided in route.
this.product = products.find(product => product.id === productIdFromRoute);
}
}
product-details.component.html
<h2>Product Details</h2>
<div *ngIf="product">
<h3>{{ product.name }}</h3>
<h4>{{ product.price | currency }}</h4>
<p>{{ product.description }}</p>
</div>
I hope someone can help to figure out the problem because I see the code is functioning.
Thank you.

I'm also doing this tutorial and I fixed it by adding the ProductDetailsComponent to the declarations inside app.module.ts.
declarations: [
AppComponent,
TopBarComponent,
ProductListComponent,
ProductAlertsComponent,
ProductDetailsComponent //<-- add this into declarations
],
Don't forget to import first the ProductDetailsComponent in app.module.ts.
import { ProductDetailsComponent } from './product-details/product-details.component';
It should work after refreshing the entire website (not just the display to the right, go save and refresh the browser tab).

You might want to try using product?.name

I fixed that by adding 'ProductDetailsComponent' import in 'app.module.ts'

tsconfig.json file
"strict": false,
Turn off the strict. Angular will able to compile successfully.

Related

Why ngfor directive is not working, though I have created proper object in Typescript class

I have created proper Ingredient object in Typesript, also proper "model" for the object. Though ngfor directive is not working. I am getting this error "NG0303: Can't bind to 'ngforOf' since it isn't a known property of 'a'" on inspecting in browser.
My model code
export class Ingredient {
constructor(public name: string, public amount: number){}
}
My TypeScript code
import { Component, OnInit } from '#angular/core';
import { Ingredient } from '../shared/ingredient.model';
#Component({
selector: 'app-shopping-list',
templateUrl: './shopping-list.component.html',
styleUrls: ['./shopping-list.component.css']
})
export class ShoppingListComponent implements OnInit {
ingredients: Ingredient[] = [
new Ingredient ("Apple", 5),
new Ingredient ("Tomato", 5)
];
constructor() { }
ngOnInit(): void {
}
}
My HTML code
<div class="row">
<div class="col-xs-10">
<app-shopping-edit></app-shopping-edit>
<hr>
<ul class = "list-group">
<a class = "list-group-item"
style = "cursor : pointer"
*ngfor = "let ingredient of ingredients">
{{ ingredient.name }}({{ ingredient.amount}})
</a>
</ul>
</div>
</div>
Page is loading properly, but ingredients are not loading. On inspecting in browser this error "NG0303: Can't bind to 'ngforOf' since it isn't a known property of 'a'" is coming.
Can someone please help.
Try moving the *ngFor inside the ul tag.
Edit: You have a typo.. It's *ngFor="", not *ngfor="".
If you are inside AppModule check if you have BroswerModule in the imports array there.
If you are in some different module then check if CommonModule is a part of that module's array. CommonModule provides us with core Angular features like *ngIf or *ngFor in your specific case.
Also watch out, you typed *ngfor instead *ngFor.

Identifier 'image' is not defined. 'never' does not contain such a member

Edit :: The output is added. I getting the output successfully, but I just don't know why I am getting this error. I had already done this before but now I am facing this error. I had tried using "?" and "!!" but still not fixing. What is this and how to fix this..
Edit ::
html
<div class="container" *ngFor="let data of data">
<a href="{{ data.url }}"> //error in url ( Identifier 'url' is not defined. 'never' does not contain such a member )
<img src="{{ data.image }}"> // error in image ( Identifier 'image' is not defined. 'never' does not contain such a member )
</a>
</div>
ts file
import { Component, OnInit } from '#angular/core';
import { ApiService } from 'src/app/services/api.service';
#Component({
selector: 'app-technology',
templateUrl: './technology.component.html',
styleUrls: ['./technology.component.css']
})
export class TechnologyComponent implements OnInit {
data = [];
constructor(private apiservice : ApiService) { }
ngOnInit() {
this.apiservice.getTechUrl().subscribe(async (data : any) => {
this.data = await data.articles
})
}
}
I found the solution for this
I had just changed the data = []; to data : any; and there is no error and i also got the output without any error.
Error template syntax ngFor="let data of data"
ngFor can't use check point name same array data name.
Try this
<div class="container" *ngFor="let item of data">
<a href="{{ item.url }}">
<img src="{{ item.image }}">
</a>
</div>
hope help you
I found the solution
I just changed the data = []; to data: any; in the .ts file and then there is not error;
but I still don't know what is the exact reason for that error.
I don't if I am right about this the Json file which I was receiving is of type object and storing inside the data of array given me the error but after changing the to data: any; I am not giving any specific type in here, it may be of any type so error is gone.

How can I change a body tag class in Angular 10 (best practice)?

I want to switch between two classes (light and dark) at TAG Body.
What I did? I created a service:
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class ThemeService {
body = document.body;
constructor() { }
changeLight() {
this.body.classList.replace('light', 'dark');
}
changeDark() {
this.body.classList.replace('dark', 'light');
}
}
It is working as expected but I know that this code does not use best practices.
What is the correct way to change between these two classes?
Edit: Added a service to the stackblitz, but again, there are many ways to do this. This is just a starting point.
While the "right way" is subjective, you have some options to make it "Angular-y"
Component:
import { Component, Inject } from '#angular/core';
import { DOCUMENT } from '#angular/common';
// Create a type that accepts either the string 'light' or 'dark' only
type Theme = 'light' | 'dark';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
// Default to 'light' theme
currentTheme: Theme = 'light';
// Inject document which is safe when used with server-side rendering
constructor(#Inject(DOCUMENT) private document: Document) {
// Add the current (light) theme as a default
this.document.body.classList.add(this.currentTheme);
}
// Swap them out, and keep track of the new theme
switchTheme(newTheme: Theme): void {
this.document.body.classList.replace(this.currentTheme, newTheme)
this.currentTheme = newTheme;
}
}
HTML:
<p>
Current theme: {{ currentTheme }}
<button (click)="switchTheme('light')">Light mode</button>
<button (click)="switchTheme('dark')">Dark mode</button>
</p>
Many ways to do this, but one benefit of defining the types is if you provide a bad value, such as:
<p>
Current theme: {{ currentTheme }}
<button (click)="switchTheme('light')">Light mode</button>
<button (click)="switchTheme('dark')">Dark mode</button>
<button (click)="switchTheme('noop')">Invalid</button>
</p>
You'll get an error:
Argument of type '"noop"' is not assignable to parameter of type 'Theme'.
StackBlitz

How to Show Placeholder if *ngFor Let Object of Objects from HTML Binding returns an empty array

I display data from a template that I create in another component (team-announcements.component)... in the main team.component.ts I use the selector for team-announcements, and add the [announcement]="announcements" and ngFor="let announcement of announcements" to load the data. If the array returns no data IE there are no announcements, how can I display a placeholder like "No Announcements"?
This is where I load the announcements in team.component.html. The data is served through an API service and is retrieved in "team.component.ts", the HTML for the objects in question is below.
team.component.ts (get announcement functions):
getAnnouncements() {
this.teamsService.getTeamAnnouncements(this.team.slug)
.subscribe(announcements => this.announcements = announcements);
console.log("announcements", this.announcements);
}
team.component.html
<div class="team-announcement">
<div class="announcement-title">Message of the Day</div>
<app-team-announcements
[announcement]="announcement"
*ngFor="let announcement of announcements">
</app-team-announcements>
</div>
This is how "app-team-announcements" above is templated in a separate file, "team-announcement.component.html" and is exported, and then used in the above code...
team-announcements.component.ts
import { Component, EventEmitter, Input, Output, OnInit, OnDestroy } from '#angular/core';
import { Team, Announcement, User, UserService } from '../core';
import { Subscription } from 'rxjs';
#Component({
selector: 'app-team-announcements',
templateUrl: './team-announcement.component.html'
})
export class TeamAnnouncementComponent implements OnInit, OnDestroy {
constructor(
private userService: UserService
) {}
private subscription: Subscription;
#Input() announcement: Announcement;
#Output() deleteAnnouncement = new EventEmitter<boolean>();
canModify: boolean;
ngOnInit() {
// Load the current user's data
this.subscription = this.userService.currentUser.subscribe(
(userData: User) => {
this.canModify = (userData.username === this.announcement.author.username);
}
);
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
team-announcements.component.html
<div class="announcement-text">
{{announcement.body}}
</div>
I am unsure of how or where to "If" check the array length to display a placeholder. Can anyone help?
If you want to hide it and display something else instead you can use the else property from *ngIf:
<div class="team-announcement">
<div class="announcement-title">Message of the Day</div>
<ng-container *ngIf="announcements.length != 0; else emptyArray">
<app-team-announcements
[announcement]="announcement"
*ngFor="let announcement of announcements">
</app-team-announcements>
</ng-container>
</div>
<ng-template #emptyArray>No announcements...</ng-template>
When you want an element with *ngFor to depend on a condition (*ngIf), a good alternative is to nest the element with an *ngFor in a <ng-container> with an *ngIf. A good thing about <ng-container> is that it wont actually be part of the DOM but will obey the *ngIf.
You could insert a div wich is only displayed when your array is empty:
<div class="team-announcement">
<div class="announcement-title">Message of the Day</div>
<app-team-announcements
[announcement]="announcement"
*ngFor="let announcement of announcements">
</app-team-announcements>
<div *ngIf="announcements.length===0"> No announcements </div>
</div>
Edit: Corrected the errors

Angular 2 *ngFor not displaying data

I can't seem to get the data in this.heroes to display on the list. I put an alert(this.heroes[0].name); and it did display the data so this._service.getHeroes(); is returning the data
app.html
<div style="width:1000px;margin:auto">
<ul>
<li *ngFor="#hero of heroes">
<span>{{hero.name}}</span>
</li>
</ul>
</div>
app.component.ts
import {Component} from 'angular2/core';
import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router';
import {HeroService} from './heroes/hero.service';
import {Hero} from './heroes/hero';
#Component({
selector: 'my-app',
templateUrl: 'templates/app.html',
providers: [HeroService],
directives: [ROUTER_DIRECTIVES]
})
export class AppComponent {
heroes: Hero[];
constructor(private _service: HeroService) { }
ngOnInit() {
this.heroes = this._service.getHeroes();
}
}
Try this first and see if it works:
<li ng-repeat="hero in heroes">
{{hero.name}}
</li>
If this works you might have an older angular version that does not support the *ngFor syntax yet (I think this is supported in the new version angular 2).
I've done a little more research and no data is displaying. For example:
<h2>My favorite hero is {{ myHero }}</h2>
gets a blank line (where myHero is a populated string) whereas
<h2>My favorite hero is </h2>
displays
My favourite hero is
I'm going to change the Beta version of Angular 2 to see if it helps