I manage to fetch the product data through the URL and when I am on the product details page there is a button that allows me to add the product to a sub-collection of the logged in user.
Below is what I want:
users/{userID}/shopping/{productID}
details-product.component.html displaying information in product details
<div class="uk-container panier" *ngIf="product">
<div id="modal-center" class="uk-flex-top" uk-modal>
<div class="uk-modal-dialog uk-modal-body uk-margin-auto-vertical">
<button class="uk-modal-close-default" type="button" uk-close></button>
<img [src]="(product | async)?.imageURL" class="nav-left">
</div>
</div>
<div class="uk-child-width-1-2#s" uk-grid>
<div>
<a href="#modal-center" uk-toggle>
<img [src]="(product | async)?.imageURL" class="nav-left" alt="">
</a>
</div>
<div>
<div class="uk-dark uk-padding">
<h3> {{ (product | async)?.name }}</h3>
<hr class="uk-divider-icon">
<div class="uk-flex prix">
<p class="uk-text">
<span>{{ (product | async)?.price | currency: 'XOF' }} </span>
</p>
</div>
<hr class="uk-divider-icon">
<button class="uk-button uk-button-default uk-form-width-medium" (click)="showDialog()">
<i class='bx bxs-cart-add'></i>
J'achète le produit
</button>
</div>
</div>
</div>
</div>
shopping-cart.service.ts
import { AngularFirestore, AngularFirestoreCollection } from '#angular/fire/compat/firestore';
import "firebase/auth";
import { User } from 'src/app/models/user.model';
import { Product } from 'src/app/models/product.model';
#Injectable({
providedIn: 'root'
})
export class ShoppingCardService {
userCollection: AngularFirestoreCollection<User>;
productsCollection: AngularFirestoreCollection<Product>;
constructor(private readonly dbstore: AngularFirestore) {
this.userCollection = this.dbstore.collection('users');
this.productsCollection = this.dbstore.collection('products');
}
addToMyCart(product: Product, userID: string, qteProduct: number) {
const userDoc = this.dbstore.firestore.collection('users').doc(userID);
const productDoc = this.dbstore.firestore.collection('products').doc(product.id);
const userShoppingProduct = userDoc.collection('shopping');
return userShoppingProduct.doc(product.id).set(product);
}
}
product.service.ts
import { Injectable } from '#angular/core';
import { AngularFirestore, AngularFirestoreCollection } from '#angular/fire/compat/firestore';
import { Router } from '#angular/router';
import { Observable } from 'rxjs';
import { Product } from 'src/app/models/product.model';
#Injectable({
providedIn: 'root'
})
export class ProductsService {
productCollection: AngularFirestoreCollection<Product>;
constructor(private dbstore: AngularFirestore, private router: Router) {
this.productCollection = this.dbstore.collection('products', (ref) =>
ref.orderBy('category', 'desc')
);
}
getDetailProduct(productId: string): Observable<any> {
return this.productCollection.doc(productId).valueChanges();
}
}
details-product.component.ts
import { ActivatedRoute } from '#angular/router';
import { Observable } from 'rxjs/internal/Observable';
import { User } from 'src/app/models/user.model';
import { Product } from 'src/app/models/product.model';
import { ProductsService } from '../../shared/services/products.service';
import { ShoppingCardService } from 'src/app/shared/services/shopping-card.service';
import { AngularFirestoreCollection } from '#angular/fire/compat/firestore';
import { AddToCartComponent } from './add-to-cart.component'
import { MatDialog } from '#angular/material/dialog';
#Component({
selector: 'app-details-product',
templateUrl: './details-product.component.html',
styleUrls: ['./details-product.component.css']
})
export class DetailsProductComponent implements OnInit {
quantity: number = 0;
productIdRoute: string;
isMyProduct: boolean = false;
product: Observable<Product>;
userCollection!: AngularFirestoreCollection<User>
constructor(
private route: ActivatedRoute,
private productService: ProductsService,
private shoppingCardService: ShoppingCardService,
private titleService: Title,
private dialog: MatDialog,
) {
const routeParams = this.route.snapshot.paramMap;
this.productIdRoute = String(routeParams.get('productId'));
this.product = this.productService.getDetailProduct(this.productIdRoute);
}
showDialog(): void {
const data = this.dialog.open(AddToCartComponent, {
width: '30rem',
data: { productID: this.productIdRoute },
});
console.log(data);
}
add-to-cart.component.ts
component in modal to confirm adding to cart
import { Component, OnInit, Inject } from '#angular/core';
import { MatDialog, MAT_DIALOG_DATA } from '#angular/material/dialog';
import { ShoppingCardService } from '../../shared/services/shopping-card.service';
import { Product } from 'src/app/models/product.model';
#Component({
selector: 'app-add-to-cart',
template: `
<div class="relative p-4 w-full max-w-md h-full md:h-auto">
<div class="p-6 text-center">
<svg aria-hidden="true" class="mx-auto mb-4 w-14 h-14 text-gray-400 dark:text-gray-200" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
<h3 class="mb-5 text-lg font-normal text-gray-500 dark:text-gray-400">Do you want to add this product?</h3>
<button (click)="onAddPubpik(data.product, data.userID)" type="button" class="text-white bg-gray-800 hover:bg-gray-800 focus:ring-4 focus:outline-none font-medium rounded-lg text-sm inline-flex items-center px-5 py-2.5 text-center mr-2">
Yes, i add to cart
</button>
</div>
</div>
`,
styleUrls: ['./details-product.component.css']
})
export class AddToCartComponent implements OnInit {
constructor(
#Inject(MAT_DIALOG_DATA) public data: { product: Product, userID: string, qteProduct: number },
private dialog: MatDialog,
private shoppingService: ShoppingCardService
) {}
ngOnInit(): void {
}
async onAddPubpik(product: Product, userID: string): Promise<void> {
this.dialog.closeAll();
const qteProduct = (product.quantity += 1);
product.isMyProduct = true;
await this.shoppingService.addToMyCart(product, userID, qteProduct);
}
}
When i confirm, in console :
On this line const qteProduct = (product.quantity += 1);, product is undefined. You’re not passing product to the matdialog component in your showDialog() method hence the error.
Related
I created a simple calculator app. I have two component: Calculator and Result and use Angular router between them. Now I want to do this : When I perform a calculation in Calculation component, the result will be passed and displayed in another Result component. Can you show me some ways for this ?
calculator.component.ts
#Component({
selector: 'app-calculator',
templateUrl: './calculator.component.html',
styleUrls: ['./calculator.component.css']
})
export class CalculatorComponent implements OnInit {
public number1: number;
public number2: number;
public result: number;
constructor() {
}
sum() {
this.result = this.number1 + this.number2;
}
diff() {
this.result = this.number1 - this.number2;
}
mult() {
this.result = this.number1 * this.number2;
}
divi() {
this.result = this.number1 / this.number2;
}
ngOnInit(): void {
}
}
calculator.component.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div class="card">
<div class="card-header">CALCULATOR</div>
<div class="card-body">
<div class="form-group value">
<div class="form-group row">
<label class="col-md-2 col-form-label">Value 1:</label>
<div class="col-md-10 input-1">
<input [(ngModel)]='number1' class="form-control inp" type="number" name="num1">
</div>
</div>
<div class="form-group row">
<label class="col-md-2 col-form-label">Value 2:</label>
<div class="col-md-10 input-2">
<input [(ngModel)]='number2' class="form-control inp" type="number" name="num2">
</div>
</div>
</div>
<div class="buttons">
<br>
<button class="butt" (click)='sum()'> + </button>
<button class="butt" (click)='diff()'> - </button>
<button class="butt" (click)='mult()'> x </button>
<button class="butt" (click)='divi()'> / </button>
<br><br><br>
</div>
{{result}}
</div>
</div>
</body>
</html>
app-routing.module.ts
import { NgModule } from '#angular/core';
import { Routes, RouterModule } from '#angular/router';
import { CalculatorComponent } from './calculator/calculator.component';
import { ResultComponent } from './result/result.component';
const appRoutes: Routes = [
{ path: 'calculator', component: CalculatorComponent },
{ path: 'result', component: ResultComponent }
];
#NgModule({
imports: [RouterModule.forRoot(appRoutes,
{ enableTracing: true } // <-- debugging purposes only
)],
exports: [RouterModule]
})
export class AppRoutingModule { }
app.component.html
<h1>Angular Router</h1>
<nav>
<a routerLink="/calculator" routerLinkActive="active">Calculator</a><br>
<a routerLink="/result" routerLinkActive="active">Result</a>
</nav>
<router-outlet></router-outlet>
data.service.ts
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class DataService {
constructor() { }
}
There are multiple ways to do this,
The simplest way can be to use state while navigating to result route from calculator route.
this.router.navigate(['result'], { state: { result } });
This will put your result in window.history.state object, which then you can access from result component.
ngOnInit() {
this.resultValue = window.history.state.result;
}
Demo
2. You can also store the result in shared service and then access that variable from result component. Inject the service wherever required and store the retrieve data from it.
#Injectable()
export class SharedServiceService {
storeValue: number = 0;
constructor() { }
}
I have a hamburger inside of my header - as soon as it is clicked, I wan't to disable scroll - since the hamburger is moving on scroll.
I tried to set the hamburger to position: fixed. But this one changed the position of its neighbour, which looked weird.
Is there a way to realize this in typescript - so that as soon clicked is true, scrolling is disabled. Or is there a better approach?
https://stackblitz.com/edit/angular-beiajz
export class HeaderComponent implements OnInit {
clicked = false;
onClick(): void {
this.clicked = !this.clicked;
}
constructor() { }
ngOnInit(): void {
}
}
<div class="container-fluid mobile position-absolute">
<div class="row m-0 w-100">
<div class="col-6 offset-3 justify-content-center d-flex">
<a class="align-self-center" routerLink="">
<h1>NØREBRO STUDIOS</h1>
</a>
</div>
<div class="col-3">
<button class="hamburger hamburger--collapse" (click)="onClick()" [class.is-active]="clicked" type="button">
<span class="hamburger-box">
<span class="hamburger-inner"></span>
</span>
</button>
</div>
<div class="container-fluid position-fixed min-vh-100 px-0 slide-in" [class.get-active]="clicked">
</div>
</div>
</div>
One of the way is passing a event from the child to parent whenever click is made on the hamburger menu and changing the parent class CSS
in app.component.html
<app-test (hamburgerClick)="hamburgerClickEvent($event)"></app-test>
<div [ngClass]="{
'container-fluid-overlay': overlayFlag === true,
'container-fluid': overlayFlag === false }">
</div>
in app.component.ts
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Angular';
overlayFlag = false; // overlay flag..onload overlay is hidden
public hamburgerClickEvent(e:boolean):void{
this.overlayFlag= e;
}
}
in app.component.css
.container-fluid {
min-height: 200vh;
}
.container-fluid-overlay {
height: auto;
}
in test.component.ts
import { Component, OnInit , Output, EventEmitter} from '#angular/core';
#Component({
selector: 'app-test',
templateUrl: './test.component.html',
styleUrls: ['./test.component.css']
})
export class TestComponent implements OnInit {
#Output() hamburgerClick = new EventEmitter();//emitter
clicked = false;
onClick(): void {
this.clicked = !this.clicked;
this.hamburgerClick.emit(this.clicked); //emitting clicked event to parent
}
constructor() { }
ngOnInit(): void {
}
}
Hope it helps :)
I have a parent component as under and 4 child components for registration. The data is not visible if i retrun from step L2 to L1.
Any idea?
<div class="container-fluid page-content" mat-dialog-content>
<div class="row">
<ul class="nav nav-pills nav-justified">
<li [ngClass]="{'active': this.actL1}"><a (click)="onClick('l1')" data-toggle="pill">L1</a></li>
<li [ngClass]="{'active': this.actL2}"><a (click)="onClick('l2')" data-toggle="pill">L2</a>
</li>
<li [ngClass]="{'active': this.actL3}"><a (click)="onClick('l3')" data-toggle="pill">L3</a>
</li>
<li [ngClass]="{'active': this.actL4}"><a (click)="onClick('l4')" data-toggle="pill">L4</a></li>
</ul>
<app-request-ltype *ngIf=actL1></app-request-ltype>
<app-request-ptype *ngIf=actL2></app-request-ptype>
<app-request-stype *ngIf=actL3></app-request-stype>
<app-request-rType *ngIf=actL4></app-request-rtype>
</div>
</div>
ltype component (RequestDetails ts file contains eg name:string)
import { Component, OnInit, getDebugNode } from '#angular/core';
import { FormGroup, FormBuilder } from '#angular/forms';
import { Router, ActivatedRoute } from '#angular/router';
import { RequestDetails } from '';
#Component({
selector: 'app-request-l1',
templateUrl: './request-l1.component.html',
styleUrls: ['./request-l1.component.css']
})
export class LtypeComponent implements OnInit {
requestL1Form: FormGroup;
constructor(private l1FormB: FormBuilder, private router: Router, private route: ActivatedRoute) {
this.requestL1Form = this.createL1FormGroup(l1FormB);
}
ngOnInit() {
}
createL1FormGroup(l1FB: FormBuilder) {
return l1FB.group({
l1Data: l1FB.group( new RequestDetails())
});
}
onSubmit() {
console.log('submitted values:', this.requestL1Form.value);
}
}
I'm using Angular 5, and I wanted to press another button to get another button.
I do not know if I would have to use the ngIf, but I do not know how to identify the previous button.
example.ts
import { Component, OnInit } from '#angular/core';
import { Hero } from '../hero';
import { HeroService } from '../hero.service';
#Component({
selector: 'app-heroes',
templateUrl: './heroes.component.html',
styleUrls: ['./heroes.component.css']
})
export class HeroesComponent implements OnInit {
heroes: Hero[];
constructor(private heroService: HeroService) { }
ngOnInit() {
this.getHeroes();
}
getHeroes(): void {
this.heroService.getHeroes()
.subscribe(heroes => this.heroes = heroes);
}
add(name: string): void {
name = name.trim();
if (!name) { return; }
this.heroService.addHero({ name } as Hero)
.subscribe(hero => {
this.heroes.push(hero);
});
}
delete(hero: Hero): void {
this.heroes = this.heroes.filter(h => h !== hero);
this.heroService.deleteHero(hero).subscribe();
}
}
example.html
<ul class="heroes">
<li *ngFor="let hero of heroes">
<a routerLink="/detail/{{hero.id}}">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</a>
<button class="delete" title="delete hero" (click)="delete(hero)">x</button>
</li>
</ul>
In your component.ts:
#Component({
selector: 'app-heroes',
templateUrl: './heroes.component.html',
styleUrls: ['./heroes.component.css']
})
export class HeroesComponent implements OnInit {
heroes: Hero[];
showSecondButton: boolean = false;
constructor(private heroService: HeroService) { }
ngOnInit() {
this.getHeroes();
}
delete(hero: Hero): void {
this.heroes = this.heroes.filter(h => h !== hero);
this.heroService.deleteHero(hero).subscribe();
this.showSecondButton = true;
}
}
In your html:
<ul class="heroes">
<li *ngFor="let hero of heroes">
<a routerLink="/detail/{{hero.id}}">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</a>
<button class="delete" title="delete hero" (click)="delete(hero)">x</button>
<button *ngIf="showSecondButton"> Your second button</button>
</li>
</ul>
so your best bet is to create two buttons within a div, then have a toggle that switches the button that is displayed.
Example:
<div>
<button *ngIf=!displaysecondbutton (click)='showsecondbutton()'>First Button Yo</button>
<button *ngIf=displaysecondbutton>SecondButtonYo</button>
</div>
typescript file;
displaysecondbutton = false;
showsecondbutton(){
this.displaysecondbutton = true;
}
To keep the code concise I would implement an if statement in your delete function:
public deleteConfirmed = false;
public confirmDelete = false;
delete(hero: Hero): void {
if (deleteConfirmed) {
confirmDelete = deleteConfirmed = false;
this.heroes = this.heroes.filter(h => h !== hero);
this.heroService.deleteHero(hero).subscribe();
}
else{
confirmDelete = true;
}
}
Then in your html:
<button class="delete" title="delete hero" (click)="delete(hero)">Delete</button>
<button *ngIf="confirmDelete" class="delete" title="delete hero" (click)="deleteConfirmed=true; delete(hero)">Confirm Deletion</button>
This way, you are always cycling the same function and just altering some states along the way.
I'm trying to use the dynamic component provided by #GünterZöchbauer here Angular 2 dynamic tabs with user-click chosen components to create rows and columns. So far I got successfully the rows added but still can't get the columns created inside the rows.
Here is my code:
DesignerModule
import { NgModule } from '#angular/core';
import { CommonModule } from '#angular/common';
import { MaterializeModule } from '../../shared/materialize/materialize.module';
import { DesignerComponent } from './designer.component';
import { RowComponent } from './designer.component';
import { ColumnComponent } from './designer.component';
import { DynWrapperComponent } from './dyn-wrapper.component';
#NgModule({
imports: [
CommonModule,
MaterializeModule,
],
declarations: [
DesignerComponent,
DynWrapperComponent,
RowComponent,
ColumnComponent,
],
entryComponents: [
RowComponent,
ColumnComponent,
],
providers: [
]
})
export class DesignerModule {}
DynWrapperComponent
import { Component, Compiler, ViewContainerRef, ViewChild, Input, ElementRef,
ComponentRef, ComponentFactory, ComponentFactoryResolver} from '#angular/core'
import {BrowserModule} from '#angular/platform-browser'
// Helper component to add dynamic components
#Component({
moduleId: module.id,
selector: 'dcl-wrapper',
template: `<div #target></div>`
})
export class DynWrapperComponent {
#ViewChild('target', {read: ViewContainerRef}) target: any;
#Input() type: any;
cmpRef:ComponentRef<any>;
private isViewInitialized:boolean = false;
constructor(private componentFactoryResolver: ComponentFactoryResolver,
private compiler: Compiler,
private el: ElementRef) {}
updateComponent() {
if(!this.isViewInitialized) {
return;
}
if(this.cmpRef) {
this.cmpRef.destroy();
}
let factory = this.componentFactoryResolver.resolveComponentFactory(this.type);
//this.resolver.resolveComponent(this.type).then((factory:ComponentFactory<any>) => {
this.cmpRef = this.target.createComponent(factory)
}
ngOnChanges() {
this.updateComponent();
}
ngAfterViewInit() {
this.isViewInitialized = true;
this.updateComponent();
}
ngOnDestroy() {
if(this.cmpRef) {
this.cmpRef.destroy();
}
}
}
DesignerComponent
import { Component, ViewChild, ElementRef, ContentChildren } from '#angular/core';
#Component({
moduleId: module.id,
selector: 'my-row',
templateUrl: 'row.component.html',
styles: [
`.row:hover {
border: 3px dashed #880e4f ;
}
`
]
})
export class RowComponent {
colIndex: number = 0;
colList: Object[] = [];
addColumn() {
this.colList.splice(this.colIndex, 0, ColumnComponent);
this.colIndex++;
}
removeColumn(colIdx: number) {
this.colList.splice(colIdx, 1);
}
}
#Component({
moduleId: module.id,
selector: 'my-column',
templateUrl: 'column.component.html',
styles: [
`.col:hover {
border: 3px solid #304ffe;
}
`
]
})
export class ColumnComponent {
}
#Component({
moduleId: module.id,
selector: 'my-designer',
templateUrl: 'designer.component.html',
})
export class DesignerComponent {
#ViewChild('builder') builder:ElementRef;
elementIndex: number = 0;
list: Object[] = [];
ngAfterViewInit() {
}
addRow() {
this.list.splice(this.elementIndex, 0, RowComponent);
this.elementIndex++;
}
remove(idx: number) {
this.list.splice(idx, 1);
}
}
DesignerComponent.html
<div #builder class="row">
<div class="s1 teal lighten-2">
<p class="flow-text">teste do html builder</p>
<div *ngFor="let row of list; let idx = index" >
<p class="flow-text">Linha {{idx}}</p>
<dcl-wrapper [type]="row"></dcl-wrapper>
<a class="btn-floating btn-small waves-effect waves-light purple" (click)="remove(idx)"><i class="material-icons">remove</i></a>
</div>
</div>
</div>
<a class="btn-floating btn-large waves-effect waves-light red" (click)="addRow()"><i class="material-icons">add</i></a>
RowComponent.html
<div #row class="row">
<div class="s12 teal lighten-2">
<p class="flow-text">adicionando linha no html builder</p>
</div>
<div *ngFor="let col of colList; let colIndex = index">
<p>Column</p>
<dcl-wrapper [type]="col"></dcl-wrapper>
</div>
<a class="btn-floating btn-small waves-effect waves-light waves-teal" (click)="addColumn()"><i class="material-icons">view_column</i></a>
</div>
ColumnComponent.html
<div class="col s1 purple lighten-2">
<p class="flow-text">column added ....</p>
</div>
This approach is generating the following error:
Expression has changed after it was checked. Previous value:
'CD_INIT_VALUE'. Current value:
Did it anyone get this working as nested elements?
Thanks very much for the help!
Try to use ngAfterContentInit hook instead of ngAfterViewInit in your DynWrapperComponent:
dyn-wrapper.component.ts
ngAfterContentInit() {
this.isViewInitialized = true;
this.updateComponent();
}