I've got an object in my $scope that contains a bunch of details about, say, an election. This object includes a voters array of objects, each with an _id:
$scope.election = {
voters: [
{ _id: '123' },
{ _id: '456' },
{ _id: '789' }
]
}
Also in my scope I have details about the currently logged in user:
$scope.user = { _id: '456' }
How can I bind ng-disabled to the presence of $scope.user._id in the array of objects $scope.voters?
What I've Tried
I have success simply displaying the presence of $scope.user._id in $scope.election.voters like this (Jade syntax):
pre(ng-bind="election.voters | filter:{user._id} | json")
When the current user is among the voters, they get displayed. When they're not among the voters, I get an empty array. That seems quite close to what I want.
But using the same filter (sans | json) with ng-disabled, I get the Angular Infinite $digest loop error.
Is this situation too complicated? Should I move it to a $filter? If so, how would I go about making it generic enough to be useful in a number of situations (if that's even feasible)?
Can run a simple filter right in controller, or using app.filter('filterName', func...) create a custom filter you can use in markup
$scope.userIsVoter = function() {
return $scope.election.voters.filter(function(el) {
return el._id == $scope.user._id;
}).length
}
<button ng-disabled="userIsVoter()">Do Something</button>
Related
// my db structure now
rcv : {
visible: 'all',
ids: [
[0] : userId,
[1] : user2Id ]
}
this is how i query to get the data it works.
//service.ts
getAlbumByUserId(userId) {
return this.afs.collection('albums', ref => ref.where('rcv.visible', '==', 'all').where('rcv.ids', 'array-contains', userId)).valueChanges();
}
//component.ts
this.service.getAlbumByUserId(this.userId);
but i want to set the structure like this but i don't know how to query nested objects in firebase
// database structure
rcv : {
visible: 'all',
ids: {
userId: {
id: userId
}
user2Id: {
id: user2Id
}
}
}
You're looking for the array-contains operator, which can check if a field that is an array contains a certain value.
You're already using the correct array-contains operator, but not with the correct syntax. The array-contains operator checks whether any element of your array is exactly the same as the value you pass in. So you need to pass in the complete value that exists in the array:
ref.where('rcv.visible', '==', 'all').where('rcv.ids', 'array-contains', { id: userId })
As you add more data to the array, it may become unfeasible to reproduce the entire array element for the query. In that case, the common approach is to add an additional field where you keep just the IDs.
So you'd end up with one field (say rcv.users) where you keep all details about the receiving users, and one field (say rcv.ids) where you just keep their IDs, and that you use for querying.
I'm using the query below to get all forms from a model;
query = await Form.find({}).populate('assigned_to');
the query result is as below;
[{
**
Some Other Fields **
assigned_to: {
links: [Array],
_id: 5 d7a903d8b8f3e0ced2dd308,
name: 'Test E 2',
email: 'te2#somedomain.com',
school: 5 d79e99b4d4df989ea771525,
department: 5 d79e99b4d4df989ea771526,
role: 'some_role',
__v: 0
},
},
**
OTHER OBJECT
**
]
Running an Array.prototype.filter;
waiting = query.filter(form =>
form.assigned_to._id == req.user._id);
Trying to accessing the property _id of the nested object assigned_to returns an error of;
Cannot read property 'id' of undefined
I understand the nature of the error being that not all forms have an assigned_to nested object at the same time which would result in that error because running a filter on the form solely which has that nested objects passes.
How do I improve this filter to cater for the possibility that not all forms have assigned_to nested object?
EDIT: I didn't originally indicate that it query was an array of objects for brevity.
Answer for your question "How do I improve this filter to cater for the possibility that not forms have assigned_to nested object?". Make sure query returns an array.
This will make sure if key "assigned_to" is present. You could use filter method like :-
arr.filter(elem => (elem.assigned_to && elem.assigned_to._id &&
elem.assigned_to._id === req.user._id));
Another way
arr.filter(elem => {
if(elem.assigned_to && elem.assigned_to._id) {
if (elem.assigned_to._id === req.user._id) {
return elem;
}
}
});
I am relatively new to Angular (more experienced with older version of Angular). I have two APIs, one is "/vehicles" and another is "/vehicle/{id}".
I get the "/vehicles" data and I loop through, match the IDs and then do another API call to "/vehicle/{id}" to get additional data for that particular vehicle and then create a new data object.
This all works but I get a console log error of Cannot read property 'id' of undefined so I'm assuming the template is looking for this data before it's finished getting the data (because the page loads as it should but can't get rid of this error).
This is the part of HTML issue I get - <h2>{{ car.id | uppercase }} {{ car.modelYear | uppercase }}</h2>
cars: Cars[] = [];
this.restApi.getCars().subscribe((cars: any = []) => {
cars.forEach((car: any = {}, i: string) => {
this.restApi.getCar(car.id).subscribe((c: any = {}) => {
if (car.id === c.id) {
this.cars[i] = {...c, ...car};
}
});
});
});
Here are the console logs of data
cars = [
{id: "xe", modelYear: "k17", url: "/api/vehicle/xe", media: Array(1)}
{id: "xf", modelYear: "k17", url: "/api/vehicle/xf", media: Array(1)}
{id: "xj", modelYear: "k16", url: "/api/vehicle/xj", media: Array(1)}
]
car = {
description: "Premium luxury saloon, spacious and beautiful yet powerfully agile."
id: "xj"
meta: {passengers: 5, drivetrain: Array(2), bodystyles: Array(2), emissions: {…}}
price: "£50,000"
}
My Rest Api service
getCars() {
return this.http.get(`${this.apiURL}/vehicles/`)
.pipe(
retry(1),
catchError(this.handleError)
);
}
getCar(id: string) {
return this.http.get(`${this.apiURL}/vehicle/${id}`)
.pipe(
retry(1),
catchError(this.handleError)
);
}
Thanks in advance
You should be careful when chaining observable subscribe. A better approach is to use rxjs operator like mergeMap to combine multiple http cals
this.restApi.getCars().pipe(
mergeMap((cars) => {
return cars.map( car => this.restApi.getCar(car.id).pipe(
map(carDetail => Object.assign(car, carDetail))
));
})
).subscribe((cars: any = []) => {
this.cars = cars
});
You are right here in assuming, that angular is attempting to access the data in cars array, before the service call is getting completed, to render the template. As the service is yet to return a response, angular gets undefined for all the child elements of car array.
One way to get rid of this problem would be to loop over the cars array instead of accessing the child element by specifying the index number(which I'm unsure if you're doing or not) as below -
<div *ngFor=" car in cars">
<h1>{{car.id}}</h1>
</div>
This will prevent angular from accessing child elements of cars array in case it is empty.
Another way to get rid of it would be to use a conditional render method. Simply add an if loop for the block rendering the cars data to render only if length of cars array is greater than zero. For instance,
<div ngIf="cars.length > 0">
<h1>{{cars[0].id}}</h1>
</div>
Although, this is something, I'm saying without having a proper look at your template file. Do let me know if this helps or if there's anything else I can help with
I'm creating a StencilJS app (no framework) with a Google Firestore backend, and I want to use the RxFire and RxJS libraries as much as possible to simplify data access code. How can I combine into a single observable stream data coming from two different collections that use a reference ID?
There are several examples online that I've read through and tried, each one using a different combination of operators with a different level of nested complexity. https://www.learnrxjs.io/ seems like a good resource, but it does not provide line-of-business examples that make sense to me. This question is very similar, and maybe the only difference is some translation into using RxFire? Still looking at that. Just for comparison, in SQL this would be a SELECT statement with an INNER JOIN on the reference ID.
Specifically, I have a collection for Games:
{ id: "abc000001", name: "Billiards" },
{ id: "abc000002", name: "Croquet" },
...
and a collection for Game Sessions:
{ id: "xyz000001", userId: "usr000001", gameId: "abc000001", duration: 30 },
{ id: "xyz000002", userId: "usr000001", gameId: "abc000001", duration: 45 },
{ id: "xyz000003", userId: "usr000001", gameId: "abc000002", duration: 55 },
...
And I want to observe a merged collection of Game Sessions where gameId is essentially replace with Game.name.
I current have a game-sessions-service.ts with a function to get sessions for a particular user:
import { collectionData } from 'rxfire/firestore';
import { Observable } from 'rxjs';
import { GameSession } from '../interfaces';
observeUserGameSesssions(userId: string): Observable<GameSession[]> {
let collectionRef = this.db.collection('game-sessions');
let query = collectionRef.where('userId', '==', userId);
return collectionData(query, 'id);
}
And I've tried variations of things with pipe and mergeMap, but I don't understand how to make them all fit together properly. I would like to establish an interface GameSessionView to represent the merged data:
export interface GameSessionView {
id: string,
userId: string,
gameName: string,
duration: number
}
observeUserGameSessionViews(userId: string): Observable<GameSessionView> {
this.observeUserGameSessions(userId)
.pipe(
mergeMap(sessions => {
// What do I do here? Iterate over sessions
// and embed other observables for each document?
}
)
}
Possibly, I'm just stuck in a normalized way of thinking, so I'm open to suggestions on better ways to manage the data. I just don't want too much duplication to keep synchronized.
You can use the following code (also available as Stackblitz):
const games: Game[] = [...];
const gameSessions: GameSession[] = [...];
combineLatest(
of(games),
of(gameSessions)
).pipe(
switchMap(results => {
const [gamesRes, gameSessionsRes] = results;
const gameSessionViews: GameSessionView[] = gameSessionsRes.map(gameSession => ({
id: gameSession.id,
userId: gameSession.userId,
gameName: gamesRes.find(game => game.id === gameSession.gameId).name,
duration: gameSession.duration
}));
return of(gameSessionViews);
})
).subscribe(mergedData => console.log(mergedData));
Explanation:
With combineLatest you can combine the latest values from a number of Obervables. It can be used if you have "multiple (..) observables that rely on eachother for some calculation or determination".
So assuming you lists of Games and GameSessions are Observables, you can combine the values of each list.
Within the switchMap you create new objects of type GameSessionView by iterating over your GameSessions, use the attributes id, userId and duration and find the value for gameName within the second list of Games by gameId. Mind that there is no error handling in this example.
As switchMap expects that you return another Observable, the merged list will be returned with of(gameSessionViews).
Finally, you can subscribe to this process and see the expected result.
For sure this is not the only way you can do it, but I find it the simplest one.
I get a list of objects from an API:
let sold = [
{ objId: 3240747,
soldDate: '2018-09-27',
soldPrice: 4610000,
apartmentNumber: '1202',
soldPriceSource: 'bid',
},
{ objId: 3234263,
soldDate: '2018-09-24',
soldPrice: 2580000,
soldPriceSource: 'bid',
}
...
]
I store these in a collection:
soldCollection.insertMany(sold)
Some of the objects have been retrieved before, and I only want to store the once that are not already in the database.
dbo.collection("sold").createIndex({ "objId": 1 }, { unique: true })
What would be an efficient way of doing this? Should I ask for each object before storing it or is there method for dealing with this?
By default insertMany will stop inserting when first error occurs (E11000 duplicate key error in this case). You can change that behavior by specifying ordered parameter set to false. In that case you'll get a list of errors from failed inserts however all valid documents will be inserted succesfully:
db.sold.insertMany(sold, { ordered: false })
docs example here