Updating a list on button click Angular - javascript

I hope i post this in a concise way. The main goal here is to update a list after a button click. I have a list of hobbies. When I click the (hobbies.component) in the list, the hobby-view component should update with a list of that hobby type. I can successfully make a call to the server. But, I'm not seeing the component hobby-view update.
Hobbies.component
<ul class="hobbyList">
<li *ngFor="let hobby of hobbies" (click)="onSelect(hobby)">
<span class="badge">{{hobby.HobbyName}}</span>
</li>
</ul>
getHobbies() creates the ul of hobbies to click. onSelect then uses a service to update a string inside the hobby-view component (which works). Then tries to update a list inside the hobby-view component.
export class HobbiesComponent implements OnInit {
selectedHobby: hobbyObj;
hobbies: hobbyObj[];
constructor(
private hobbyService: HobbyService,
private masterhobbydata: MasterHobbydata
) { }
ngOnInit() {
this.getHobbies();
}
getHobbies(): void {
this.hobbyService.getHobbies()
.subscribe(hobbies => this.hobbies = hobbies);
}
onSelect(hobby: hobbyObj): void {
this.selectedHobby = hobby;
this.masterhobbydata.changeMessage(this.selectedHobby.HobbyName);
this.masterhobbydata.getHobbiesById();
}
}
Hobby-view.component
<ul>
<li *ngFor="let hobby of hobbiesRooms">
<span class="badge">{{hobby.hname}}</span>
</li>
</ul>
<p>
Message: {{message}}
</p>
export class HobbyViewComponent implements OnInit {
hobbyRoomObj = HobbyRoomObj;
hobbiesRooms: HobbyRoomObj[];
message:string;
constructor(private data: MasterHobbydata) { }
ngOnInit() {
this.data.currentMessage.subscribe(message => this.message = message);
}
getHobbiesById(): void {
console.log("hit");
this.data.getHobbiesById()
.subscribe(hobbiesRooms => this.hobbiesRooms = hobbiesRooms);
}
}
Inside ngOnIt is where the simple string gets updated successfully from the onClick method inside hobbies.component.
Here is the service component MasterHobbyData
#Injectable({
providedIn: 'root'
})
export class MasterHobbydata {
private messageSource = new BehaviorSubject<string>("1");
currentMessage = this.messageSource.asObservable();
private hobbyListByIdUrl="http://local/?Hobby=";
constructor(private http: HttpClient){}
changeMessage(message: string) {
this.hobbyListByIdUrl += message;
this.messageSource.next(message);
}
//Returns a full list of current hobbies hosted
getHobbiesById(): Observable<HobbyRoomObj[]>{
return this.http.get<HobbyRoomObj[]>(this.hobbyListByIdUrl)
.pipe(
catchError(this.handleError('getHobbiesById', []))
);
}
}
I've been trying to manipulate this piece of code for the list of hobbies. I believe this where my problem lies.
private messageSource = new BehaviorSubject<string>("1");
currentMessage = this.messageSource.asObservable();
I think I have to make variable of BehaviorSubject
But I can't seem to find the correct syntax to this.
Am I correct in my approach? If i am, how should I implement the syntax? Any help would be much appreciated. I am new to Angular.
LIST
Karate
Apples
Cake
When I click on Apples, a request to my php script will return a list of apples. And so on and so forth.

my suggestion is to use Subject.
i.e messageSource = new Subject<string>();
and subscribe to messageSource
currentMessage = this.messageSource.asObservable(); not required remove it.
Change the currentMessage to messageSource
ngOnInit() {
this.data.messageSource .subscribe(message => this.message = message);
}
Whenever the messageSource changed it will automatically subscribed in you component.
#Injectable({
providedIn: 'root'
})
export class MasterHobbydata {
private messageSource = new BehaviorSubject<string>("1");
currentMessage = this.messageSource.asObservable();
private hobbyListByIdUrl="http://local/?Hobby=";
constructor(private http: HttpClient){}
changeMessage(message: string) {
this.messageSource.next(message);
}
//Returns a full list of current hobbies hosted
getHobbiesById(message): Observable<HobbyRoomObj[]>{
const url = this.hobbyListByIdUrl + message;
return this.http.get<HobbyRoomObj[]>(url)
.pipe(map(( response) =>{
return response;
}
)).pipe(catchError(this.handleError('getHobbiesById', []))
);
}
}
component file changes
export class HobbyViewComponent implements OnInit {
hobbyRoomObj = HobbyRoomObj;
hobbiesRooms: HobbyRoomObj[];
message:string;
constructor(private data: MasterHobbydata) { }
ngOnInit() {
this.data.currentMessage.subscribe(message => {
this.message = message
this.getHobbiesById(message);
});
}
getHobbiesById(message): void {
console.log("hit");
this.data.getHobbiesById(message)
.subscribe(hobbiesRooms => this.hobbiesRooms = hobbiesRooms);
}
}

Related

Why does the child component mutation my variable in angular

I'm trying to find a way to share a variable between my components in Angular. My variable that I pass to my child components are interchangeable between their child components. I used service for this, but still could not solve the problem. I am sharing a demo by simplifying my operations using BehaviorSubject.
demo : https://stackblitz.com/edit/angular-ivy-6usdww?file=src%2Fapp%2Fhello.component.ts
My problem is:
i am sending variables from my app.component.ts component to hello and hello-a components. i am mutating variable in hello component. However, just before that I point the first version to a service. however, the changed status is forwarded.
hello.component
export class HelloComponent {
#Input() colDef: any[];
col: any[];
constructor(private columnDefService: ColumnDefService) {}
ngOnInit() {
let colDefFirst = Object.assign([], this.colDef);
this.columnDefService.setFirstColDef(colDefFirst);
this.setColumnDef(this.colDef);
}
setColumnDef(colDef: any[]): void {
let rC: any = Object.assign([], colDef);
rC[0].hide = true;
this.columnDefService.setColDef([].concat(rC));
this.columnDefService.colDef$.subscribe((colDef: any) => {
this.col = colDef;
});
}
}
hello-a.component
export class HelloAComponent {
#Input() colDef: any[];
firstColDef: any[] = [];
constructor(private ColumnDefService: ColumnDefService) {}
ngOnInit() {
this.ColumnDefService.firstColDef$.subscribe((firstColDef: any) => {
console.log('firstColDef', firstColDef);
this.firstColDef = firstColDef;
});
}
}
coldef.service
#Injectable({
providedIn: 'root'
})
export class ColumnDefService {
private colDefSource = new BehaviorSubject([]);
private firstColDefSource = new BehaviorSubject([]);
colDef$ = this.colDefSource.asObservable();
firstColDef$ = this.firstColDefSource.asObservable();
setColDef(colDef: any) {
this.colDefSource.next(colDef);
}
setFirstColDef(colDef: any) {
this.firstColDefSource.next(colDef);
}
}
this is what i want to do:
storing my variable in a component for future use. i want to continuously change my variable on demand in my other component.
thanks for your advice

httpClient.get is undefined when using dynamic function/observable references

So I asked a question a few days ago and got some headway on a solution, however now I'm stuck at another wall I'm unsure how to get over.
I have two parent components, a shared view/component with an extended base component, and a service all hooked together. The objective is to use the two parent components to drive what data is shown within the shared component. The two parent components use references to service methods passed into the shared component to get the data.
I've reached an issue where my http.get is always undefined no matter what I try. I've instantiated it like I do in my other services but I've had no luck. I suspect this is caused by how i pass in my service references. Code below:
Parent Component Code:
// PARENT COMPONENT
myData$: Observable<myType>;
searchMethod: Function;
constructor(private myService){
this.myData$ = this.myService.myData$;
this.searchMethod = this.myService.searchData;
}
// PARENT COMPONENT HTML
<app-shared-component
[myData$] = "myData$"
[searchMethod]="searchMethod">
</app-shared-component>
Shared Component Code:
export class MySharedComponent extends BaseComponent<MyType> implements OnInit {
#Input() myData$: Observable<myType>;
#Input() searchMethod: Function;
constructor() { super(); }
ngOnInit(): void {
this.data$ = this.myData$;
}
search(): void {
this.searchMethod().subscribe(//do something);
}
Base Component Code:
#Input data$: Observable<T>;
ngOnInit(): void {
this.data$.subscribe((response: T) => //do something);
super.ngOnInit();
}
Service Code:
private myDataSubject = new BehaviorSubject<MyType>(new MyType());
get myData$(): Observable<MyType> {
return this.myDataSubject.asObservable();
}
constructor(private http: HttpClient) { }
searchData(): Observable<void> {
return new Observable<void>(observer => {
this.http.get<MyType>(
'http://myuri'
).subscribe(
response => {
// do something
},
() => observer.error(),
() => observer.complete()
);
});
}
It looks like you're losing the context of your service when you set this.searchMethod = this.myService.searchData in your parent component. It should work if you change searchData() { to an arrow function: searchData = (): Observable<void> => {.

Angular 4: How to watch an object for changes?

ETA: I know that there are various ways to watch my form for changes. That is not what I am trying to do. As the title says, I am asking how to watch for changes to an object. The app shown below is for illustration purposes only. Please answer the question that I have asked. Thanks!
I have this simple app:
import { Component, OnInit } from '#angular/core';
export class Customer {
firstName: string;
favoriteColor: string;
}
#Component({
selector: 'my-app',
template: `
<div *ngIf="customer">
<input type="text" [(ngModel)]="customer.firstName">
<input type="text" [(ngModel)]="customer.favoriteColor">
</div>
`
})
export class AppComponent implements OnInit {
private customer: Customer;
ngOnInit(): void {
this.customer = new Customer();
// TODO: how can I register a callback that will run whenever
// any property of this.customer has been changed?
}
}
Note the TODO. I need to register a callback that will run whenever any property of this.customer has been changed.
I cannot use ngChange on the inputs. I need to subscribe directly to changes on the model. The reasons pertain to my use case, and aren't worth going into here. Just trust me that this isn't an option.
Is this possible? I've done a lot of Googling, but I've come up dry.
Angular usually uses injected into constructor KeyValueDiffers class.
For your case it could look like:
import { KeyValueChanges, KeyValueDiffer, KeyValueDiffers } from '#angular/core';
export class Customer {
firstName: string;
favoriteColor: string;
}
#Component({
selector: 'my-app',
templateUrl: `./app.component.html`
})
export class AppComponent {
private customerDiffer: KeyValueDiffer<string, any>;
private customer: Customer;
constructor(private differs: KeyValueDiffers) {}
ngOnInit(): void {
this.customer = new Customer();
this.customerDiffer = this.differs.find(this.customer).create();
}
customerChanged(changes: KeyValueChanges<string, any>) {
console.log('changes');
/* If you want to see details then use
changes.forEachRemovedItem((record) => ...);
changes.forEachAddedItem((record) => ...);
changes.forEachChangedItem((record) => ...);
*/
}
ngDoCheck(): void {
const changes = this.customerDiffer.diff(this.customer);
if (changes) {
this.customerChanged(changes);
}
}
}
Stackblitz Example
One more option is using setter on properties that you want to check.
See also
http://blog.mgechev.com/2017/11/14/angular-iterablediffer-keyvaluediffer-custom-differ-track-by-fn-performance/
I need to subscribe directly to changes on the model.
Then you need to listen to model changes with ngModelChange
Template:
<input type="text" (ngModelChange)="doSomething($event)" [ngModel]="customer.firstName">
Class:
doSomething(event) {
console.log(event); // logs model value
}
DEMO
You can't watch changes in an object. Its not angular 1 there are no watchers here. Another solution will be via observables.
use form
<form #f="ngForm">
<input type="text" name="firstName" [(ngModel)]="customer.firstName">
<input type="text" name="favoriteColor" [(ngModel)]="customer.favoriteColor">
</form>
in code
#ViewChild('f') f;
ngAfterViewInit() {
this.f.form.valueChanges.subscribe((change) => {
console.log(change)
})
}
You could use custom setters to trigger your callback:
class Customer {
private _firstName: string
get firstName(): string {
return this._firstName
}
set firstName(firstName: string) {
this.valueChanged(this._firstName, firstName)
this._firstName = firstName
}
private _lastName: string
get lastName(): string {
return this._lastName
}
set lastName(lastName: string) {
this.valueChanged(this._lastName, lastName)
this._lastName = lastName
}
valueChanged: (oldVal, newVal) => void
constructor (valueChanged?: (oldVal, newVal) => void) {
// return an empty function if no callback was provided in case you don't need
// one or want to assign it later
this.valueChanged = valueChanged || (() => {})
}
}
Then just assign the callback when you create the object:
this.customer = new Customer((oldVal, newVal) => console.log(oldVal, newVal))
// or
this.customer = new Customer()
this.customer.valueChanged = (oldVal, newVal) => console.log(oldVal, newVal)
visit https://github.com/cartant/rxjs-observe. it bases on rxjs and proxy.
import { observe } from "rxjs-observe";
const instance = { name: "Alice" };
const { observables, proxy } = observe(instance);
observables.name.subscribe(value => console.log(name));
proxy.name = "Bob";
We are tasked with converting an Angular 1.x app to Angular 9. It's an application with ESRI maps, so we have some neat tools that the ESRI framework brought to the table. ESRI has watchUtils that do a whole lot more than just watch for changes.
But I missed Angular 1's simple $watch. Besides, we create Entities and Models in our application, and we may need to observe these from time to time.
I created an abstract class called MappedPropertyClass. It uses a Map<string, any> to map class properties, which allows me to easily implement toJSON and other utility functions.
The other Map this class has is _propertyChangeMap: Map<string, EventEmitter<{newvalue,oldvalue}>;
We also have a function called... $watch, which takes a string and a callback function.
This class can be extended by Entities as well as components or services
I'm happy to share, the caveat is your properties must look like this:
public get foo(): string {
return this._get("foo");
}
public set foo(value:string) {
this._set("foo", value);
}
--------------------------------------------------------------------
import { EventEmitter } from '#angular/core';
export abstract class MappedPropertyClass {
private _properties: Map<string, any>;
private _propertyChangeMap: Map<string, EventEmitter<{ newvalue, oldvalue }>>;
protected _set(propertyName: string, propertyValue: any) {
let oldValue = this._get(propertyName);
this._properties.set(propertyName, propertyValue);
this.getPropertyChangeEmitter(propertyName).emit({ newvalue:
propertyValue, oldvalue: oldValue });
}
protected _get(propertyName: string): any {
if (!this._properties.has(propertyName)) {
this._properties.set(propertyName, undefined);
}
return this._properties.get(propertyName);
}
protected get properties(): Map<string, any> {
var props = new Map<string, any>();
for (let key of this._properties.keys()) {
props.set(key, this._properties.get(key));
}
return props;
}
protected constructor() {
this._properties = new Map<string, any>();
this._propertyChangeMap = new Map<string, EventEmitter<{ newvalue: any,
oldvalue: any }>>();
}
private getPropertyChangeEmitter(propertyName: string): EventEmitter<{
newvalue, oldvalue }> {
if (!this._propertyChangeMap.has(propertyName)) {
this._propertyChangeMap.set(propertyName, new EventEmitter<{ newvalue,
oldvalue }>());
}
return this._propertyChangeMap.get(propertyName);
}
public $watch(propertyName: string, callback: (newvalue, oldvalue) => void):
any {
return this.getPropertyChangeEmitter(propertyName).subscribe((results) =>
{
callback(results.newvalue, results.oldvalue);
});
}
}

ngFor doesn't fires after update depending variable in Angular2

I have 2 components: CommandListComponent and CommandLineComponent. Inside of a CommandListComponent template i handle a click event on a text string:
CommandListComponent template:
<li *ngFor="#command of commandList" class="b-command-list__command"><span (click)="checkCommand(command)" class="b-command-list__text">{{command}}</span></li>
commandlist.component.ts
import {CommandLineComponent} from "./commandline.component";
...
export class CommandListComponent {
commandLineComponent: any;
constructor(private _commandLine: CommandLineComponent) {
this.commandLineComponent = _commandLine;
}
checkCommand(command: string): void {
this.commandLineComponent.add(command);
}
}
When click is fired i pass choosen command to add method of a CommandLineComponent:
export class CommandLineComponent {
commands: string[] = [];
add(command: string): void {
if (command) this.commands.push(command);
console.log(this.commands);
}
}
And within a template of a CommandLineComponent i print a list of a commands with *ngFor:
<li *ngFor="#command of commands" class="b-command-textarea__command">{{command}}</li>
But *ngFor doesn't fires when i choose a command and commands array of a CommandLineComponent updated. So, data binding is not working. commands array updates successfully:
Thank you for help.
The problem is the way you reference the commandLineComponent component. If there is a relation between them you could use the ViewChild decorator
class CommandListComponent {
#ViewChild(CommandLineComponent)
commandLineComponent: any;
(...)
}
If not, you need to use a shared service to share the commands list between these two components. Something like that:
export class CommandService {
commands:string[] = [];
commandAdded:Subject<string> = new Subject();
add(command: string): void {
if (command) {
this.commands.push(command);
this.commandAdded.next(command);
}
console.log(this.commands);
}
}
You need to define the service when bootstrapping your application and both components can inject it.
class CommandListComponent {
constructor(private commandService:CommandService) {
}
}
checkCommand(command: string): void {
this.commandService.add(command);
}
The CommandLineComponent component will be notified of a new command like this and can update the view accordingly:
class CommandLineComponent {
constructor(private commandService:CommandService) {
this.commandService.commandAdded.subscribe(command => {
// Update the list displayed in the component...
});
}
}

Angular 2 passing data to another component

I am new to Angular 2 and am still figuring things out.
I have two components:
1) List Component
This lists all the products in a store and does other functions
#Component({
selector :'home-list',
providers :[CartService,CartStatus]
})
#View({
templateUrl :'/app/views/list-product.partial.html'
})
export class HomeList{
title: string;
products : ProductInterface[];
cart;
private isFetching: boolean = false;
constructor(
private _router : Router,
private _dataService: DataService,
private _cartService: CartService,
private _cartStatus: CartStatus
){}
ngOnInit(){
this.title = 'Featured Products';
this.getfeaturedproducts();
}
getfeaturedproducts(){
this._dataService.getFeatured().subscribe(
products => {
this.products = products;
this.isFetching = true;
}
)
}
gotoDetail(slug:string) {
console.log(slug);
this._router.navigate(['ProductsDetail', {productslug:slug}]);
return false;
}
getCart(){
this._cartService.getCartContent().subscribe(
res => {
this.cart = res;
console.log(res.result)
}
);
}
addtoCart(id:number){
this._cartService.addToCart(id).subscribe(
res => console.log(res)
this._cartStatus.updateCart(id);
//want to pass the data to CartStatus Component
)
}
}
2) CartUpdate Component which shows no of items in cart
#Component({
selector : 'cart-status'
})
#View({
template :'{{cart}}'
})
export class CartStatus{
cart;
updateCart(id:number){
this.cart = id;
}
}
The problem is that I have not been able to pass the id or any value to the CartStatus view. When I console.log the id on updateCart it shows accurate value but does not reflect on the view of the CartStatus.
Am I doing something wrong here??
From your code, what I can figure out is CartStatus is a component so,
providers :[CartService,CartStatus]
should be,
providers : [CartService]
directives : [cardStatus]
Now, check this official docs for communication between components,
https://angular.io/docs/ts/latest/cookbook/component-communication.html
I am also creating a large scale shopping cart using MEAN stack with Angular 2
As for me adding items to a cart is not so easy for me to implement. User has items(products, qty, total price), User has orderedItems(items, totalQty, totalPrice), User - logged in, all session cart items are cleared and added to user items and so on. I implemented my cart with MongoStore (session with expiration date) so that when you refresh the page, the items are still stored in the cart. MongoStore is a long subject and I can not help you here. There is a tutorial in Utube(angular 1 by MAX from Udemy and that's how I learned it). Honestly, I do not know how to store a "class object" in a LocalStorage. For learning purposes of how to add items to a cart is to use array.
Lets' create a Cart class:
export Class {
public product: Product;
public qty: number) {}
}
Create a service: cart.service.ts
import { Injectable } from '#angular/core';
import { Cart } from './cart';
import { Product } from './product';
#Injectable()
export class CartService {
private carts: Carts[];
getCart() {
return this.carts;
}
addToCart(product: Product) {
for(let i=0; i<this.carts.length; i++) {
if(this.carts[i].product.productId == product.productId) {
this.carts[i].quantity = this.carts[i].quantity + 1;
return;
}
let cart = new Cart(product, 1);
this.carts.push(cart;
}
}
getCartTotal(){
let total = 0;
for(let i=0; i<carts.length; i++) {
total = total + this.carts[i].quantity;
}
return total;
}
emptyCart() {
this.carts = [];
}
}
In your cart component:
export class CartStatus implements OnInit {
carts: Cart[];
constructor(private: _cartService: CartService) {
ngOnInit(){
this.carts = this._cartService.getCart(); //no subsribe() here since is not an http request.
}
Please don't forget to add CartService to to your boot file. I hope this helps. I can also help you with deleteItem, updateItem, and getTotalCost. Thanks and Happy coding..

Categories

Resources