Angular 2: Change Detection of array inside the object - arrays

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
}

Related

how to change value in struct globally in swift

So I have an api request that requests a bunch of data from a fake api url, the data I am getting is being put on a placeholder, I just want to have a global variable to be able to use that array of codable data in my collectionviews.
struct productsList{
static var itemsList = [ProductItem]()
}
func getProducts() {
storeRepo
.getAllProducts()
.subscribe { result in
productsList.itemsList = result
for item in productsList.itemsList{
print(item.category)
}
} onError: { error in
print(error.localizedDescription)
}.disposed(by: disposeBag)
}
func printReuslt() {
for i in productsList.itemsList{
print(i.id)
}
}
note that it's not printing the printResult() but it's looping inside of the .subscribe
note that i am using Moya as well as RXswift
What you're looking for is called a Singleton. Swift makes this extremely easy to do. Basically, the short and sweet is that the struct you create, initializes itself as a property of itself. Anytime you access (In this example) APIHandler.shared you'll get a reference to the only single object, which has your other properties dataObj1 and someObj2 from this example.
class APIHandler {
let shared = APIHandler()
var dataObj1: YourObj?
var someObj2: YourObj2?
init() {
self.someObj1 = yourMethodCall()
self.someObj2 = someCalculation()
}
}
This is how you access it from another class. BE CAREFUL you can access APIHandler.someObj which would result in a null reference exception if you don't have an object created, so when doing this always access the shared property.
class MainClass {
let apiHandler: APIHandler?
override func viewDidLoad(...) {
super.viewDidLoad(...)
apiHandler = APIHandler.shared
}
}

Angular2 ngOnChanges clone #Input array

I'm using a DashboardComponent that gets the data from my DashboardService. This Component then passes my array of objects to my form component.
(Plunkr link at bottom of post)
DashboardComponent.ts
private bottleArray: Bottle[] = [];
ngOnInit() {
// Get bottle types from service to the form needing them
this.dashboardService.getBottleTypesAndNames()
.subscribe(bottlesData => {
bottlesData.forEach(bottle => {
// Convert to Bottle type
let bottleObject: Bottle = new Bottle(bottle.bottleTypeId, bottle.bottleName);
this.bottleArray.push(bottleObject);
});
});
}
DashboardComponent.html
<ct-create-order-form [bottleArray]="bottleArray"> </ct-create-order-form>
I did it that way so that my form components linked to my Dashboard won't be doing any call to my service.
I'm trying to clone my #Input so that my data updated from the form is not linked to my parent component (Dashboard), but I can't seem to do it... See code below :
CreateOrderFormComponent.ts
export class CreateOrderFormComponent implements OnChanges {
#Input() private bottleArray: Bottle[];
constructor() { }
private clonedBottleArray: BottleCommand[];
ngOnChanges(changes) {
if (changes.bottleArray) {
let test: BottleCommand[] = changes.bottleArray.currentValue;
// Cloning
console.log(test); // Array of 6 Bottles
this.clonedBottleArray = [...test];
console.log(this.clonedBottleArray); // Empty Array
this.clonedBottleArray = Array.from(test);
console.log(this.clonedBottleArray); // Empty Array
this.clonedBottleArray = test.slice();
console.log(this.clonedBottleArray); // Empty Array
this.clonedBottleArray = test;
console.log(this.clonedBottleArray); // Array of 6 bottles
}
}
Is there any way to achieve what I am doing ? I don't understand why I can't clone my Input when I get the data ?
From this Youtube video made by AngularConnect, he is doing the exact same except that he is manipulating an Object, and I'm manipulating an Array of Objets.
https://youtu.be/-nsedZwvl9U?t=12m22s
EDIT : After creating a Plunkr, this seems to be working correctly in there.
https://plnkr.co/edit/js1vl0fcgOKtQNqXsWTL?p=preview
EDIT 2 : At the ngOnInit() from my DashboardComponent, if I mock the data, it is cloned correctly in my child component.
Looks like angular OnChange not firing due to it specific way of checking, here's brief explanation from this answer:
During change detection, when Angular checks components' input properties for change, it uses (essentially) === for dirty checking. For arrays, this means the array references (only) are dirty checked. Since the rawLapsData array reference isn't changing, ngOnChanges() will not be called.
And in your example, you're .pushing bottles in bottleArray, so OnChange doesn't fire on the same array reference.
To get the changes, you could use DoCheck:
ngDoCheck() {
console.log(this.bottleArray);
this.clonedBottleArray = [...this.bottleArray].slice(0, 4);
console.log(this.clonedBottleArray);
}
it will fire when you push new values to the bottleArray. Working plunker here.

Displaying data from Firebase in React without arrays

I am new to both React and Firebase. I struggled a bit to get data from the database, even though the instructions on the Firebase website were pretty straightforward.
I managed to print data in the view by using this code:
Get data from DB and save it in state:
INSTRUMENTS_DB.once('value').then(function(snapshot) {
this.state.instruments.push(snapshot.val());
this.setState({
instruments: this.state.instruments
});
From Firebase, I receive and Object containing several objects, which correspond to the differen instruments, like shown in the following snippet:
Object {
Object {
name: "Electric guitar",
image: "img/guitar.svg"
}
Object {
name: "Bass guitar",
image: "img/bass.svg"
}
// and so on..
}
Currently, I print data by populating an array like this:
var rows = [];
for (var obj in this.state.instruments[0]) {
rows.push(<Instrument name={this.state.instruments[0][obj].name}
image={this.state.instruments[0][obj].image}/>);
}
I feel like there's a better way to do it, can somedody give a hint? Thanks
I user firebase a lot and mu solution is little ES6 helper function
const toArray = function (firebaseObj) {
return Object.keys(firebaseObj).map((key)=> {
return Object.assign(firebaseObj[key], {key});
})
};
I also assign the firebase key to object key property, so later I can work with the keys.
The native map function only works for arrays, so using directly it on this object won't work.
What you can do instead is:
Call the map function on the keys of your object using Object.keys():
getInstrumentRows() {
const instruments = this.state.instruments;
Object.keys(instruments).map((key, index) => {
let instrument = instruments[key];
// You can now use instrument.name and instrument.image
return <Instrument name={instrument.name} image={instrument.image}/>
});
}
Alternatively, you can also import the lodash library and use its map method which would allow you to refactor the above code into:
getInstrumentRowsUsingLodash() {
const instruments = this.state.instruments;
_.map(instruments, (key, index) => {
let instrument = instruments[key];
// You can now use instrument.name and instrument.image
return <Instrument name={instrument.name} image={instrument.image}/>
});
}
Side note:
When you retrieve you data from Firebase you attempt to update the state directly with a call on this.state.instruments. The state in React should be treated as Immutable and should not be mutated with direct calls to it like push.
I would use map function:
_getInstrumentRows() {
const instruments = this.state.instruments[0];
if (instruments) {
return instruments.map((instrument) =>
<Instrument name={instrument.name}
image={instrument.image}/>);
}
}
In your render() method you just use {_getInstrumentRows()} wherever you need it.

Angular2 dont fire changeDetection after click

Angular2 doesn't trigger the ChangeDetection after a click event. The code snippets below are to get the data from one component to another.
onClickEvent
(click)="$event.preventDefault(); setApartmentObject(FlatObject)";
ApartmentOverviewComponent
constructor(private _apart:ApartmentService) {}
setApartmentObject(flat:ApartmentObject) {
this._apart.setApartmentDetails(flat);
}
ApartmentService
Injectable()
export class ApartmentService {
apartmentDetails:ApartmentObject
getApartmentDetails():Observable<ApartmentObject> {
return Observable.create((observer) => {
observer.next(this.apartmentDetails);
observer.complete();
});
}
setApartmentDetails(value:ApartmentObject) {
this.apartmentDetails = value;
}
}
ApartmentDetailComponent
constructor(private _apart:ApartmentService)
get apartmentDetails() {
this._apart.getApartmentDetails().subscribe(data => {
this._apartmentDetails = data;
});
return this._apartmentDetails;
}
In the HTML file
<p><strong>{{apartmentDetails.name || 'Musterwohnung'}}</strong></p>
I also tried to fix this problem with an eventemitter, but without success. Only the following dirty fix works:
constructor(private _ref:ChangeDetectorRef) {
this._ref.detach();
setInterval(() => {
this._ref.detectChanges();
}, 300);
}
There are some issues with your code that actually prevent the value from being read.
First of all—in your service—when you set the value, you just do it on the service's instance, instead of feeding it to the observable object. The observable just can't know that value has changed, so it won't emit the change (next) event. This is why the ApartmentOverviewComponent. setApartmentObject() does nothing. To actually feed the observable with data, you need to use a Subject.
In the ApartmentDetailComponent, in this simple scenario (where data is always synchronously provided), you could get the value in the way you try it. But, as mentioned before, the data won't ever change. It's also needles to store the data on the component's instance's _apartmentDetails field. You could use the observable in your template.
The working implementation is like that:
#Injectable()
class ApartmentService {
// BehaviorSubject is a type of an Observable that can be manually fed with data
// and returns it's last value to any subscriber.
apartmentDetails = new BehaviorSubject<ApartmentObject>({name: 'Musterwohnung'});
// Instead of using a property of the service, just inform the
// subject about knew data and let it spread the change for you.
setApartmentDetails(value: ApartmentObject) {
this.apartmentDetails.next(value);
}
}
#Component({
selector: 'overview-cmp',
// Side note: you don't need to .preventDefault() here.
template: `<a (click)="setApartmentObject({name: 'Shiny Aparament'})">click</a>`
})
class ApartmentOverviewComponent {
constructor(private apartService: ApartmentService) {}
// Works same as before.
setApartmentObject(flat: ApartmentObject) {
this.apartService.setApartmentDetails(flat);
}
}
#Component({
selector: 'details-cmp',
// Use the 'async' pipe to access the data stored in an Observable
// object. Also, to secure the code, use '?' to safely access the property.
template: `<p><strong>{{(details | async)?.name}}</strong></p>`
})
class Apartament {
// This is the observable with data.
details: Observable<ApartmentObject>;
constructor(private apartService: ApartmentService) {}
// When component initialises, assign the observable data from the service
ngOnInit() {
this.details = this.apartService.apartmentDetails;
}
}

Getting elements from array based on property values (AngularJS)

I have an array of players, each player is an object that has a number of properties, one is "goals".
var players = [
{
"id":"4634",
"name":"A. Turan",
"number":"0",
"age":"28",
"position":"M",
"goals":"1"
},
{
"id":"155410",
"name":"H. Çalhano?lu",
"number":"0",
"age":"21",
"position":"A",
"goals":"0"
},
{
"id":"4788",
"name":"B. Y?lmaz",
"number":"0",
"age":"30",
"position":"A",
"goals":"2",
}
]
I've written a function to cycle through the array and push every element that has more than '0' goals to an array, topScorers. Like so:
$scope.topScorerSearch = function() {
var topScorers = [];
$scope.teamDetails.squad.forEach(function(o) {
if (o.goals > 0) {
topScorers.push(o)
}
});
return topScorers;
}
With the function called as {{topScorerSearch()}}.
This returns only players who have scored. Perfect.
However, I want to run this on other properties, which will result in a lot of repetitious code. How can I make this a general purpose function that can be executed on different properties?
I tried including the 'prop' parameter, but it didn't work:
$scope.topScorerSearch = function(prop) {
var topScorers = [];
$scope.teamDetails.squad.forEach(function(o) {
if (o.prop > 0) {
topScorers.push(o)
}
});
return topScorers;
}
...and called the function like this:
{{topScorerSearch(goals)}}
Why doesn't this work? Where am I going wrong?
I believe the issue is that prop will not resolve to goals because goals is being treated as a variable with a null or undefined value, making prop null or undefined.
If you use the alternative way of accessing object properties object["property"] and use the function {{topScorers("goals")}} it should work out.

Resources