I have a an observable array that binds to a data grid in the html and this is working fine. My component.ts gets the array like:
this.data = this.store.pipe(select(selectData));
where selectData is an ngrx action. I'm trying to set the value of an input outside the data grid based on the first value in the data array. I'm trying something like
this.input= this.data.pipe(map(m =>
moment(new Date(m[0].timestamp / 1000000)).format('DD-MM-YYYY HH:mm:ss')));
When I'm debugging this, I can see the break point on the line with moment(new Date... hitting. However, the m at this time is an empty array. Both of the above statements are declared in ngOnInit like
ngOnInit(): void {
this.data = this.store.pipe(select(selectData));
this.input= this.data.pipe(map(m =>
moment(new Date(m[0].timestamp / 1000000)).format('DD-MM-YYYY HH:mm:ss')));
}
It looks like the observable is resolved before the array is actually populated causing the input to be undefined. How can I make sure that the this.data array is actually fully populated by the time I try to set the this.input?
You need to subscribe in order for the selector to pick up data from the store. Make the following change:
ngOnInit(): void {
this.store.pipe(select(selectData))
.subscribe(m => {
this.input = moment(new Date(m[0].timestamp / 1000000))
.format('DD-MM-YYYY HH:mm:ss');
});
}
Related
I'm trying to grab these arrays from one of the nasa open apis -> https://api.nasa.gov/neo/rest/v1/feed?start_date=START_DATE&end_date=END_DATE&api_key=API_KEY
I have a dynamic date in the params so the objects returned match the date, is there any way I can use my date (even though its a string) and turn it into a 'key' value so I can dynamically grab the objects I need?
like => "2021-08-26" would reference { 2021-08-26: [{},{},{}...] }
I have included my code so far with what I've tried, currently I'm trying to just select all the keys inside the {near_earth_objects} using forEach but I'm still getting an error data.near_earth_objects.forEach is not a function
constructor(){
super();
this.state={
asteroids:[],
time: []
}
}
//grab the current time (year-month-day) and store it in the state
componentWillMount(){
var today = new Date();
var start = today.getFullYear()+'-'+0+(today.getMonth()+1)+'-'+today.getDate();
var end = today.getFullYear()+'-'+0+(today.getMonth()+1)+'-'+(today.getDate()+1);
this.setState({time: [start,end]})
}
componentDidMount(){
fetch(`https://api.nasa.gov/neo/rest/v1/feed?start_date=${this.state.time[0]}&end_date=${this.state.time[1]}&api_key=ipAxYzaENbqRKb7GgzFPcH6QUBsHXY3QKB7uXOf5`
)
.then(response => response.json())
.then((data) => {
let asteroids = []
data.near_earth_objects.forEach((arr)=>{
asteroids.push(arr)
})
this.setState({asteroids:asteroids})
});
}
here is an example of the logged data I'm trying to access
It's important to note that the data is coming back as an object where the values are arrays. Since the return data is an object you cannot iterate over it.
However, to get the data you can Object.values(object) (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/values) to get an array of the values. This will return something that looks like [ [...], [...] ]
After that you can iterate over this information.
In the end your code should look something like this:
Object.values(data.near_earth_objects).forEach((arr)=>{
asteroids.push(...arr)
})
I'm testing Material Table(mat-table) on Angular 7, here's a weird issue I ran into.
Send a request to jsonplaceholder for fake data in users.service
export class UsersService {
API_BASE = 'https://jsonplaceholder.typicode.com/users';
constructor(private http: HttpClient) {}
getUsers(): Observable<object> {
const url = this.API_BASE;
return this.http.get(url);
}
}
Because jsonplaceholder only returns 10 rows of data, so I concatenate the data for a larger array, say, 30 rows for testing pagination feature with ease. Meanwhile, update the 'id' field with iterate index so the 'id's looks like 1,2,3...30, instead of 1,2,3...10,1,2,3...10,1,2,3...10, which is a result of concatenation, that's it, nothing special.
users.component:
ngOnInit(): void {
this.userService.getUsers().subscribe((users: UserData[]) => {
users = users.concat(users, users);
users.forEach((user, index) => (user.id = index +1));
console.log(users);
this.dataSource.data = users;
});
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}
Although the table shows up beautifully, but the 'id's column looks weird, they are not 1,2,3...30 sequentially, instead, they are 21,22,23...30,21,22,23...30,21,22,23...30
I tried to print out the user.id inside the forEach loop, it's all good.
users.forEach((user, index) => {
user.id = index + 1;
console.log(user.id);
});
Where did I go wrong with this? Any clue? Thanks.
P.S, API used in the code: https://jsonplaceholder.typicode.com/users
even though you have 30 array elements after concatenating the array twice, you still only have 10 unique objects. the Object behind users[20] is the same as users[0], so you override the id of the already processed objects from index 10 to 29
you can fix this by creating a copy of each object. There are many ways too do this. a very simple way is serializing and deserializing using JSON.stringify and JSON.parse:
users.forEach(user => users.push(JSON.parse(JSON.stringify(user))));
I'm using ng-repeat to (guess) put array content in table.
Content is drawn dynamically, and it works well, when I'm modifying single elements of an array. But when I reload a whole array, there is this moment, when array is reassigned with new value, and ng-repeat draws blank table (which is actually logically correct). Is there a way to delay redrawing of content that way, the ng-repeat ignores the moment when the array is empty? Like the content is switched to new content without the 'clear' time.
I'm assigning new elements to array this way:
items = newItems;
where items is the array ng-repeat uses and newItems is an array of items freshly downloaded from database. The newItems is complete, when the assignment occurres. I'm not doing items = []; before the assignemt.
I'm usign angular 1.3
EDIT:
the ng-repeat:
<tr ng-repeat="order in submittedOrders">
stuff
<\tr>
js:
`$scope.reloadView = function() {
$scope.submittedOrders = OrdersService.getOrdersByStatus(ORDER_STATUS.submitted);
};`
Can it be the that the table is cleared in the first place, before call to database(service takes data from database) and during the wait, the table is cleared?
You may have to make use of Observables and async pipe of Angular.
Here are few steps you can take:
Convert your newItems to a rxjs Subject.
newItems$ = new Subject();
Whenever you get new values for your array, emit them via subject.
this.newItems$.next(newItems);
Make the items an observable of newItems$, and filter out empty arrays.
items = this.newItems$.pipe(
filter((a:any[]) => {
return a.length != 0;
})
);
In your template, use async pipe to iterate over array.
*ngFor="item of items | async"
Below is relevant parts of code that can get you started.
import { Observable, of, from, Subject } from 'rxjs';
import { filter, mapTo } from 'rxjs/operators';
...
newItems$ = new Subject();
items = this.newItems$.pipe(
filter((a:any[]) => {
return a.length != 0;
})
);
...
// A test method - link it to (click) handler of any div/button in your template
// This method will emit a non-empty array first, then, after 1 second emit an empty
// array, and then, after 2 seconds it will emit a non-empty array again with updated
// values.
testMethod() {
this.newItems$.next([3,4,5]);
setTimeout((v) => {
console.log("Emptying the array - should not be displayed browser");
this.newItems$.next([]);
}, 1000);
setTimeout((v) => {
console.log("Updating the array - should be displayed in browser");
this.newItems$.next([3,4,4,5]);
}, 2000);
}
In one component I can filter my array using the following:
// Array of product objects
const result = products.filter(p => p.name.includes('val'));
and value of products remains same as the first value but filtered value stores in result.
But in the following code, filter() filters array of strings itself:
// Array of strings
const result = strs.filter(s => s.includes('val'));
The question is how can I filter strings and return result without modifying the strs itself?
Note: I tried with array.filter(function() { return res; }); but didn't make any change.
It returns the filtered ones and don't change the actual array. You are doing something wrong
const strs = ['valval', 'bal', 'gal', 'dalval'];
const result = strs.filter(s => s.includes('val'));
console.log(strs);
console.log(result);
First thing we need to know is, if we filter our list we loose our original data
products: any[] = [
{
"productId": 1,
"productName": "foo-bar",
"price": 32.99
}
]
and can't get it back without re-getting the data from it's source so we have to make another list to store the filtered list.
filteredProduce: any[];
Next if you are working to show a list of filtered product on a grid or something like this we need a way to know when the user changes the filter criteria. we could use event binding and watch for key presses or value changes, but an easier way is to change our _listFilter property into a getter and setter, like this
get listFilter: string {
return this._listFilter;
}
set listFilter(value:string) {
this._listFilter= value;
}
next we want to set our filteredProducts array to the filtered list of products like this
set listFilter(value:string) {
this._listFilter= value;
this.filteredProducts = this._listFilter? this.performFilter(this._listFilter) : this.products;
}
in preceding code we are using js conditional operator to handle the posibility that _listFilterstring is empty, null or undefined.
Next we use this.performFilter(this._listFilter) to filter our list.
performFilter(filterBy: string): any[] {
filterBy = filterBy.toLocaleLowerCase();
return this.products.filter((product: any) =>
product.productName.toLocaleLowerCase().indexOf(filterBy) !== -1);
}
Finally we should assign the main list of products to the filteredProducts and _listFilter to what we want.
constructor() {
this.filteredProducts = this.products;
this._listFilter= 'foo-bar';
}
last step is to change our template to bind to our filteredProducts property.
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.