How to reset ViewContainerRef in angular2 after change Detection? - javascript

So I am working on this app in which I have used ViewContainerRef along with dynamicComponentLoader like below:
generic.component.ts
export class GenericComponent implements OnInit, OnChanges{
#ViewChild('target', { read: ViewContainerRef }) target;
#Input('input-model') inputModel: any = {};
constructor(private dcl: DynamicComponentLoader) { }
ngAfterViewInit() {
this.dcl.loadNextToLocation(DemoComponent,this.target)
.then(ref => {
if (this.inputModel[this.objAttr] === undefined) {
ref.instance.inputModel = this.inputModel;
} else {
ref.instance.inputModel[this.objAttr] = this.inputModel[this.objAttr];
}
});
console.log('Generic Component :===== DemoComponent===== Loaded');
}
ngOnChanges(changes) {
console.log('ngOnChanges - propertyName = ' + JSON.stringify(changes['inputModel'].currentValue));
this.inputModel=changes['inputModel'].currentValue;
}
}
generic.html
<div #target></div>
So It renders the DemoComponentin target element correctly.
but when I am changing the inputModel then I want to reset the view of target element.
I tried onOnChanges to reset the inputModel , its getting changed correctly but the view is not getting updated for respective change.
So I want to know if is it possible to reset the view inside ngOnChanges after the inputModel is updated?
any inputs?

There is no connection between this.inputModel and ref.instance.inputModel. If one changes you need to copy it again.
For example like:
export class GenericComponent implements OnInit, OnChanges{
componentRef:ComponentRef;
#ViewChild('target', { read: ViewContainerRef }) target;
#Input('input-model') inputModel: any = {};
constructor(private dcl: DynamicComponentLoader) { }
ngAfterViewInit() {
this.dcl.loadNextToLocation(DemoComponent,this.target)
.then(ref => {
this.componentRef = ref;
this.updateModel();
});
console.log('Generic Component :===== DemoComponent===== Loaded');
}
updateModel() {
if(!this.componentRef) return;
if (this.inputModel[this.objAttr] === undefined) {
this.componentRef.instance.inputModel = this.inputModel;
} else {
this.componentRef.instance.inputModel[this.objAttr] = this.inputModel[this.objAttr];
}
}
ngOnChanges(changes) {
console.log('ngOnChanges - propertyName = ' + JSON.stringify(changes['inputModel'].currentValue));
this.inputModel=changes['inputModel'].currentValue;
this.updateModel();
}
}

Related

How to show in template property from array of objects

I just try to show the value of a property in the template. But at the moment nothing is shown.
So this is the component:
export class ServerStatusComponent implements OnInit {
snovieCollection: SnovietatusDto = {};
constructor(private snovierStatus: snovieStatusService) {}
ngOnInit(): void {
this.sensorStatus
.getSensorStatuses()
.pipe(
map((data) => {
console.log(data.cameraSensors);
})
)
.subscribe((status) => {
});
}
}
And this is the template:
<p>Camera sensoren</p>
<tr *ngFor="let camera of snovieStatusCollection.key|keyvalue">
test
<h3> {{camera | json}}</h3>
</tr>
So I just want to show in the template the value of key. And the console.log returns this:
0: {key: "T", latestTimestamp: "2021-03-12T10:09:00Z"}
So I don't get any errors. But also nothing is shown.
Two things:
You aren't returning anything from the map. So undefined would be emitted to the subscription. Use tap for side-effects instead.
You aren't assigning the response to this.sensorStatusCollection in the subscription.
export class ServerStatusComponent implements OnInit {
sensorStatusCollection: SensorStatusDto = {};
constructor(private sensorStatus: SensorStatusService) {}
ngOnInit(): void {
this.sensorStatus
.getSensorStatuses()
.pipe(
tap((data) => { // <-- `tap` here
console.log(data.cameraSensors);
})
)
.subscribe((status) => {
this.sensorStatusCollection = status; // <-- assign here
});
}
}
Update: Type
As pointed out by #TotallyNewb in the comments, the type of this.sensorStatusCollection needs to be an array of type SensorStatusDto
export class ServerStatusComponent implements OnInit {
sensorStatusCollection: SensorStatusDto[] = [];
...
}

Not able to bind onchange event to lit-flatpickr element

import 'lit-flatpickr';
import { html, LitElement } from 'lit-element';
class MyApp extends LitElement {
getValue() {
this.shadowRoot.querySelector('#my-date-picker').getValue();
}
getSelectedDate(){
console.log('selected date');
}
render() {
return html<lit-flatpickr id="my-date-picker" altInput altFormat="F j, Y" dateFormat="Y-m-d" theme="material_orange" minDate="2020-01" maxDate="2020-12-31" #change="${this.getSelectedDate}" ></lit-flatpickr>;
}
}
getSelectedDate is not triggering at all. Can you help us how invoke hooks and methods of lit-flatpickr?
https://github.com/Matsuuu/lit-flatpickr
Try this:
(async function() {
await import('https://unpkg.com/lit-flatpickr?module');
const { html, css, LitElement } = await import('https://unpkg.com/lit?module');
class MyApp extends LitElement {
static get styles() {
return css`
lit-flatpickr {
background: pink;
}
`;
}
get picker() {
return this.shadowRoot.querySelector('#my-date-picker')
}
getValue() {
this.picker.getValue();
}
getSelectedDates(e) {
console.log(e);
}
render() {
return html `
<lit-flatpickr id="my-date-picker"
altInput
allowInput
altFormat="F j, Y"
dateFormat="Y-m-d"
theme="material_orange"
minDate="2020-01"
maxDate="2020-12-31"
.onChange="${this.getSelectedDates}"
></lit-flatpickr>
`;
}
}
customElements.define('my-app', MyApp);
})();
<my-app id="app"></my-app>
<lit-flatpickr> (at least the version i got as of this writing) doesn't have any DOM events, you have to pass these custom on* functions instead.

Update ngrx selector inside ngOnChanges

I have a parent component (B) that is getting data from it's parent input (A)
(C) have is (B) child component.
Inside (B) I'm having a selector that gets data from the store.
export class BComponent implements OnChanges {
#Input() branchId;
ngOnChanges() {
this.selectedDataByBranch$ = this.store.pipe(
select(selectBranchDirections, { branchId: this.branchId, dir: this.selectedDirection })
);
this.selectedDataByBranch$.subscribe(selectedDataByBranch => {
this.trainsDatasets = this.getDatasets(selectedDataByBranch);
this.lineChart.data.datasets = this.trainsDatasets ? this.trainsDatasets : [];
this.lineChart.update();
});
directionChanged(event) {
this.selectedDirection = event;
this.selectedDataByBranch$ = this.store.pipe(
select(selectBranchDirections, { branchId: this.branchId, dir: this.selectedDirection })
);
}
}
directionChanged is the Output event that I get from (C)
The issue this that selectedDataByBranch subscription is not getting the new data update triggered inside selectedDataByBranch$
I have also tried this way
directionChanged(event) {
this.selectedDirection = event;
select(selectBranchDirections, { branchId: this.branchId, dir: this.selectedDirection });
}
What i could suggest is. Turn your parameters into a Subject then merge with the store selection, in your directionChanged(event) method provide value to subject.
So your final code will be something like this:
export class BComponent implements OnChanges {
#Input() branchId;
criterias$= new Subject<{branchId:number,dir:number}>;
ngOnChanges() {
this.selectedDataByBranch$ = this.criterias$.pipe(mergeMap(criteria=> this.store.pipe(
select(selectBranchDirections, { branchId: criteria.branchId, dir: this.searchDirection})
)));
this.selectedDataByBranch$.subscribe(selectedDataByBranch => {
this.trainsDatasets = this.getDatasets(selectedDataByBranch);
this.lineChart.data.datasets = this.trainsDatasets ? this.trainsDatasets : [];
this.lineChart.update();
});
this.criterias$.next({branchId:this.branchId,dir:this.sortDirection}); // init first call
}
directionChanged(event) {
this.selectedDirection = event;
this.criterias$.next({ branchId: criteria.branchId, dir: this.searchDirection}});
);
}
}
This stackblitz tries to materialize what i say.

Angular 4 function works only from second time

I am creating a shopping cart in Angular 4 and want to check if a new product prod yet exists in the cartProducts array.
Here's my Component:
Component
import { Component, OnInit } from '#angular/core';
import { Router } from "#angular/router";
import { ProductsService } from '../service/products.service';
#Component({
selector: 'app-store',
templateUrl: './store.component.html',
styleUrls: ['./store.component.css']
})
export class StoreComponent implements OnInit {
itemCount: number;
cartProducts: any = [];
productsList = [];
constructor( private _products: ProductsService ) { }
ngOnInit() {
this.itemCount = this.cartProducts.length;
this._products.product.subscribe(res => this.cartProducts = res);
this._products.updateProducts(this.cartProducts);
this._products.getProducts().subscribe(data => this.productsList = data);
}
addToCart(prod){
this.cartProducts.hasOwnProperty(prod.id) ? console.log("Added yet!") : this.cartProducts.push(prod);
console.log(this.cartProducts)
}
}
My addToCart function which is fired by click works fine, but only from second time.
1 click - we add a product in the empty cartProducts array, the product is added
2 click - although the product is added, it is added again and there are two same products in the array now. I've got the array with the two same products.
3 click - console shows "Added yet!", now it recognizes that the product is in the array yet.
UPD
The product is an object of type:
{
"id" : "1",
"title" : "Title 1",
"color" : "white"
}
How to fix the issue?
hasOwnProperty is for checking if a key exists in an object, you're using it for an array. Use this instead:
addToCart(prod){
this.cartProducts.indexOf(prod) > -1 ? console.log("Added yet!") : this.cartProducts.push(prod);
console.log(this.cartProducts)
}
try this :
let idx = this.cartProducts.findIndex(elem => {
return prod === elem
})
if (idx !== -1) {
console.log("Added yet!")
} else {
this.cartProducts.push(prod);
}

Angular : Detect click event inside a div with ngFor inside

I'm working on an Application with a lot of dropdowns, I would like to be able to close the dropdown whenever a click happens outside of this one.
I found some good solutions, but none of them handle the case of having a ngFor in it, when I log the click event target in the ngFor, I get the element but this one doesn't have any parent. I can not detect it with 'find' or 'contains' neither.
Does someone have a solution to detect if this target is part of the dropdown ?
the directive
import {
Directive,
ElementRef,
EventEmitter,
Input,
OnInit,
Output,
SimpleChange
} from '#angular/core';
#Directive({selector: '[clickOutside]'})
export class ClickOutside implements OnInit {
#Output() clickOutside:EventEmitter<Event> = new EventEmitter<Event>();
constructor(private _el:ElementRef) {
this.onClickBody = this.onClickBody.bind(this);
}
ngOnInit() {
document.body.addEventListener('click', this.onClickBody);
}
private onClickBody(e:Event) {
if (!this.isClickInElement(e)) {
this.clickOutside.emit(e);
}
}
private isClickInElement(e:any):boolean {
var current = e.target;
do {
console.log(current);
if ( current === this._el.nativeElement ) {
return( true );
}
current = current.parentNode;
} while ( current );
return false;
}
}
Example of where I call the directive
<div (clickOutside)="onClickedOutside($event)">
<ul>
<li *ngFor="let item of itemsList" (click)="selectItem(item)">
<span class="item">
{{item.name}}
</span>
</li>
</ul>
</div>
When I click on item.name, console.log(current); returns me two lines
<span>Item</span>
<li>
<span>Item</span>
</li>
#Directive({selector: '[clickOutside]'})
export class ClickOutside implements OnInit {
#Output() clickOutside:EventEmitter<Event> = new EventEmitter<Event>();
constructor(private _eref: ElementRef) { }
#HostListener('window:click')
private onClickBody(e:Event) {
if (!this.isClickInElement(e)) {
this.clickOutside.emit(e);
}
}
private isClickInElement(e:any):boolean {
return this._eref.nativeElement.contains(event.target);
}
}
See also https://stackoverflow.com/a/35713421/217408
This solution works with Chrome but unfortunately not with IE. I'm still looking for another way to do it
private isClickInElement(e:any):boolean {
var current = e.target;
if(current == this._el.nativeElement) {
return true;
}
for(let parentKey in e.path) {
if(e.path[parentKey] == this._el.nativeElement) {
return true;
}
}
return false;
}

Categories

Resources