How to clear last #Input variable values ? Angular 2+ - javascript

I use changeDetection strategy and it works ok but when my component is destroy when I come back I have the last value saved.
Example I props 3 values to child component and went to another component when i try again to prop data i see my last values ..
Example props values 1 , 2 and 3.
I see my last values 3.
How to destoy it ?
Check code and parent component:
<div class="row">
<app-poi-address [poiPin]="pinedAddress"></app-poi-address>
</div>
this.pinedAddress = $event;
Child component:
#Component({
changeDetection: ChangeDetectionStrategy.OnPush
})
export class PoiAddressComponent implements OnInit, OnChanges {
#Input () poiPin!: Object
public allPin: any[] = [];
constructor() { }
ngOnInit(): void {
console.log('datica' , this.poiPin);
}
ngOnChanges(): void {
console.log('HelloComponent: Change Detection count = ' , this.poiPin);
this.allPin.push(this.poiPin)
}
}
<div *ngFor="let d of allPin ">
<p> {{ d?.coords?.lat }} </p>
</div>
Saved last values. I want to clear all array of allPin...

This would clear your all pin variable on when recreating the component after desctruction.
ngOnInit(): void {
allPin = [];
console.log('datica' , this.poiPin);
}

Related

How to get a dynamically generated element in Angular without querySelector?

I am currently creating my own toastr service as seen in the GIF below
What I want to achieve
https://stackblitz.com/edit/angular-ivy-tgm4st?file=src/app/app.component.ts
But without queryselector. From what i have read, you should not be using queryselector for retrieving elements in the DOM in angular
The issue
Whenever I click the CTA button I add a toast element to an array of toasts which the component is subscribed to and utilizes to update the DOM.
The toasts are generated like this:
export class ToastComponent implements OnInit {
constructor(private toast: ToastService, protected elementRef: ElementRef) {}
toasts = this.toast.Toasts;
<div
class="toast-wrapper wobble-animation"
*ngFor="let t of toasts.value"
(click)="DestroyToast(t, $event)"
What I want
I want to add an eventlistener to the toast whenever 'animationend' to destroy the HTML element. I already do this by when clicking with this line of code:
DestroyToast(element, event): void {
event.target.classList.remove('wobble-animation');
event.target.classList.add('slide-out-animation');
event.target.addEventListener('animationend', () => {
this.toasts.value.splice(this.toasts.value.indexOf(element), 1);
});
}
My initial thought was to subscribe to the array and use that as an eventlistener for when something is pushed. I would then use a function to fetch the latest toast and add another eventlistener, the 'animationend' one.
I tried the method like this:
ngOnInit(): void {
this.toast.Toasts.subscribe((args) => {
this.UpdateToasts();
});
}
UpdateToasts() {
let toastElements = document.querySelectorAll('.toast');
console.log(toastElements);
}
But unfortunately it is too slow and always returns null on the first event.
I think that I have read that using querySelector in angular is generally bad practice. So the question is:
How to get a dynamically generated element in Angular without querySelector?
FULL CODE
Toast.Component.ts
import { ToastService } from './../../services/toast.service';
import { toast } from './toast.model';
import { Component, OnInit, ElementRef } from '#angular/core';
import { Observable } from 'rxjs';
#Component({
selector: 'app-toast',
templateUrl: './toast.component.html',
styleUrls: ['./toast.component.scss'],
})
export class ToastComponent implements OnInit {
constructor(private toast: ToastService, protected elementRef: ElementRef) {}
toasts = this.toast.Toasts;
ngOnInit(): void {
this.toast.Toasts.subscribe((args) => {
this.UpdateToasts();
});
}
ngOnDestroy() {
this.toasts.unsubscribe();
}
DestroyToast(element, event): void {
event.target.classList.remove('wobble-animation');
event.target.classList.add('slide-out-animation');
event.target.addEventListener('animationend', () => {
this.toasts.value.splice(this.toasts.value.indexOf(element), 1);
});
}
UpdateToasts() {
let toastElements = document.querySelectorAll('.toast');
console.log(toastElements);
}
}
Toast.Component.html
<div class="toast-container">
<div
class="toast-wrapper wobble-animation"
*ngFor="let t of toasts.value"
(click)="DestroyToast(t, $event)"
>
<div
class="toast default"
[ngClass]="{ 'slide-out-animation': t.TimeLeft < 1 }"
>
<div class="notification-count" *ngIf="t.Count > 1">
{{ t.Count }}
</div>
<div class="content-container">
<p class="title">
{{ t.Title }}
</p>
<p class="content">{{ t.Content }}</p>
</div>
<span class="progress">
<span
class="real-progress"
[ngStyle]="{ 'width.%': t.PercentageCompleted }"
></span>
</span>
</div>
</div>
</div>
Toast.Service.ts
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { toast } from '../components/toast/toast.model';
#Injectable({
providedIn: 'root',
})
export class ToastService {
public Toasts = new BehaviorSubject<Array<object>>([]);
constructor() {}
Toast(Title: string, Message?: string, Style?: string, Timer?: number) {
const toastModel = new toast({
Title: Title,
Content: Message,
Timer: Timer,
Style: Style,
TimeLeft: Timer,
Count: 1,
PercentageCompleted: 100,
});
this.AddToast(toastModel);
}
private AddToast(toast: toast) {
const currentArr = this.Toasts.value;
const updatedToast = [...currentArr, toast];
let timer = setInterval(function () {
toast.PercentageCompleted = toast.TimeLeft / (toast.Timer / 100);
toast.TimeLeft = toast.TimeLeft - 10;
if (toast.TimeLeft <= 0 || !toast.TimeLeft) {
clearInterval(timer);
}
}, 10);
this.Toasts.next(updatedToast);
}
}
Link to website with live code ModernnaMedia
I'm not 100% sure I understood you correctly, there seem to be two animationend events going on.
I want to add an eventlistener to the toast whenever 'animationend' to destroy the HTML element.
You can bind that directly in the template:
<div
*ngFor="let toast of toasts"
#toastEl
(animationend)="DestroyToast(toastEl)"
class="toast">
</div>
DestroyToast(toastEl: HTMLElement) {
// …
}
Like mentioned by others already, using ViewChildren would be the "Angular" way to do it, instead of queryselector. We can also with ViewChildren subscribe to changes of the querylist we are listening to! I think that is probably suitable for your code...
So first, attach a ref to the toasts, here I just call it myToasts:
<div
#myToasts
class="toast default"
[ngClass]="{ 'slide-out-animation': t.TimeLeft < 1 }"
>
OK, now declare the querylist in the component:
#ViewChildren('myToasts') myToasts: QueryList<ElementRef>;
Now you can simply subscribe to the changes in AfterViewInit and do whatever you need to do with the elements:
ngAfterViewInit() {
this.myToasts.changes.subscribe(toasts => {
console.log('Array length: ', toasts.length);
console.log('Array of elements: ', toasts.toArray())
})
}
if you add rxjs delay function after your observable variable like below
this.toast.Toasts.pipe(delay(0)).subscribe(()=>{this.UpdateToasts();})
you will not get null reference error.
and if you don't want to use queryselector you can use angular viewchildren
for more information visit angular documentation site.
https://angular.io/api/core/ViewChildren

Angular - How to get data from subscribed service few times and not overwrite previous results

I have a service which has following method:
room-service.ts
#Injectable()
export class RoomService {
private readonly _equipment: BehaviorSubject<EquipmentDto[]> = new BehaviorSubject([]);
public equipment$ = this._equipment.asObservable();
getEquipmentForRoom(roomId: number) {
this.restService.getEquipmentForRoom(roomId).subscribe(res => {
this._equipment.next(res);
});
}
room-component.ts:
#Component()
export class RoomsComponent implements OnInit {
#Input() room: RoomEntity;
equipment$: Observable < EquipmentDto[] > ;
equipmentList: Array < EquipmentDto > ;
constructor(private equipmentService: EquipmentService) {}
ngOnInit() {
this.equipment$ = this.equipmentService.equipment$;
this.equipmentService.getEquipmentForRoom(this.room.id);
this.equipment$.subscribe(items => {
this.equipmentList = items;
});
room-component.html
<div *ngFor="let eq of room.equipmentList">
<!-- list my equipment here -->
</div>
Now I have a parent component which contains multiple Room Components (those are added programmatically based on the amount of rooms). Anyway, list of equipment is the same for each of the rooms. It looks like once subscribed, the data in first components is overwritten by the component created as the last one.
My question is, how can I get a proper data for each of the rooms using the observable from my service?
You can use this approach with single BehaviorSubject only when your data is the only source of this data.
Instead, you can change your getEquipmentForRoom(roomId: number) like this:
getEquipmentForRoom(roomId: number) {
return this.restService.getEquipmentForRoom(roomId);
}
And then subscribe to it in the compoment:
this.equipmentService.getEquipmentForRoom(this.room.id).subscribe(items => {
this.equipmentList = items;
});
And I agree with Alexander, this component should be dumb as possible.
try to build pipes instead of subscriptions
#Injectable()
export class RoomService {
// return an observable.
getEquipmentForRoom$(roomId: number) {
return this.restService.getEquipmentForRoom(roomId);
}
#Component()
export class RoomsComponent implements OnInit {
#Input() room: RoomEntity;
equipment$: Observable<EquipmentDto[]>;
constructor(private equipmentService: EquipmentService){}
ngOnInit() {
// simply share observable.
this.equipment$ = this.equipmentService.getEquipmentForRoom$(this.room.id);
});
<div *ngFor="let eq of equipment$ | async"> <!-- add async here -->
<!-- list my equipment here -->
</div>
You can fetch the data once in your parent and pass the data to your child components.
That child component (RoomsComponent) should be dumb and not doing requests
Adding onto Alexander’s answer.
The problem is that you are subscribing to the same BehaviourSubject for all the components and the components are taking the latest roomData that is emitted by the BehaviourSubject, which would be the last RoomComponent.
It would work if the BehaviuorSubject holds all the rooms which are fetched by the parent component, and passing each room data to each RoomComponent using #Input.

Two Way Binding on an Angular 2+ Component

I have an Ionic application where I have created a component to show some data of an object. My problem is that when I update the data in the parent that hosts the component the data within the component does not update:
my-card.component.ts
#Component({
selector: 'my-card',
templateUrl: './my-card.html'
})
export class MyCard {
#Input('item') public item: any;
#Output() itemChange = new EventEmitter();
constructor() {
}
ngOnInit() {
// I do an ajax call here and populate more fields in the item.
this.getMoreData().subscribe(data => {
if (data.item){
this.item = data.item;
}
this.itemChange.emit(this.item);
});
}
}
my-card.html
<div class="comment-wrapper" *ngFor="let subitem of item.subitems">
{{subitem.title}}
</div>
And in the parent I use the component like this:
<my-card [(item)]="item"></my-card>
And the ts file for the parent:
#IonicPage()
#Component({
selector: 'page-one',
templateUrl: 'one.html',
})
export class OnePage {
public item = null;
constructor(public navCtrl: NavController, public navParams: NavParams) {
this.item = {id:1, subitems:[]};
}
addSubItem():void{
// AJAX call to save the new item to DB and return the new subitem.
this.addNewSubItem().subscribe(data => {
let newSubItem = data.item;
this.item.subitems.push(newSubItem);
}
}
}
So when I call the addSubItem() function it doesnt update the component and the ngFor loop still doesnt display anything.
You are breaking the object reference when you are making the api request. You are assigning new value, that is overwriting the input value you get from the parent, and the objects are no longer pointing to the same object, but item in your child is a completely different object. As you want two-way-binding, we can make use of Output:
Child:
import { EventEmitter, Output } from '#angular/core';
// ..
#Input() item: any;
#Output() itemChange = new EventEmitter();
ngOnInit() {
// I do an ajax call here and populate more fields in the item.
this.getMoreData(item.id).subscribe(data => {
this.item = data;
// 'recreate' the object reference
this.itemChange.emit(this.item)
});
}
Now we have the same object reference again and whatever you do in parent, will reflect in child.
If the getMoreData method returns an observable, this code needs to look as follows:
ngOnInit() {
// I do an ajax call here and populate more fields in the item.
this.getMoreData().subscribe(
updatedItem => this.item = updatedItem
);
}
The subscribe causes the async operation to execute and returns an observable. When the data comes back from the async operation, it executes the provided callback function and assigns the item to the returned item.
You declared item with #Input() decorator as:
#Input('item') public item: any;
But you use two-way binding on it:
<my-card [(item)]="item"></my-card>
If it is input only, it should be
<my-card [item]="item"></my-card>
Now if you invoke addSubItem() it should display the new added item.
this.item = this.getMoreData();
The getMoreData() doesn't make sense if you put it in your card component as you want to use the item passed via #Input()
Your component interactions are a little off. Check out the guide on the Angular docs (https://angular.io/guide/component-interaction). Specifically, using ngOnChanges (https://angular.io/guide/component-interaction#intercept-input-property-changes-with-ngonchanges) or use a service to subscribe and monitor changes between the parent and the child (https://angular.io/guide/component-interaction#parent-and-children-communicate-via-a-service).

Javascript Angular 4 Change ngClass from another Component

I currently have this code in my app.component.ts
app.component.html
<div [ngClass]="myclass">
...rest of the content here
</div>
This I have the this:
<button (click)="changeClass('myFavClass')">Change Class to myFavClass</div>
app.component.ts
export class AppComponent {
myclass: string;
changeClass(myclass) {
this.myclass = myclass;
}
}
Now, all this works fine BUT I now want to put the triggering button on another component.
If I put this on another component:
<button (click)="changeClass('myFavClass')">Change Class to myFavClass</div>
How can I get it to change the class?
There are two ways you can do this you can use output with an EventEmit
Or you can set up a service that monitors the changes to a variable and use that as the control point for the change.
Personally, I use services for this instance as its easier to manage the code and its flow.
This answer has all the code in you need to look at.
Changing a value in two different components at the same time Angular 2
Hope that helps
There are at least two options. Subject and Observable or if this another component is a parent you can use #Input.
Subject and Observable method:
angular guide Highly recommended to read whole page.
Some component
export class SomeComponent {
constructor(private ClassService: ClassService) { }
private changeClass(class) {
this.ClassService.changeClass(class);
}
}
Another Component
export class AnotherComponent implements OnInit, OnDestroy {
constructor(private ClassService: ClassService) { }
private class: string = "";
private subscribtion: Subscribtion;
ngOnInit(): void {
this.Subscribtion = this.ClassService.someClass$.subscribe(
(class) => { this.class = class; }
)
}
ngOnDestroy(): void {
this.Subscribtion.unsubscribe();
}
}
Service
#Injectable();
export class ClassService{
constructor() { }
private someClassSource= new Subject<string>();
someClass$= this.someClassSource.asObservable();
changeClass(class) {
this.someClassSource.next(class);
}
}
taken from my answer
#Input method:
angular guide
This is very simple, when you click button changeClass method will change elClass which will be passed to another component by #Input decorator, every change of #Input will cause a detect changes which will detect that value has changed so class will change to myClass.
Parent component
parent.component.html
<another-component [elementClass]="elClass"></another-component>
<button (click)="changeClass('myClass')">change class<button>
parent.component.ts
export class ParentComponnet {
private elClass: string = "";
changeClass(class: string) {
elClass = class;
}
}
Another component (must be child component)
another.component.html
<div [ngClass]="elementClass">
another.component.ts
export class AnotherComponent {
#Input() elementClass: string;
}
There is also Child to Parent interaction via #Output (emitting event) angular guide

Passing a parameter through output binding AngularJS

I'm having to following setup with AngularJs 1.5.8, using Typescript:
I have a parent component wich contains a catalog (2D array of items). From where I pass each list in the catalog to a child component:
...
export class ParentController {
private catalog = []; //contains a list of lists
...
private printCallback(item: any): void {
console.log(item);
}
}
and the template:
<div ng-repeat="list in $ctrl.catalog">
<child content="list" callback="$ctrl.printCallback(item)"></child>
</div>
Now in the child component I iterate again over every item in the list. And whenever I click on an item from the list, I want the parent to know the item I clicked on:
export class ChildComponent implements IComponentOptions {
template: any = require('./child.component.html');
public bindings: any = {
content: '<',
callback: '&'
};
controller: any = ChildController;
}
export class ChildController {
public content: Array<any>;
public callback;
...
public onTrigger(item: any): void {
this.callback(item);
}
}
And the child template:
<div ng-repeat"item in $ctrl.content">
<button ng-click="$ctrl.onTrigger(item)">{{item.name}}</button>
</div>
Now I can't seem to print the item in the printCallBack() of my parent component. I don't know exactly what I'm doing wrong cause the only thing that's printing is undefined.
I've looked around and couldn't find a working solution. So I hope you can help me.
While callback, do it like: this.callback({item : item});, pass as object.

Categories

Resources