Angular2 ngOnChanges not firing when input is array - arrays

I'm using a ParentComponent that sets inputs to a ChildComponent.
If the changed input is number, the ngOnChanges hook fires, but if it's an array, it does not.
Can someone tell me what am I doing wrong, or how to make ngOnChanges firing when the array is changed?
Thank you.
child ts:
export class ChildComponent implements OnInit {
#Input() num = 0;
#Input() arr: Array<string> = [];
constructor() { }
ngOnInit(): void {
}
ngOnChanges() {
console.log('input changed');
}
}
parent ts:
export class ParentComponent implements OnInit {
constructor() { }
num = 0;
arr : Array<string> =[];
ngOnInit(): void {
}
changeNumber() {
this.num = this.num + 1;
}
changeArray() {
this.arr.push('some value');
}
}
parent html:
<button (click)="changeNumber()">change num</button>
<button (click)="changeArray()">change array</button>
<app-child [num]="num" [arr]="arr"></app-child>

That's because num is a primitive data type, whereas an array is not. An array is basically an object, where the reference is stored in the variable arr. When you "push" a new entry to the array, the reference itself is not changed.
This is the reason ngOnChange does not fire. If you want to create a new reference to the array and "push" a value to it, you should use the following code:
this.arr = [...this.arr, 'some value'];

ngOnChanges can't detect array changes since the array reference remain as it is even when the content gets changed. instead, you can use a setter method
private _arr: Array<string> = [];
#Input() set arr(data) {
this._arr= data;
console.log(this._arr);
};

Related

Only the first object from the Angular array is retrieved

I need your help. I'm practicing Angular and trying to pass data between components using rxjs. I have 2 components and a service. I'm getting data from an API. My code works, but it's not correct.
I manage to get the object from the array and transfer it, but unfortunately, only the first element of the array is extracted. Please tell me how to get different objects by clicking on the button, and not just the first one? Thank you very much
Service
export class CheckboxServiceService {
public allSelectedProducts: BehaviorSubject<ProductModel[]> = new BehaviorSubject<ProductModel[]>([])
sendProductToList(product: any) {
this.allSelectedProducts.next(product);
}
}
ComponentSender
export class ProductsListApiComponent implements OnInit {
products: ProductModel[];
productDifferent: any;
constructor(private apiService: ApiService , private checkboxService: CheckboxServiceService) { }
ngOnInit(): void {
this.apiService.getAllProducts().subscribe(value => this.products = value);
}
addProductToList() {
let currentValue = this.checkboxService.allSelectedProducts.getValue();
this.productDifferent = this.products.find(element => element.id);
currentValue.push(this.productDifferent);
this.checkboxService.sendProductToList(currentValue);
}
}
ComponentReceiver
export class ProductsRecieverComponent implements OnInit {
public selectedProductsList: ProductModel[] = [];
constructor(private checkboxService: CheckboxServiceService) { }
ngOnInit(): void {
this.checkboxService.allSelectedProducts.subscribe(value => {
this.selectedProductsList = value;
})
}
}
The problem lies in the sendProductToList method. Indeed, you're only emitting one product, rather than adding a new product to the product list.
sendProductToList(product: any) {
this.allSelectedProducts.next(product);
}
By calling next passing a single product, you're always emitting a single product, they won't "accumulate".
Besides that, most times you don't want to expose a BehaviorSubject as a public member of your service, but rather an Observable, since the first supports read and write operations while the latter is readonly, preventing clients from using your service the wrong way.
Hence, this should fix it.
export class CheckboxService {
public products$: Observable<Product[]>;
private productsSubject: BehaviorSubject<Product[]>;
constructor() {
this.productsSubject = new BehaviorSubject([]);
this.products$ = this.productsSubject.asObservable();
}
sendProductToList(product: Product) {
const products = this.productsSubject.value;
const updatedList = [...products, product);
this.productsSubject.next(updatedList);
}
}

Angular: handle object changes in ngFor, force print

I have my app.component with a list of objects
class Hero {
alias: string;
constructor(public firstName: string,
public lastName: string) {
}
}
class AppComponent {
...
heroes: Hero[] = [
new Hero("foo", "bar")
];
...
onHeroChange($event: Hero, index: number): void {
this.heroes[index] = $event;
}
<div *ngFor="let hero of heroes; let index=index">
<hero [hero]="hero" (heroChange)="onHeroChange($event, index)"></hero>
</div>
The HeroComponent is
export class HeroComponent {
#Input()
set hero(newValue: Hero) {
this._hero = newValue;
this.showAlias = !!newValue.alias;
}
get hero(): Hero {
return this._hero;
}
#Output() heroChange: EventEmitter<Hero> = new EventEmitter<Hero>();
showAlias: boolean = !1;
private _hero: Hero;
//
#HostListener('click')
onHeroClick(): void {
this.hero.alias = `alias_${+new Date()}`;
console.info('HERO TO EMIT', this.hero);
this.heroChange.emit(this.hero);
}
}
My problem is that even by assigning the changed hero in app.component, the set hero inside hero.component is not called, so showAlias in the example is not updated and I don't see the alias in the hero.component.
Do I need to force the ngFor by assigning the entire array?
Maybe a workaround could be removing the object from the array and then inserting again?
Sounds like useless computation though.
Note: this is just an example, it's not what I'm really working on, so something like
Update the showAlias prop in the onHeroClick method
or
Assign hero in the hero.component
unfortunately don't solve the issue. I need the changes to be on the outside because other stuff happens.
Could be another option changing the detection to onPush and marking for check manually?
Blitz ==> https://stackblitz.com/edit/angular-ivy-kpn3ds
You're not setting a new hero, you're just modifying a property on the existing one:
this.hero.alias = `alias_${+new Date()}`;
That doesn't fire the setter. Change the line like this:
this.hero = {...this.hero, alias: `alias_${+new Date()}`};

Angular 2: Change Detection of array inside the object

I have an object as follows which comes through #Input.
#Input() data;
//**
{
"class_a":["John","Harr y"],
"class_b":["Joseph","Phlip","David"],
"class_c":[]
}
**//
I need to detect the changes if data added or removed in class_a or class_b but im only getting change detection if values of objects are string.
Since the keys in my object are dynamic i couldn't iterate the object and create Iterable differs.
Is there anyway to detect changes of array inside the object.
My Implementation:
constructor(private differs: KeyValueDiffers) {
this.keyValueDiffer = differs.find({}).create();
}
ngDoCheck() {
let changes = this.keyValueDiffer.diff(this.data[this.component.factedBindKey]);
if (changes) {
console.log('Changes detected');
}
}
you can test like this
constructor(private cd: ChangeDetectorRef) {
}
ngOnChanges() {
let actualData =this.data
this.mymethod(actualData);
}
and call this line where you want to access that actual data like this
mymethod(data){
this.cd.detach();
//write main logic
}

Child component cant get array from its parent after removing an element.

I'm a beginner to angular. I've got small ap with 3 components.
One component is just input and button which sends input value to the parent, and parent adds the incoming input to an array which is send forward to child where I want to print out all objects of array. Whenever I run function remove() and try to add another element afterwards by add() it is added only to listOfTasks but it's not added to taskList. Can someone explain why?
Component with input:
export class InputTaskComponent implements OnInit {
#Output('newTask') newTask = new EventEmitter<string>();
input: string;
constructor() { }
ngOnInit() {
}
add() {
this.newTask.emit(this.input);
this.input='';
}
Main component:
export class AppComponent {
addedTask: string;
listOfTasks: string[]=[];
doneTask:string[]=[];
constructor() {
}
receiveNewTask(event) {
this.addedTask=event;
this.listOfTasks.push(this.addedTask);
}
receiveDoneTask(event) {
this.doneTask.push(event);
}
}
Second child:
export class AddTaskComponent implements OnInit {
#Input('tasksFromInput') taskList: string[];
#Output('doneTask') doneTask = new EventEmitter<string>();
constructor() {
}
ngOnInit() {
}
done(task) {
this.doneTask.emit(task);
}
remove(task) {
this.taskList = this.taskList.filter(e => e !== task);
console.log(this.taskList);
}
HTML of main component:
<div>
<div style="float:left; width:300px;">
<app-input-task (newTask)="receiveNewTask($event)">
</app-input-task>
</div>
<div style="float:left; width:300px;">
<app-add-task [tasksFromInput]="listOfTasks" (doneTask)="receiveDoneTask($event)">
</app-add-task>
</div>
<div style="float:left; width:300px;">
<app-done-task [done]="doneTask">
</app-done-task>
</div>
</div>
This is due to how change detection works in Angular. You have arrays in several places and use them as inputs for components.
When you add a task to them you use the push method which adds the element to the array, but the array itself is the same one, basically the reference does not change.
When you want to add an object to the tasks list and trigger the change detection you have to create a new array, for example:
this.listOfTasks = [...this.listOfTasks, this.addedTask];
In this way the example app will work. More info about change detection in Angular here.

Initialize variables in constructor or in declaration

When using ReactJS with TypeScript, is it better to initialize class variables in the constructor or when the class variable is being declared? It works fine either way and the transpiled javascript looks the same either way.
export class MyClass extends React.Component<iProps, {}> {
private myName: string = "Hello";
constructor(props: iProps) {
super(props);
this.myName= "Hello";
}
}
It's exactly the same, for example:
class MyClass1 {
private myName: string = "Hello";
}
Compiles to:
var MyClass1 = (function () {
function MyClass1() {
this.myName = "Hello";
}
return MyClass1;
}());
And:
class MyClass2 {
private myName: string;
constructor() {
this.myName = "Hello";
}
}
Compiles to:
var MyClass2 = (function () {
function MyClass2() {
this.myName = "Hello";
}
return MyClass2;
}());
(code in playground)
As you can see the compiled versions are identical (except for the class names).
So you can use which one you find more elegant.
As for react, you can use the props which are passed to the constructor.
When using es6 style classes with react components then your initial state is assigned in the constructor and not using the getInitialState method.
If your initial state is a function of the props then you'll need to use those in the constructor.

Resources